From 1404b083e7c2bf556ef0b40e9ee9f8fa097353d5 Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 29 Aug 2024 18:28:22 +0200 Subject: [PATCH 001/182] feat: initial commit --- .gitmodules | 3 + lib/morpho-blue | 1 + script/Counter.s.sol | 19 --- src/Counter.sol | 14 --- src/LiquidationProtection.sol | 225 ++++++++++++++++++++++++++++++++++ test/Counter.t.sol | 24 ---- 6 files changed, 229 insertions(+), 57 deletions(-) create mode 160000 lib/morpho-blue delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol create mode 100644 src/LiquidationProtection.sol delete mode 100644 test/Counter.t.sol diff --git a/.gitmodules b/.gitmodules index 888d42d..ba6bc3a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [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 = git@github.com:morpho-org/morpho-blue.git diff --git a/lib/morpho-blue b/lib/morpho-blue new file mode 160000 index 0000000..8e35224 --- /dev/null +++ b/lib/morpho-blue @@ -0,0 +1 @@ +Subproject commit 8e35224dfc69eebf68ad67408bb1154173aeed16 diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol new file mode 100644 index 0000000..3ce8e25 --- /dev/null +++ b/src/LiquidationProtection.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +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 {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; +import {IERC20} from "../lib/morpho-blue/src/interfaces/IERC20.sol"; +import {SafeTransferLib} from "../lib/morpho-blue/src/libraries/SafeTransferLib.sol"; +import {IMorphoLiquidateCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; +import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; +import {MathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; +import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol"; + +struct SubscriptionParams { + Id marketId; + address borrower; + uint256 slltv; + uint256 closeFactor; + uint256 liquidationIncentive; +} + +contract LiquidationProtection { + using MarketParamsLib for MarketParams; + using UtilsLib for uint256; + using SafeTransferLib for IERC20; + using SharesMathLib for uint256; + using MathLib for uint256; + using MathLib for uint128; + + address immutable MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + + mapping(uint256 => SubscriptionParams) subscriptions; + mapping(uint256 => bool) isValidSubscriptionId; + uint256 nbSubscriptions; + + // TODO EIP-712 signature + // TODO authorize this contract on morpho + + function subscribe( + SubscriptionParams calldata subscriptionParams + ) public returns (uint256) { + IMorpho morpho = IMorpho(MORPHO); + MarketParams memory marketParams = morpho.idToMarketParams( + subscriptionParams.marketId + ); + + require( + msg.sender == subscriptionParams.borrower, + "Unauthorized account" + ); + require( + subscriptionParams.slltv < marketParams.lltv, + "Liquidation threshold higher than market LLTV" + ); + // should close factor be lower than 100% ? + // should there be a max liquidation incentive ? + + isValidSubscriptionId[nbSubscriptions] = true; + subscriptions[nbSubscriptions] = subscriptionParams; + + nbSubscriptions++; + + return nbSubscriptions - 1; + } + + function unsubscribe(uint256 subscriptionId) public { + require( + msg.sender == subscriptions[subscriptionId].borrower, + "Unauthorized account" + ); + + isValidSubscriptionId[subscriptionId] = false; + } + + function liquidate( + uint256 subscriptionId, + MarketParams calldata marketParams, + address borrower, + uint256 seizedAssets, + uint256 repaidShares, + bytes calldata data + ) public { + IMorpho morpho = IMorpho(MORPHO); + require( + isValidSubscriptionId[subscriptionId], + "Non-valid subscription" + ); + require(subscriptions[subscriptionId].borrower == borrower); + require( + Id.unwrap(subscriptions[subscriptionId].marketId) == + Id.unwrap(marketParams.id()) + ); + require( + UtilsLib.exactlyOneZero(seizedAssets, repaidShares), + "Inconsistent input" + ); + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + require( + !_isHealthy( + marketParams, + marketParams.id(), + borrower, + collateralPrice, + subscriptions[subscriptionId].slltv + ), + "Position is healthy" + ); + + // Compute seizedAssets or repaidShares and repaidAssets + + Market memory marketState = morpho.market(marketParams.id()); + if (seizedAssets > 0) { + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( + collateralPrice, + ORACLE_PRICE_SCALE + ); + + repaidShares = seizedAssetsQuoted + .wDivUp(subscriptions[subscriptionId].liquidationIncentive) + .toSharesUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); + } else { + seizedAssets = repaidShares + .toAssetsDown( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ) + .wMulDown(subscriptions[subscriptionId].liquidationIncentive) + .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + } + uint256 repaidAssets = repaidShares.toAssetsUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); + + // Check if liquidation is ok with close factor + Position memory borrowerPosition = morpho.position( + marketParams.id(), + borrower + ); + require( + borrowerPosition.collateral.wMulDown( + subscriptions[subscriptionId].closeFactor + ) > seizedAssets, + "Cannot liquidate more than close factor" + ); + + bytes memory callbackData = abi.encode( + marketParams.collateralToken, + marketParams.loanToken, + seizedAssets, + repaidAssets, + borrower, + msg.sender, + data + ); + morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); + + isValidSubscriptionId[subscriptionId] = false; + } + + function onMorphoRepay( + uint256 assets, + bytes calldata callbackData + ) external { + ( + MarketParams memory marketParams, + uint256 seizedAssets, + uint256 repaidAssets, + address borrower, + address liquidator, + bytes memory data + ) = abi.decode( + callbackData, + (MarketParams, uint256, uint256, address, address, bytes) + ); + + IMorpho morpho = IMorpho(MORPHO); + morpho.withdrawCollateral( + marketParams, + assets, + borrower, + address(this) + ); + + IERC20(marketParams.collateralToken).safeTransfer( + liquidator, + seizedAssets + ); + + IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assets, data); + + IERC20(marketParams.loanToken).safeTransferFrom( + liquidator, + address(this), + repaidAssets + ); + // TODO IERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); + } + + function _isHealthy( + MarketParams calldata marketParams, + Id id, + address borrower, + uint256 collateralPrice, + uint256 ltvThreshold + ) internal view returns (bool) { + IMorpho morpho = IMorpho(MORPHO); + Position memory borrowerPosition = morpho.position(id, borrower); + Market memory marketState = morpho.market(id); + + uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); + uint256 maxBorrow = uint256(borrowerPosition.collateral) + .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) + .wMulDown(ltvThreshold); + + return maxBorrow >= borrowed; + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} From 9f84b3e7911b72cb7d80e5779c9d836d38150f06 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 11:36:07 +0200 Subject: [PATCH 002/182] fix: stack too deep --- src/LiquidationProtection.sol | 43 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 3ce8e25..7fb27f0 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -32,7 +32,7 @@ contract LiquidationProtection { mapping(uint256 => SubscriptionParams) subscriptions; mapping(uint256 => bool) isValidSubscriptionId; - uint256 nbSubscriptions; + uint256 public nbSubscriptions; // TODO EIP-712 signature // TODO authorize this contract on morpho @@ -110,26 +110,31 @@ contract LiquidationProtection { // Compute seizedAssets or repaidShares and repaidAssets Market memory marketState = morpho.market(marketParams.id()); - if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( - collateralPrice, - ORACLE_PRICE_SCALE - ); - repaidShares = seizedAssetsQuoted - .wDivUp(subscriptions[subscriptionId].liquidationIncentive) - .toSharesUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares + { + uint256 liquidationIncentive = subscriptions[subscriptionId] + .liquidationIncentive; + if (seizedAssets > 0) { + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( + collateralPrice, + ORACLE_PRICE_SCALE ); - } else { - seizedAssets = repaidShares - .toAssetsDown( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ) - .wMulDown(subscriptions[subscriptionId].liquidationIncentive) - .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + + repaidShares = seizedAssetsQuoted + .wDivUp(liquidationIncentive) + .toSharesUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); + } else { + seizedAssets = repaidShares + .toAssetsDown( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ) + .wMulDown(liquidationIncentive) + .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + } } uint256 repaidAssets = repaidShares.toAssetsUp( marketState.totalBorrowAssets, From 453654132b7adf9d692fdac39a7613f443ed6f98 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 12:02:13 +0200 Subject: [PATCH 003/182] fix: handle empty callback --- src/LiquidationProtection.sol | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 7fb27f0..10793f7 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -186,17 +186,16 @@ contract LiquidationProtection { IMorpho morpho = IMorpho(MORPHO); morpho.withdrawCollateral( marketParams, - assets, + seizedAssets, borrower, - address(this) + liquidator ); - IERC20(marketParams.collateralToken).safeTransfer( - liquidator, - seizedAssets - ); - - IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assets, data); + if (data.length > 0) + IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate( + assets, + data + ); IERC20(marketParams.loanToken).safeTransferFrom( liquidator, From 14637df233c791667066551f2fefaa0dde91a048 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 12:13:14 +0200 Subject: [PATCH 004/182] fix: correct callback arg --- src/LiquidationProtection.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 10793f7..c61bbbe 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -154,8 +154,7 @@ contract LiquidationProtection { ); bytes memory callbackData = abi.encode( - marketParams.collateralToken, - marketParams.loanToken, + marketParams, seizedAssets, repaidAssets, borrower, From 8db8b6341e7a97dd573a197d3a02ac5809d03556 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 12:18:26 +0200 Subject: [PATCH 005/182] feat: first tests --- src/interfaces/IERC20.sol | 90 +++++++++++++++++++++ test/LiquidationProtection.t.sol | 130 +++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/interfaces/IERC20.sol create mode 100644 test/LiquidationProtection.t.sol diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol new file mode 100644 index 0000000..3489177 --- /dev/null +++ b/src/interfaces/IERC20.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.19; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance( + address owner, + address spender + ) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); +} diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol new file mode 100644 index 0000000..249db73 --- /dev/null +++ b/test/LiquidationProtection.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../lib/forge-std/src/Test.sol"; +import "../lib/forge-std/src/console.sol"; + +import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProtection.sol"; +import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {IERC20} from "../src/interfaces/IERC20.sol"; + +contract BaseTest is Test { + uint256 internal constant BLOCK_TIME = 12; + + address internal BORROWER; + address internal LIQUIDATOR; + + LiquidationProtection internal liquidationProtection; + Id internal marketId; + MarketParams internal market; + IMorpho morpho; + IERC20 loanToken; + IERC20 collateralToken; + + function setUp() public virtual { + BORROWER = makeAddr("Borrower"); + LIQUIDATOR = makeAddr("Liquidator"); + + address MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + morpho = IMorpho(MORPHO); + + marketId = Id.wrap( + 0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e + ); // wstETH/WETH (96.5%) + market = morpho.idToMarketParams(marketId); + loanToken = IERC20(market.loanToken); + collateralToken = IERC20(market.collateralToken); + + liquidationProtection = new LiquidationProtection(); + + vm.startPrank(BORROWER); + loanToken.approve(address(morpho), type(uint256).max); + collateralToken.approve(address(morpho), type(uint256).max); + + uint256 collateralAmount = 1 * 10 ** 18; + deal(address(collateralToken), BORROWER, collateralAmount); + morpho.supplyCollateral(market, collateralAmount, BORROWER, hex""); + uint256 borrowAmount = 5 * 10 ** 17; + morpho.borrow(market, borrowAmount, 0, BORROWER, BORROWER); + + morpho.setAuthorization(address(liquidationProtection), true); + + vm.startPrank(LIQUIDATOR); + deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); + loanToken.approve(address(liquidationProtection), type(uint256).max); + collateralToken.approve( + address(liquidationProtection), + type(uint256).max + ); + + // TODO implement in contract + vm.startPrank(address(liquidationProtection)); + loanToken.approve(address(morpho), type(uint256).max); + } + + function test_set_subscription() public virtual { + vm.startPrank(BORROWER); + + SubscriptionParams memory params; + params.borrower = BORROWER; + params.marketId = marketId; + params.closeFactor = 10 ** 18; // 100% + params.liquidationIncentive = 10 ** 16; // 1% + params.slltv = 90 * 10 ** 16; // 90% + + liquidationProtection.subscribe(params); + + assertEq(liquidationProtection.nbSubscriptions(), 1); + } + + function test_remove_subscription() public virtual { + vm.startPrank(BORROWER); + + SubscriptionParams memory params; + params.borrower = BORROWER; + params.marketId = marketId; + params.closeFactor = 10 ** 18; // 100% + params.liquidationIncentive = 10 ** 16; // 1% + params.slltv = 90 * 10 ** 16; // 90% + + uint256 subscriptionId = liquidationProtection.subscribe(params); + + liquidationProtection.unsubscribe(subscriptionId); + + vm.startPrank(LIQUIDATOR); + + vm.expectRevert(bytes("Non-valid subscription")); + liquidationProtection.liquidate( + subscriptionId, + market, + BORROWER, + 0, + 0, + hex"" + ); + } + + function test_soft_liquidation() public virtual { + vm.startPrank(BORROWER); + + SubscriptionParams memory params; + params.borrower = BORROWER; + params.marketId = marketId; + params.closeFactor = 10 ** 18; // 100% + params.liquidationIncentive = 10 ** 16; // 1% + params.slltv = 10 * 10 ** 16; // 10% + + uint256 subscriptionId = liquidationProtection.subscribe(params); + + vm.startPrank(LIQUIDATOR); + Position memory position = morpho.position(marketId, BORROWER); + liquidationProtection.liquidate( + subscriptionId, + market, + BORROWER, + 0, + position.borrowShares, + hex"" + ); + } +} From 3d40d228766412f6abafebd5c395be2ae4bae2b3 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 12:18:33 +0200 Subject: [PATCH 006/182] forge install: solmate --- .gitmodules | 3 +++ lib/solmate | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solmate diff --git a/.gitmodules b/.gitmodules index ba6bc3a..792e98b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/morpho-blue"] path = lib/morpho-blue url = git@github.com:morpho-org/morpho-blue.git +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 0000000..97bdb20 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit 97bdb2003b70382996a79a406813f76417b1cf90 From 65dd2b97fbbe67a83c0c183d3d8f7761ee654ce5 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 12:26:27 +0200 Subject: [PATCH 007/182] feat: use solmate erc20 lib --- src/LiquidationProtection.sol | 11 ++++++----- test/LiquidationProtection.t.sol | 4 ---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index c61bbbe..b4b4fc5 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -5,12 +5,12 @@ import {Id, MarketParams, IMorpho, Position, Market} from "../lib/morpho-blue/sr import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {UtilsLib} from "../lib/morpho-blue/src/libraries/UtilsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; -import {IERC20} from "../lib/morpho-blue/src/interfaces/IERC20.sol"; -import {SafeTransferLib} from "../lib/morpho-blue/src/libraries/SafeTransferLib.sol"; import {IMorphoLiquidateCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; import {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"; struct SubscriptionParams { Id marketId; @@ -23,10 +23,10 @@ struct SubscriptionParams { contract LiquidationProtection { using MarketParamsLib for MarketParams; using UtilsLib for uint256; - using SafeTransferLib for IERC20; using SharesMathLib for uint256; using MathLib for uint256; using MathLib for uint128; + using SafeTransferLib for ERC20; address immutable MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; @@ -196,12 +196,13 @@ contract LiquidationProtection { data ); - IERC20(marketParams.loanToken).safeTransferFrom( + ERC20(marketParams.loanToken).safeTransferFrom( liquidator, address(this), repaidAssets ); - // TODO IERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); + + ERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); } function _isHealthy( diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index 249db73..cbfe96b 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -56,10 +56,6 @@ contract BaseTest is Test { address(liquidationProtection), type(uint256).max ); - - // TODO implement in contract - vm.startPrank(address(liquidationProtection)); - loanToken.approve(address(morpho), type(uint256).max); } function test_set_subscription() public virtual { From 675ce0d58fc36887019934ea2008df1639210735 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 14:51:37 +0200 Subject: [PATCH 008/182] fix: remove unused arg --- src/LiquidationProtection.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b4b4fc5..cfff0a8 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -98,7 +98,6 @@ contract LiquidationProtection { uint256 collateralPrice = IOracle(marketParams.oracle).price(); require( !_isHealthy( - marketParams, marketParams.id(), borrower, collateralPrice, @@ -206,7 +205,6 @@ contract LiquidationProtection { } function _isHealthy( - MarketParams calldata marketParams, Id id, address borrower, uint256 collateralPrice, From afc0f96455c27abeec740097c9b4554d6e822011 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 14:54:01 +0200 Subject: [PATCH 009/182] style: adapt linter --- .prettierrc | 15 ++++ src/LiquidationProtection.sol | 120 +++++++------------------------ test/LiquidationProtection.t.sol | 27 ++----- 3 files changed, 44 insertions(+), 118 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..11a0f59 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "tabWidth": 2, + "printWidth": 100, + + "overrides": [ + { + "files": "*.sol", + "options": { + "tabWidth": 4, + "printWidth": 120 + } + } + ] + } + \ No newline at end of file diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index cfff0a8..7fa5f8b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -37,22 +37,12 @@ contract LiquidationProtection { // TODO EIP-712 signature // TODO authorize this contract on morpho - function subscribe( - SubscriptionParams calldata subscriptionParams - ) public returns (uint256) { + function subscribe(SubscriptionParams calldata subscriptionParams) public returns (uint256) { IMorpho morpho = IMorpho(MORPHO); - MarketParams memory marketParams = morpho.idToMarketParams( - subscriptionParams.marketId - ); + MarketParams memory marketParams = morpho.idToMarketParams(subscriptionParams.marketId); - require( - msg.sender == subscriptionParams.borrower, - "Unauthorized account" - ); - require( - subscriptionParams.slltv < marketParams.lltv, - "Liquidation threshold higher than market LLTV" - ); + require(msg.sender == subscriptionParams.borrower, "Unauthorized account"); + require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? @@ -65,10 +55,7 @@ contract LiquidationProtection { } function unsubscribe(uint256 subscriptionId) public { - require( - msg.sender == subscriptions[subscriptionId].borrower, - "Unauthorized account" - ); + require(msg.sender == subscriptions[subscriptionId].borrower, "Unauthorized account"); isValidSubscriptionId[subscriptionId] = false; } @@ -82,27 +69,13 @@ contract LiquidationProtection { bytes calldata data ) public { IMorpho morpho = IMorpho(MORPHO); - require( - isValidSubscriptionId[subscriptionId], - "Non-valid subscription" - ); + require(isValidSubscriptionId[subscriptionId], "Non-valid subscription"); require(subscriptions[subscriptionId].borrower == borrower); - require( - Id.unwrap(subscriptions[subscriptionId].marketId) == - Id.unwrap(marketParams.id()) - ); - require( - UtilsLib.exactlyOneZero(seizedAssets, repaidShares), - "Inconsistent input" - ); + require(Id.unwrap(subscriptions[subscriptionId].marketId) == Id.unwrap(marketParams.id())); + require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); require( - !_isHealthy( - marketParams.id(), - borrower, - collateralPrice, - subscriptions[subscriptionId].slltv - ), + !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].slltv), "Position is healthy" ); @@ -111,64 +84,37 @@ contract LiquidationProtection { Market memory marketState = morpho.market(marketParams.id()); { - uint256 liquidationIncentive = subscriptions[subscriptionId] - .liquidationIncentive; + uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( - collateralPrice, - ORACLE_PRICE_SCALE - ); + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted - .wDivUp(liquidationIncentive) - .toSharesUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ); + repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); } else { seizedAssets = repaidShares - .toAssetsDown( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ) + .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) .wMulDown(liquidationIncentive) .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } - uint256 repaidAssets = repaidShares.toAssetsUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ); + uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); // Check if liquidation is ok with close factor - Position memory borrowerPosition = morpho.position( - marketParams.id(), - borrower - ); + Position memory borrowerPosition = morpho.position(marketParams.id(), borrower); require( - borrowerPosition.collateral.wMulDown( - subscriptions[subscriptionId].closeFactor - ) > seizedAssets, + borrowerPosition.collateral.wMulDown(subscriptions[subscriptionId].closeFactor) > seizedAssets, "Cannot liquidate more than close factor" ); - bytes memory callbackData = abi.encode( - marketParams, - seizedAssets, - repaidAssets, - borrower, - msg.sender, - data - ); + bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); isValidSubscriptionId[subscriptionId] = false; } - function onMorphoRepay( - uint256 assets, - bytes calldata callbackData - ) external { + function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { ( MarketParams memory marketParams, uint256 seizedAssets, @@ -176,30 +122,14 @@ contract LiquidationProtection { address borrower, address liquidator, bytes memory data - ) = abi.decode( - callbackData, - (MarketParams, uint256, uint256, address, address, bytes) - ); + ) = abi.decode(callbackData, (MarketParams, uint256, uint256, address, address, bytes)); IMorpho morpho = IMorpho(MORPHO); - morpho.withdrawCollateral( - marketParams, - seizedAssets, - borrower, - liquidator - ); + morpho.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); - if (data.length > 0) - IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate( - assets, - data - ); + if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assets, data); - ERC20(marketParams.loanToken).safeTransferFrom( - liquidator, - address(this), - repaidAssets - ); + ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); ERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); } diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index cbfe96b..b476792 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -28,9 +28,7 @@ contract BaseTest is Test { address MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; morpho = IMorpho(MORPHO); - marketId = Id.wrap( - 0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e - ); // wstETH/WETH (96.5%) + marketId = Id.wrap(0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e); // wstETH/WETH (96.5%) market = morpho.idToMarketParams(marketId); loanToken = IERC20(market.loanToken); collateralToken = IERC20(market.collateralToken); @@ -52,10 +50,7 @@ contract BaseTest is Test { vm.startPrank(LIQUIDATOR); deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); loanToken.approve(address(liquidationProtection), type(uint256).max); - collateralToken.approve( - address(liquidationProtection), - type(uint256).max - ); + collateralToken.approve(address(liquidationProtection), type(uint256).max); } function test_set_subscription() public virtual { @@ -90,14 +85,7 @@ contract BaseTest is Test { vm.startPrank(LIQUIDATOR); vm.expectRevert(bytes("Non-valid subscription")); - liquidationProtection.liquidate( - subscriptionId, - market, - BORROWER, - 0, - 0, - hex"" - ); + liquidationProtection.liquidate(subscriptionId, market, BORROWER, 0, 0, hex""); } function test_soft_liquidation() public virtual { @@ -114,13 +102,6 @@ contract BaseTest is Test { vm.startPrank(LIQUIDATOR); Position memory position = morpho.position(marketId, BORROWER); - liquidationProtection.liquidate( - subscriptionId, - market, - BORROWER, - 0, - position.borrowShares, - hex"" - ); + liquidationProtection.liquidate(subscriptionId, market, BORROWER, 0, position.borrowShares, hex""); } } From e273bc66f0b16581cac3893be3cc3ef309a3a531 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 14:55:15 +0200 Subject: [PATCH 010/182] style: rename variable --- src/LiquidationProtection.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 7fa5f8b..1571667 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -32,7 +32,7 @@ contract LiquidationProtection { mapping(uint256 => SubscriptionParams) subscriptions; mapping(uint256 => bool) isValidSubscriptionId; - uint256 public nbSubscriptions; + uint256 public nbSubscription; // TODO EIP-712 signature // TODO authorize this contract on morpho @@ -46,12 +46,12 @@ contract LiquidationProtection { // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - isValidSubscriptionId[nbSubscriptions] = true; - subscriptions[nbSubscriptions] = subscriptionParams; + isValidSubscriptionId[nbSubscription] = true; + subscriptions[nbSubscription] = subscriptionParams; - nbSubscriptions++; + nbSubscription++; - return nbSubscriptions - 1; + return nbSubscription - 1; } function unsubscribe(uint256 subscriptionId) public { @@ -80,7 +80,6 @@ contract LiquidationProtection { ); // Compute seizedAssets or repaidShares and repaidAssets - Market memory marketState = morpho.market(marketParams.id()); { From 282763c2930076babf8ae53a963507017f775f0a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 15:03:53 +0200 Subject: [PATCH 011/182] doc: natspec --- src/LiquidationProtection.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 1571667..e914c0b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -20,6 +20,10 @@ struct SubscriptionParams { uint256 liquidationIncentive; } +/// @title Morpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice The Liquidation Protection Contract for Morpho contract LiquidationProtection { using MarketParamsLib for MarketParams; using UtilsLib for uint256; @@ -28,14 +32,17 @@ contract LiquidationProtection { using MathLib for uint128; using SafeTransferLib for ERC20; + /* IMMUTABLE */ address immutable MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + /* STORAGE */ mapping(uint256 => SubscriptionParams) subscriptions; mapping(uint256 => bool) isValidSubscriptionId; uint256 public nbSubscription; // TODO EIP-712 signature // TODO authorize this contract on morpho + // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) function subscribe(SubscriptionParams calldata subscriptionParams) public returns (uint256) { IMorpho morpho = IMorpho(MORPHO); @@ -68,7 +75,6 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - IMorpho morpho = IMorpho(MORPHO); require(isValidSubscriptionId[subscriptionId], "Non-valid subscription"); require(subscriptions[subscriptionId].borrower == borrower); require(Id.unwrap(subscriptions[subscriptionId].marketId) == Id.unwrap(marketParams.id())); @@ -79,6 +85,7 @@ contract LiquidationProtection { "Position is healthy" ); + IMorpho morpho = IMorpho(MORPHO); // Compute seizedAssets or repaidShares and repaidAssets Market memory marketState = morpho.market(marketParams.id()); From b0cc6cc907d89b0e73c99bc515f5fadbb3159f3d Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 15:09:04 +0200 Subject: [PATCH 012/182] feat: licence --- src/LiquidationProtection.sol | 2 +- test/LiquidationProtection.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e914c0b..6f2f45a 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.19; import {Id, MarketParams, IMorpho, Position, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index b476792..e9a5009 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../lib/forge-std/src/Test.sol"; From 738e3d394e0a567768101bbf16d25a7143345a2a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 15:12:24 +0200 Subject: [PATCH 013/182] style: linter --- src/interfaces/IERC20.sol | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 3489177..fab9250 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -19,11 +19,7 @@ interface IERC20 { * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); + event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the value of tokens in existence. @@ -51,10 +47,7 @@ interface IERC20 { * * This value changes when {approve} or {transferFrom} are called. */ - function allowance( - address owner, - address spender - ) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the @@ -82,9 +75,5 @@ interface IERC20 { * * Emits a {Transfer} event. */ - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); } From c942d2c4f4bbd791f507c8f5c6c7febe6d64d75d Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 30 Aug 2024 15:14:58 +0200 Subject: [PATCH 014/182] fix: variable name --- test/LiquidationProtection.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index e9a5009..77ab99c 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -65,7 +65,7 @@ contract BaseTest is Test { liquidationProtection.subscribe(params); - assertEq(liquidationProtection.nbSubscriptions(), 1); + assertEq(liquidationProtection.nbSubscription(), 1); } function test_remove_subscription() public virtual { From e36f99d5c7dcc9950c8ba8c732bef2b56c457395 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 2 Sep 2024 15:33:07 +0200 Subject: [PATCH 015/182] doc: natspec for accrueInterest --- src/LiquidationProtection.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 6f2f45a..986501b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -67,6 +67,7 @@ contract LiquidationProtection { isValidSubscriptionId[subscriptionId] = false; } + // @dev this function does not _accrueInterest() on Morpho function liquidate( uint256 subscriptionId, MarketParams calldata marketParams, From cbfa3845104d563ecc880e7dcd4f35bd5248211f Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 2 Sep 2024 15:50:26 +0200 Subject: [PATCH 016/182] feat: move isValid attribue to SubscriptionParams --- src/LiquidationProtection.sol | 11 +++++------ test/LiquidationProtection.t.sol | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 986501b..0f7d0cf 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -18,6 +18,7 @@ struct SubscriptionParams { uint256 slltv; uint256 closeFactor; uint256 liquidationIncentive; + bool isValid; } /// @title Morpho @@ -37,7 +38,6 @@ contract LiquidationProtection { /* STORAGE */ mapping(uint256 => SubscriptionParams) subscriptions; - mapping(uint256 => bool) isValidSubscriptionId; uint256 public nbSubscription; // TODO EIP-712 signature @@ -53,7 +53,6 @@ contract LiquidationProtection { // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - isValidSubscriptionId[nbSubscription] = true; subscriptions[nbSubscription] = subscriptionParams; nbSubscription++; @@ -64,10 +63,10 @@ contract LiquidationProtection { function unsubscribe(uint256 subscriptionId) public { require(msg.sender == subscriptions[subscriptionId].borrower, "Unauthorized account"); - isValidSubscriptionId[subscriptionId] = false; + subscriptions[subscriptionId].isValid = false; } - // @dev this function does not _accrueInterest() on Morpho + // @dev this function does not _accrueInterest() on Morpho when computing health function liquidate( uint256 subscriptionId, MarketParams calldata marketParams, @@ -76,7 +75,7 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - require(isValidSubscriptionId[subscriptionId], "Non-valid subscription"); + require(subscriptions[subscriptionId].isValid, "Non-valid subscription"); require(subscriptions[subscriptionId].borrower == borrower); require(Id.unwrap(subscriptions[subscriptionId].marketId) == Id.unwrap(marketParams.id())); require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); @@ -118,7 +117,7 @@ contract LiquidationProtection { bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); - isValidSubscriptionId[subscriptionId] = false; + subscriptions[subscriptionId].isValid = false; } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index 77ab99c..ead2c51 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -97,6 +97,7 @@ contract BaseTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% params.slltv = 10 * 10 ** 16; // 10% + params.isValid = true; uint256 subscriptionId = liquidationProtection.subscribe(params); From 8f06d8f618ed796d3dbb19b6ab6e7478103fb82f Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 09:56:05 +0200 Subject: [PATCH 017/182] chore: run forge fmt --- src/LiquidationProtection.sol | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 0f7d0cf..98bbf6f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -95,14 +95,11 @@ contract LiquidationProtection { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares + marketState.totalBorrowAssets, marketState.totalBorrowShares ); } else { - seizedAssets = repaidShares - .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) - .wMulDown(liquidationIncentive) - .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); @@ -140,23 +137,20 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); } - function _isHealthy( - Id id, - address borrower, - uint256 collateralPrice, - uint256 ltvThreshold - ) internal view returns (bool) { + function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) + internal + view + returns (bool) + { IMorpho morpho = IMorpho(MORPHO); Position memory borrowerPosition = morpho.position(id, borrower); Market memory marketState = morpho.market(id); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares + marketState.totalBorrowAssets, marketState.totalBorrowShares ); - uint256 maxBorrow = uint256(borrowerPosition.collateral) - .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) - .wMulDown(ltvThreshold); + uint256 maxBorrow = + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); return maxBorrow >= borrowed; } From a0a598c76db4f3b90cb66f552e88bf782178fe5e Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 09:56:12 +0200 Subject: [PATCH 018/182] refactor: simplify CI --- .github/workflows/test.yml | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 762a296..38d2529 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,19 +1,17 @@ -name: CI +name: Foundry on: push: + branches: + - main pull_request: workflow_dispatch: -env: - FOUNDRY_PROFILE: ci - jobs: - check: + test: strategy: fail-fast: true - name: Foundry project runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -22,24 +20,12 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Show Forge version - run: | - forge --version - - name: Run Forge fmt - run: | - forge fmt --check - id: fmt + - name: Run forge fmt + run: forge fmt --check - - name: Run Forge build - run: | - forge build --sizes - id: build + - name: Run forge build + run: forge build --sizes - - name: Run Forge tests - run: | - forge test -vvv - id: test + - name: Run forge tests + run: forge test -vvv From 26ff87090a256e36c87068fa4f775b4613d24adf Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 09:54:03 +0200 Subject: [PATCH 019/182] chore: use consistent naming for ci file --- .github/workflows/{test.yml => foundry.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{test.yml => foundry.yml} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/foundry.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/foundry.yml From e9af6722c74b7ddf03b82a10dbf2664461b02ba5 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 10:02:20 +0200 Subject: [PATCH 020/182] refactor: use camel case for test names --- test/LiquidationProtection.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtection.t.sol index ead2c51..b3d3a64 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtection.t.sol @@ -53,7 +53,7 @@ contract BaseTest is Test { collateralToken.approve(address(liquidationProtection), type(uint256).max); } - function test_set_subscription() public virtual { + function testSetSubscription() public virtual { vm.startPrank(BORROWER); SubscriptionParams memory params; @@ -68,7 +68,7 @@ contract BaseTest is Test { assertEq(liquidationProtection.nbSubscription(), 1); } - function test_remove_subscription() public virtual { + function testRemoveSubscription() public virtual { vm.startPrank(BORROWER); SubscriptionParams memory params; @@ -88,7 +88,7 @@ contract BaseTest is Test { liquidationProtection.liquidate(subscriptionId, market, BORROWER, 0, 0, hex""); } - function test_soft_liquidation() public virtual { + function testSoftLiquidation() public virtual { vm.startPrank(BORROWER); SubscriptionParams memory params; From 4cc9e14690d6bcb331eb731d01634d4fb1e722c7 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 11:02:59 +0200 Subject: [PATCH 021/182] refactor: test contract naming --- ...iquidationProtection.t.sol => LiquidationProtectionTest.sol} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{LiquidationProtection.t.sol => LiquidationProtectionTest.sol} (98%) diff --git a/test/LiquidationProtection.t.sol b/test/LiquidationProtectionTest.sol similarity index 98% rename from test/LiquidationProtection.t.sol rename to test/LiquidationProtectionTest.sol index b3d3a64..dcf1c89 100644 --- a/test/LiquidationProtection.t.sol +++ b/test/LiquidationProtectionTest.sol @@ -8,7 +8,7 @@ import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProte import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IERC20} from "../src/interfaces/IERC20.sol"; -contract BaseTest is Test { +contract LiquidationProtectionTest is Test { uint256 internal constant BLOCK_TIME = 12; address internal BORROWER; From bd87eac88bcc0cab2c4d5a44643f6e7644c5ac5e Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 11:04:55 +0200 Subject: [PATCH 022/182] refactor: store IMorpho directly --- src/LiquidationProtection.sol | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 98bbf6f..e794659 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -34,7 +34,7 @@ contract LiquidationProtection { using SafeTransferLib for ERC20; /* IMMUTABLE */ - address immutable MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + IMorpho immutable MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); /* STORAGE */ mapping(uint256 => SubscriptionParams) subscriptions; @@ -45,8 +45,7 @@ contract LiquidationProtection { // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) function subscribe(SubscriptionParams calldata subscriptionParams) public returns (uint256) { - IMorpho morpho = IMorpho(MORPHO); - MarketParams memory marketParams = morpho.idToMarketParams(subscriptionParams.marketId); + MarketParams memory marketParams = MORPHO.idToMarketParams(subscriptionParams.marketId); require(msg.sender == subscriptionParams.borrower, "Unauthorized account"); require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); @@ -127,14 +126,13 @@ contract LiquidationProtection { bytes memory data ) = abi.decode(callbackData, (MarketParams, uint256, uint256, address, address, bytes)); - IMorpho morpho = IMorpho(MORPHO); - morpho.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); + MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assets, data); ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); - ERC20(marketParams.loanToken).safeApprove(MORPHO, repaidAssets); + ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) @@ -142,9 +140,8 @@ contract LiquidationProtection { view returns (bool) { - IMorpho morpho = IMorpho(MORPHO); - Position memory borrowerPosition = morpho.position(id, borrower); - Market memory marketState = morpho.market(id); + Position memory borrowerPosition = MORPHO.position(id, borrower); + Market memory marketState = MORPHO.market(id); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( marketState.totalBorrowAssets, marketState.totalBorrowShares From 6982647df24a19ad4472dbe899aa9e30de03dc77 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 12:08:55 +0200 Subject: [PATCH 023/182] refactor: remove borrower and market id from params --- src/LiquidationProtection.sol | 38 +++++++++++++----------------- test/LiquidationProtectionTest.sol | 33 +++++++++++++------------- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e794659..36d03de 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -13,8 +13,6 @@ import {SafeTransferLib} from "../lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; struct SubscriptionParams { - Id marketId; - address borrower; uint256 slltv; uint256 closeFactor; uint256 liquidationIncentive; @@ -37,46 +35,41 @@ contract LiquidationProtection { IMorpho immutable MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); /* STORAGE */ - mapping(uint256 => SubscriptionParams) subscriptions; - uint256 public nbSubscription; + mapping(bytes32 => SubscriptionParams) public subscriptions; // TODO EIP-712 signature // TODO authorize this contract on morpho // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) - function subscribe(SubscriptionParams calldata subscriptionParams) public returns (uint256) { - MarketParams memory marketParams = MORPHO.idToMarketParams(subscriptionParams.marketId); + function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public { + MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); + + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); - require(msg.sender == subscriptionParams.borrower, "Unauthorized account"); require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - subscriptions[nbSubscription] = subscriptionParams; - - nbSubscription++; - - return nbSubscription - 1; + subscriptions[subscriptionId] = subscriptionParams; } - function unsubscribe(uint256 subscriptionId) public { - require(msg.sender == subscriptions[subscriptionId].borrower, "Unauthorized account"); + function unsubscribe(Id marketId) public { + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); subscriptions[subscriptionId].isValid = false; } // @dev this function does not _accrueInterest() on Morpho when computing health function liquidate( - uint256 subscriptionId, MarketParams calldata marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) public { + bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id()); + require(subscriptions[subscriptionId].isValid, "Non-valid subscription"); - require(subscriptions[subscriptionId].borrower == borrower); - require(Id.unwrap(subscriptions[subscriptionId].marketId) == Id.unwrap(marketParams.id())); require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); require( @@ -84,9 +77,8 @@ contract LiquidationProtection { "Position is healthy" ); - IMorpho morpho = IMorpho(MORPHO); // Compute seizedAssets or repaidShares and repaidAssets - Market memory marketState = morpho.market(marketParams.id()); + Market memory marketState = MORPHO.market(marketParams.id()); { uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; @@ -104,14 +96,14 @@ contract LiquidationProtection { uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); // Check if liquidation is ok with close factor - Position memory borrowerPosition = morpho.position(marketParams.id(), borrower); + Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); require( borrowerPosition.collateral.wMulDown(subscriptions[subscriptionId].closeFactor) > seizedAssets, "Cannot liquidate more than close factor" ); bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); - morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); + MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); subscriptions[subscriptionId].isValid = false; } @@ -135,6 +127,10 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } + function computeSubscriptionId(address borrower, Id marketId) public pure returns (bytes32) { + return keccak256(abi.encode(borrower, marketId)); + } + function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) internal view diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index dcf1c89..43b16f7 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -57,52 +57,53 @@ contract LiquidationProtectionTest is Test { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.borrower = BORROWER; - params.marketId = marketId; + params.slltv = 90 * 10 ** 16; // 90% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - params.slltv = 90 * 10 ** 16; // 90% + params.isValid = true; - liquidationProtection.subscribe(params); + liquidationProtection.subscribe(marketId, params); - assertEq(liquidationProtection.nbSubscription(), 1); + bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId); + (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive, bool isValid) = + liquidationProtection.subscriptions(subscriptionId); + assertEq(params.slltv, slltv); + assertEq(params.closeFactor, closeFactor); + assertEq(params.liquidationIncentive, liquidationIncentive); + assertEq(params.isValid, isValid); } function testRemoveSubscription() public virtual { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.borrower = BORROWER; - params.marketId = marketId; + params.slltv = 90 * 10 ** 16; // 90% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - params.slltv = 90 * 10 ** 16; // 90% - uint256 subscriptionId = liquidationProtection.subscribe(params); + liquidationProtection.subscribe(marketId, params); - liquidationProtection.unsubscribe(subscriptionId); + liquidationProtection.unsubscribe(marketId); vm.startPrank(LIQUIDATOR); vm.expectRevert(bytes("Non-valid subscription")); - liquidationProtection.liquidate(subscriptionId, market, BORROWER, 0, 0, hex""); + liquidationProtection.liquidate(market, BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.borrower = BORROWER; - params.marketId = marketId; + params.slltv = 10 * 10 ** 16; // 10% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - params.slltv = 10 * 10 ** 16; // 10% params.isValid = true; - uint256 subscriptionId = liquidationProtection.subscribe(params); + liquidationProtection.subscribe(marketId, params); vm.startPrank(LIQUIDATOR); Position memory position = morpho.position(marketId, BORROWER); - liquidationProtection.liquidate(subscriptionId, market, BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.liquidate(market, BORROWER, 0, position.borrowShares, hex""); } } From 968785a42e12f8aa4cf1cb835bb577d13c224373 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 2 Sep 2024 15:57:42 +0200 Subject: [PATCH 024/182] feat: remove auto-unsubscribe in liquidate --- src/LiquidationProtection.sol | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 36d03de..a62e69d 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -86,11 +86,14 @@ contract LiquidationProtection { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - marketState.totalBorrowAssets, marketState.totalBorrowShares + marketState.totalBorrowAssets, + marketState.totalBorrowShares ); } else { - seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) - .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares + .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .wMulDown(liquidationIncentive) + .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); @@ -103,9 +106,7 @@ contract LiquidationProtection { ); bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); - MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - - subscriptions[subscriptionId].isValid = false; + morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { @@ -131,19 +132,22 @@ contract LiquidationProtection { return keccak256(abi.encode(borrower, marketId)); } - function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) - internal - view - returns (bool) - { + function _isHealthy( + Id id, + address borrower, + uint256 collateralPrice, + uint256 ltvThreshold + ) internal view returns (bool) { Position memory borrowerPosition = MORPHO.position(id, borrower); Market memory marketState = MORPHO.market(id); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( - marketState.totalBorrowAssets, marketState.totalBorrowShares + marketState.totalBorrowAssets, + marketState.totalBorrowShares ); - uint256 maxBorrow = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); + uint256 maxBorrow = uint256(borrowerPosition.collateral) + .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) + .wMulDown(ltvThreshold); return maxBorrow >= borrowed; } From 2643e33de892fe6fd025403b75222997e656c0f3 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 2 Sep 2024 18:01:15 +0200 Subject: [PATCH 025/182] fix: callback calls liquidator instead of msg.sender --- src/LiquidationProtection.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index a62e69d..b4e43a2 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -121,7 +121,7 @@ contract LiquidationProtection { MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); - if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(assets, data); + if (data.length > 0) IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(assets, data); ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); From 55773563e633984ff3bddd45e70ed3b9dbbbd7f4 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 3 Sep 2024 11:51:40 +0200 Subject: [PATCH 026/182] feat: improve SubscriptionParams for gas opti --- src/LiquidationProtection.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b4e43a2..5021a9e 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -13,10 +13,12 @@ import {SafeTransferLib} from "../lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; struct SubscriptionParams { + Id marketId; + address borrower; + bool isValid; uint256 slltv; uint256 closeFactor; uint256 liquidationIncentive; - bool isValid; } /// @title Morpho From a40be1bfe66ee24dfee64329192efbfa1837c08a Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 3 Sep 2024 12:14:16 +0200 Subject: [PATCH 027/182] feat: implement events --- src/LiquidationProtection.sol | 44 +++++++++++++++++++++++++++++++---- src/libraries/EventsLib.sol | 32 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 src/libraries/EventsLib.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 5021a9e..704cea8 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -11,6 +11,7 @@ import {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"; struct SubscriptionParams { Id marketId; @@ -52,13 +53,28 @@ contract LiquidationProtection { // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - subscriptions[subscriptionId] = subscriptionParams; + subscriptions[nbSubscription] = subscriptionParams; + + emit EventsLib.Subscribe( + subscriptionParams.marketId, + subscriptionParams.borrower, + nbSubscription, + subscriptionParams.slltv, + subscriptionParams.closeFactor, + subscriptionParams.liquidationIncentive + ); + + nbSubscription++; + + return nbSubscription - 1; } function unsubscribe(Id marketId) public { bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); subscriptions[subscriptionId].isValid = false; + + emit EventsLib.Unsubscribe(subscriptionId); } // @dev this function does not _accrueInterest() on Morpho when computing health @@ -90,12 +106,18 @@ contract LiquidationProtection { repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( marketState.totalBorrowAssets, marketState.totalBorrowShares + marketState.totalBorrowAssets, + marketState.totalBorrowShares ); } else { seizedAssets = repaidShares .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) .wMulDown(liquidationIncentive) .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares + .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .wMulDown(liquidationIncentive) + .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); @@ -109,6 +131,17 @@ contract LiquidationProtection { bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); + + emit EventsLib.Liquidate( + marketParams.id(), + msg.sender, + borrower, + repaidAssets, + repaidShares, + seizedAssets, + 0, + 0 + ); } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { @@ -130,10 +163,6 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } - function computeSubscriptionId(address borrower, Id marketId) public pure returns (bytes32) { - return keccak256(abi.encode(borrower, marketId)); - } - function _isHealthy( Id id, address borrower, @@ -146,10 +175,15 @@ contract LiquidationProtection { uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( marketState.totalBorrowAssets, marketState.totalBorrowShares + marketState.totalBorrowAssets, + marketState.totalBorrowShares ); uint256 maxBorrow = uint256(borrowerPosition.collateral) .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) .wMulDown(ltvThreshold); + uint256 maxBorrow = uint256(borrowerPosition.collateral) + .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) + .wMulDown(ltvThreshold); return maxBorrow >= borrowed; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol new file mode 100644 index 0000000..44694e2 --- /dev/null +++ b/src/libraries/EventsLib.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + +/// @title EventsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library exposing events. +library EventsLib { + event Liquidate( + Id indexed id, + address indexed caller, + address indexed borrower, + uint256 repaidAssets, + uint256 repaidShares, + uint256 seizedAssets, + uint256 badDebtAssets, + uint256 badDebtShares + ); + + event Subscribe( + Id indexed id, + address indexed borrower, + uint256 indexed subscriptionId, + uint256 slltv, + uint256 closeFactor, + uint256 liquidationIncentive + ); + + event Unsubscribe(uint256 indexed subscriptionId); +} From 692849a71520b0fb68e5f77e257e5f77a5ffd781 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 13:47:08 +0200 Subject: [PATCH 028/182] fix: setup CI RPC --- .github/workflows/foundry.yml | 6 +++++- foundry.toml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 38d2529..68c5427 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -28,4 +28,8 @@ jobs: run: forge build --sizes - name: Run forge tests - run: forge test -vvv + run: | + echo "Alchemy key length" ${#ALCHEMY_KEY} + forge test -vvv + env: + ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} diff --git a/foundry.toml b/foundry.toml index 25b918f..88ddd09 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,6 @@ src = "src" out = "out" libs = ["lib"] +eth_rpc_url = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options From 730cb9d4d4a360c2cc1d519b5f930277cb580c58 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 14:45:04 +0200 Subject: [PATCH 029/182] chore: use createSelectFork --- .github/workflows/foundry.yml | 2 -- foundry.toml | 4 +++- test/LiquidationProtectionTest.sol | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 68c5427..0ad75db 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -31,5 +31,3 @@ jobs: run: | echo "Alchemy key length" ${#ALCHEMY_KEY} forge test -vvv - env: - ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} diff --git a/foundry.toml b/foundry.toml index 88ddd09..a20daa6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,8 @@ src = "src" out = "out" libs = ["lib"] -eth_rpc_url = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" + +[profile.default.rpc_endpoints] +mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 43b16f7..69555f8 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -22,6 +22,8 @@ contract LiquidationProtectionTest is Test { IERC20 collateralToken; function setUp() public virtual { + vm.createSelectFork(vm.rpcUrl("mainnet")); + BORROWER = makeAddr("Borrower"); LIQUIDATOR = makeAddr("Liquidator"); From 925b87300b12c01b580d2b2427b8503e7bf5a840 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 14:46:39 +0200 Subject: [PATCH 030/182] fix: import ALCHEMY_KEY secret --- .github/workflows/foundry.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 0ad75db..68c5427 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -31,3 +31,5 @@ jobs: run: | echo "Alchemy key length" ${#ALCHEMY_KEY} forge test -vvv + env: + ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} From 32c12d930ec24da8128139dc0a23f48ec44c9656 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 15:12:40 +0200 Subject: [PATCH 031/182] refactor: remove key length --- .github/workflows/foundry.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 68c5427..d40ea4c 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -28,8 +28,6 @@ jobs: run: forge build --sizes - name: Run forge tests - run: | - echo "Alchemy key length" ${#ALCHEMY_KEY} - forge test -vvv + run: forge test -vvv env: ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} From bdb7078929cad7420e2bb46721b5b76a3b2018fa Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 17:13:11 +0200 Subject: [PATCH 032/182] refactor: remove isValid flag --- src/LiquidationProtection.sol | 9 +++++++-- test/LiquidationProtectionTest.sol | 5 +---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 704cea8..be16690 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -72,7 +72,7 @@ contract LiquidationProtection { function unsubscribe(Id marketId) public { bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); - subscriptions[subscriptionId].isValid = false; + delete subscriptions[subscriptionId]; emit EventsLib.Unsubscribe(subscriptionId); } @@ -87,7 +87,12 @@ contract LiquidationProtection { ) public { bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id()); - require(subscriptions[subscriptionId].isValid, "Non-valid subscription"); + require( + subscriptions[subscriptionId].slltv != 0 && subscriptions[subscriptionId].closeFactor != 0 + && subscriptions[subscriptionId].liquidationIncentive != 0, + "Non-valid subscription" + ); + require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); require( diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 69555f8..7aa4bb6 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -62,17 +62,15 @@ contract LiquidationProtectionTest is Test { params.slltv = 90 * 10 ** 16; // 90% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - params.isValid = true; liquidationProtection.subscribe(marketId, params); bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId); - (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive, bool isValid) = + (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = liquidationProtection.subscriptions(subscriptionId); assertEq(params.slltv, slltv); assertEq(params.closeFactor, closeFactor); assertEq(params.liquidationIncentive, liquidationIncentive); - assertEq(params.isValid, isValid); } function testRemoveSubscription() public virtual { @@ -100,7 +98,6 @@ contract LiquidationProtectionTest is Test { params.slltv = 10 * 10 ** 16; // 10% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - params.isValid = true; liquidationProtection.subscribe(marketId, params); From 98acabd053006874a8efc104e8faa4e98503c7a9 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 3 Sep 2024 16:40:13 +0200 Subject: [PATCH 033/182] feat: accrue interest on morpho in liquidate --- src/LiquidationProtection.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index be16690..04de8ed 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -77,7 +77,6 @@ contract LiquidationProtection { emit EventsLib.Unsubscribe(subscriptionId); } - // @dev this function does not _accrueInterest() on Morpho when computing health function liquidate( MarketParams calldata marketParams, address borrower, @@ -95,6 +94,8 @@ contract LiquidationProtection { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); + + MORPHO.accrueInterest(marketParams); require( !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].slltv), "Position is healthy" From 0735a2d0f0716fcfbaca3d34c1106b4497cbf313 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 3 Sep 2024 17:03:32 +0200 Subject: [PATCH 034/182] feat: use public mapping --- src/LiquidationProtection.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 04de8ed..62e735a 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -38,7 +38,8 @@ contract LiquidationProtection { IMorpho immutable MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); /* STORAGE */ - mapping(bytes32 => SubscriptionParams) public subscriptions; + mapping(uint256 => SubscriptionParams) public subscriptions; + uint256 public nbSubscription; // TODO EIP-712 signature // TODO authorize this contract on morpho From 2a87359329cb9d6276239ff5b4cc020dabca1276 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 17:44:26 +0200 Subject: [PATCH 035/182] refactor: use nbSubscription again --- src/LiquidationProtection.sol | 51 +++++++++++++++--------------- src/libraries/EventsLib.sol | 6 ++-- test/LiquidationProtectionTest.sol | 14 ++++---- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 62e735a..3d33f4b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -38,54 +38,55 @@ contract LiquidationProtection { IMorpho immutable MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); /* STORAGE */ - mapping(uint256 => SubscriptionParams) public subscriptions; + mapping(bytes32 => SubscriptionParams) public subscriptions; uint256 public nbSubscription; // TODO EIP-712 signature // TODO authorize this contract on morpho // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) - function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public { + function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public returns (uint256) { MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); - require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - subscriptions[nbSubscription] = subscriptionParams; + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, nbSubscription); + + subscriptions[subscriptionId] = subscriptionParams; emit EventsLib.Subscribe( - subscriptionParams.marketId, - subscriptionParams.borrower, + msg.sender, + marketId, nbSubscription, subscriptionParams.slltv, subscriptionParams.closeFactor, subscriptionParams.liquidationIncentive ); - nbSubscription++; return nbSubscription - 1; } - function unsubscribe(Id marketId) public { - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId); + function unsubscribe(Id marketId, uint256 subscriptionNumber) public { + bytes32 subscriptionId = keccak256(abi.encode(msg.sender, marketId, subscriptionNumber)); delete subscriptions[subscriptionId]; emit EventsLib.Unsubscribe(subscriptionId); + emit EventsLib.Unsubscribe(subscriptionId); } function liquidate( + uint256 subscriptionNumber, MarketParams calldata marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) public { - bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id()); + bytes32 subscriptionId = keccak256(abi.encode(borrower, marketParams.id(), subscriptionNumber)); require( subscriptions[subscriptionId].slltv != 0 && subscriptions[subscriptionId].closeFactor != 0 @@ -113,8 +114,6 @@ contract LiquidationProtection { repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( marketState.totalBorrowAssets, marketState.totalBorrowShares - marketState.totalBorrowAssets, - marketState.totalBorrowShares ); } else { seizedAssets = repaidShares @@ -137,7 +136,7 @@ contract LiquidationProtection { ); bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); - morpho.repay(marketParams, 0, repaidShares, borrower, callbackData); + MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); emit EventsLib.Liquidate( marketParams.id(), @@ -170,27 +169,29 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } - function _isHealthy( - Id id, - address borrower, - uint256 collateralPrice, - uint256 ltvThreshold - ) internal view returns (bool) { + function computeSubscriptionId(address borrower, Id marketId, uint256 subscriptionNumber) + public + pure + returns (bytes32) + { + return keccak256(abi.encode(borrower, marketId, subscriptionNumber)); + } + + function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) + internal + view + returns (bool) + { Position memory borrowerPosition = MORPHO.position(id, borrower); Market memory marketState = MORPHO.market(id); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( marketState.totalBorrowAssets, marketState.totalBorrowShares - marketState.totalBorrowAssets, - marketState.totalBorrowShares ); uint256 maxBorrow = uint256(borrowerPosition.collateral) .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) .wMulDown(ltvThreshold); - uint256 maxBorrow = uint256(borrowerPosition.collateral) - .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) - .wMulDown(ltvThreshold); return maxBorrow >= borrowed; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 44694e2..a2553fc 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -20,13 +20,13 @@ library EventsLib { ); event Subscribe( - Id indexed id, address indexed borrower, - uint256 indexed subscriptionId, + Id indexed marketId, + uint256 indexed subscriptionNumber, uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive ); - event Unsubscribe(uint256 indexed subscriptionId); + event Unsubscribe(bytes32 indexed subscriptionId); } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 7aa4bb6..921edf5 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -63,9 +63,9 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); - bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId); + bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = liquidationProtection.subscriptions(subscriptionId); assertEq(params.slltv, slltv); @@ -81,14 +81,14 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); - liquidationProtection.unsubscribe(marketId); + liquidationProtection.unsubscribe(marketId, subscriptionNumber); vm.startPrank(LIQUIDATOR); vm.expectRevert(bytes("Non-valid subscription")); - liquidationProtection.liquidate(market, BORROWER, 0, 0, hex""); + liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { @@ -99,10 +99,10 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); vm.startPrank(LIQUIDATOR); Position memory position = morpho.position(marketId, BORROWER); - liquidationProtection.liquidate(market, BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, position.borrowShares, hex""); } } From 30db15dd7819c88d14f1e4151c190b2ccdd731d6 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 17:49:31 +0200 Subject: [PATCH 036/182] refactor: only check close factor for validity --- src/LiquidationProtection.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 3d33f4b..ddc27d2 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -88,11 +88,7 @@ contract LiquidationProtection { ) public { bytes32 subscriptionId = keccak256(abi.encode(borrower, marketParams.id(), subscriptionNumber)); - require( - subscriptions[subscriptionId].slltv != 0 && subscriptions[subscriptionId].closeFactor != 0 - && subscriptions[subscriptionId].liquidationIncentive != 0, - "Non-valid subscription" - ); + require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); From f1623953657cce1eb2d209d2ed89c0d9679f71a2 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 17:52:10 +0200 Subject: [PATCH 037/182] refactor: use computeSubscriptionId --- src/LiquidationProtection.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index ddc27d2..ed5f515 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -70,7 +70,7 @@ contract LiquidationProtection { } function unsubscribe(Id marketId, uint256 subscriptionNumber) public { - bytes32 subscriptionId = keccak256(abi.encode(msg.sender, marketId, subscriptionNumber)); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionNumber); delete subscriptions[subscriptionId]; @@ -86,7 +86,7 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - bytes32 subscriptionId = keccak256(abi.encode(borrower, marketParams.id(), subscriptionNumber)); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); From f9d6b452d5f4fe39550d3876bdb3d1344a3f0f47 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 3 Sep 2024 17:55:26 +0200 Subject: [PATCH 038/182] fix: complete Unsubscribe event --- src/LiquidationProtection.sol | 24 ++++++++++++------------ src/libraries/EventsLib.sol | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index ed5f515..ad95922 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -74,8 +74,7 @@ contract LiquidationProtection { delete subscriptions[subscriptionId]; - emit EventsLib.Unsubscribe(subscriptionId); - emit EventsLib.Unsubscribe(subscriptionId); + emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionNumber); } function liquidate( @@ -165,19 +164,20 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } - function computeSubscriptionId(address borrower, Id marketId, uint256 subscriptionNumber) - public - pure - returns (bytes32) - { + function computeSubscriptionId( + address borrower, + Id marketId, + uint256 subscriptionNumber + ) public pure returns (bytes32) { return keccak256(abi.encode(borrower, marketId, subscriptionNumber)); } - function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) - internal - view - returns (bool) - { + function _isHealthy( + Id id, + address borrower, + uint256 collateralPrice, + uint256 ltvThreshold + ) internal view returns (bool) { Position memory borrowerPosition = MORPHO.position(id, borrower); Market memory marketState = MORPHO.market(id); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index a2553fc..9b85d49 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -28,5 +28,5 @@ library EventsLib { uint256 liquidationIncentive ); - event Unsubscribe(bytes32 indexed subscriptionId); + event Unsubscribe(address indexed borrower, Id indexed marketId, uint256 indexed subscriptionNumber); } From 171ed69980eea930ab959cea57d2096324d7e5b5 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 11:53:37 +0200 Subject: [PATCH 039/182] feat: require morpho as sender in repay callback --- .gitignore | 2 ++ .prettierrc | 15 --------------- src/LiquidationProtection.sol | 1 + 3 files changed, 3 insertions(+), 15 deletions(-) delete mode 100644 .prettierrc diff --git a/.gitignore b/.gitignore index 85198aa..64f151d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ docs/ # Dotenv file .env + +.prettierignore \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 11a0f59..0000000 --- a/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tabWidth": 2, - "printWidth": 100, - - "overrides": [ - { - "files": "*.sol", - "options": { - "tabWidth": 4, - "printWidth": 120 - } - } - ] - } - \ No newline at end of file diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index ad95922..e23327f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -146,6 +146,7 @@ contract LiquidationProtection { } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { + require(msg.sender == address(MORPHO), "Not Morpho"); ( MarketParams memory marketParams, uint256 seizedAssets, From 16655cf168d1044d042ff146d524425067273dab Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 12:04:55 +0200 Subject: [PATCH 040/182] feat: use subscriptionId in Liquidate event --- src/LiquidationProtection.sol | 137 ++++++++++++++++++++++++++-------- src/libraries/EventsLib.sol | 4 +- 2 files changed, 108 insertions(+), 33 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e23327f..8bcab5b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -35,7 +35,8 @@ contract LiquidationProtection { using SafeTransferLib for ERC20; /* IMMUTABLE */ - IMorpho immutable MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); + IMorpho immutable MORPHO = + IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); /* STORAGE */ mapping(bytes32 => SubscriptionParams) public subscriptions; @@ -45,14 +46,24 @@ contract LiquidationProtection { // TODO authorize this contract on morpho // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) - function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public returns (uint256) { + function subscribe( + Id marketId, + SubscriptionParams calldata subscriptionParams + ) public returns (uint256) { MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); - require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); + require( + subscriptionParams.slltv < marketParams.lltv, + "Liquidation threshold higher than market LLTV" + ); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, nbSubscription); + bytes32 subscriptionId = computeSubscriptionId( + msg.sender, + marketId, + nbSubscription + ); subscriptions[subscriptionId] = subscriptionParams; @@ -70,7 +81,11 @@ contract LiquidationProtection { } function unsubscribe(Id marketId, uint256 subscriptionNumber) public { - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionNumber); + bytes32 subscriptionId = computeSubscriptionId( + msg.sender, + marketId, + subscriptionNumber + ); delete subscriptions[subscriptionId]; @@ -85,16 +100,31 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); + bytes32 subscriptionId = computeSubscriptionId( + borrower, + marketParams.id(), + subscriptionNumber + ); - require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); + require( + subscriptions[subscriptionId].closeFactor != 0, + "Non-valid subscription" + ); - require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); + require( + UtilsLib.exactlyOneZero(seizedAssets, repaidShares), + "Inconsistent input" + ); uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); require( - !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].slltv), + !_isHealthy( + marketParams.id(), + borrower, + collateralPrice, + subscriptions[subscriptionId].slltv + ), "Position is healthy" ); @@ -102,41 +132,66 @@ contract LiquidationProtection { Market memory marketState = MORPHO.market(marketParams.id()); { - uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; + uint256 liquidationIncentive = subscriptions[subscriptionId] + .liquidationIncentive; if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - - repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( + collateralPrice, + ORACLE_PRICE_SCALE ); + + repaidShares = seizedAssetsQuoted + .wDivUp(liquidationIncentive) + .toSharesUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); } else { seizedAssets = repaidShares - .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .toAssetsDown( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ) .wMulDown(liquidationIncentive) .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); seizedAssets = repaidShares - .toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .toAssetsDown( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ) .wMulDown(liquidationIncentive) .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } - uint256 repaidAssets = repaidShares.toAssetsUp(marketState.totalBorrowAssets, marketState.totalBorrowShares); + uint256 repaidAssets = repaidShares.toAssetsUp( + marketState.totalBorrowAssets, + marketState.totalBorrowShares + ); // Check if liquidation is ok with close factor - Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); + Position memory borrowerPosition = MORPHO.position( + marketParams.id(), + borrower + ); require( - borrowerPosition.collateral.wMulDown(subscriptions[subscriptionId].closeFactor) > seizedAssets, + borrowerPosition.collateral.wMulDown( + subscriptions[subscriptionId].closeFactor + ) > seizedAssets, "Cannot liquidate more than close factor" ); - bytes memory callbackData = abi.encode(marketParams, seizedAssets, repaidAssets, borrower, msg.sender, data); + bytes memory callbackData = abi.encode( + marketParams, + seizedAssets, + repaidAssets, + borrower, + msg.sender, + data + ); MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); emit EventsLib.Liquidate( - marketParams.id(), - msg.sender, - borrower, + subscriptionNumber, repaidAssets, repaidShares, seizedAssets, @@ -145,7 +200,10 @@ contract LiquidationProtection { ); } - function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { + function onMorphoRepay( + uint256 assets, + bytes calldata callbackData + ) external { require(msg.sender == address(MORPHO), "Not Morpho"); ( MarketParams memory marketParams, @@ -154,15 +212,34 @@ contract LiquidationProtection { address borrower, address liquidator, bytes memory data - ) = abi.decode(callbackData, (MarketParams, uint256, uint256, address, address, bytes)); + ) = abi.decode( + callbackData, + (MarketParams, uint256, uint256, address, address, bytes) + ); - MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); + MORPHO.withdrawCollateral( + marketParams, + seizedAssets, + borrower, + liquidator + ); - if (data.length > 0) IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(assets, data); + if (data.length > 0) + IMorphoLiquidateCallback(liquidator).onMorphoLiquidate( + assets, + data + ); - ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); + ERC20(marketParams.loanToken).safeTransferFrom( + liquidator, + address(this), + repaidAssets + ); - ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); + ERC20(marketParams.loanToken).safeApprove( + address(MORPHO), + repaidAssets + ); } function computeSubscriptionId( diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 9b85d49..744a78a 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -9,9 +9,7 @@ import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol /// @notice Library exposing events. library EventsLib { event Liquidate( - Id indexed id, - address indexed caller, - address indexed borrower, + uint256 indexed subscriptionNumber, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets, From 27e6d3ca23b97055c396a9cdae1649437be03ad9 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 12:07:41 +0200 Subject: [PATCH 041/182] feat: remove bad debt parameter from Liquidate --- src/LiquidationProtection.sol | 4 +--- src/libraries/EventsLib.sol | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 8bcab5b..a21988f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -194,9 +194,7 @@ contract LiquidationProtection { subscriptionNumber, repaidAssets, repaidShares, - seizedAssets, - 0, - 0 + seizedAssets ); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 744a78a..f157047 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -12,9 +12,7 @@ library EventsLib { uint256 indexed subscriptionNumber, uint256 repaidAssets, uint256 repaidShares, - uint256 seizedAssets, - uint256 badDebtAssets, - uint256 badDebtShares + uint256 seizedAssets ); event Subscribe( From 3ef9ca5cf204d4c05633e6b1b39afd6adab8e72a Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 12:10:57 +0200 Subject: [PATCH 042/182] feat: implement constructor to pass morpho address --- src/LiquidationProtection.sol | 6 ++++-- test/LiquidationProtectionTest.sol | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index a21988f..dc69763 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -35,8 +35,7 @@ contract LiquidationProtection { using SafeTransferLib for ERC20; /* IMMUTABLE */ - IMorpho immutable MORPHO = - IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb); + IMorpho immutable MORPHO; /* STORAGE */ mapping(bytes32 => SubscriptionParams) public subscriptions; @@ -46,6 +45,9 @@ contract LiquidationProtection { // TODO authorize this contract on morpho // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) + constructor(address morpho) { + MORPHO = IMorpho(morpho); + } function subscribe( Id marketId, SubscriptionParams calldata subscriptionParams diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 921edf5..31be5cb 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -34,8 +34,8 @@ contract LiquidationProtectionTest is Test { market = morpho.idToMarketParams(marketId); loanToken = IERC20(market.loanToken); collateralToken = IERC20(market.collateralToken); - - liquidationProtection = new LiquidationProtection(); + + liquidationProtection = new LiquidationProtection(MORPHO); vm.startPrank(BORROWER); loanToken.approve(address(morpho), type(uint256).max); From aadb4644da9c36bd596ce83748027e96583a2c49 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 14:52:21 +0200 Subject: [PATCH 043/182] feat: remove repaidAssets from liquidate --- src/LiquidationProtection.sol | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index dc69763..b6fcc1f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -165,10 +165,6 @@ contract LiquidationProtection { .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } - uint256 repaidAssets = repaidShares.toAssetsUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ); // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position( @@ -185,16 +181,21 @@ contract LiquidationProtection { bytes memory callbackData = abi.encode( marketParams, seizedAssets, - repaidAssets, borrower, msg.sender, data ); - MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + (uint256 assets, ) = MORPHO.repay( + marketParams, + 0, + repaidShares, + borrower, + callbackData + ); emit EventsLib.Liquidate( subscriptionNumber, - repaidAssets, + assets, repaidShares, seizedAssets ); @@ -208,13 +209,12 @@ contract LiquidationProtection { ( MarketParams memory marketParams, uint256 seizedAssets, - uint256 repaidAssets, address borrower, address liquidator, bytes memory data ) = abi.decode( callbackData, - (MarketParams, uint256, uint256, address, address, bytes) + (MarketParams, uint256, address, address, bytes) ); MORPHO.withdrawCollateral( @@ -233,13 +233,10 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeTransferFrom( liquidator, address(this), - repaidAssets + assets ); - ERC20(marketParams.loanToken).safeApprove( - address(MORPHO), - repaidAssets - ); + ERC20(marketParams.loanToken).safeApprove(address(MORPHO), assets); } function computeSubscriptionId( From 8e8b5ba7884bc7ec3a98aeb469dba464daaff26e Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 4 Sep 2024 17:09:52 +0200 Subject: [PATCH 044/182] style: forge fmt --- src/LiquidationProtection.sol | 175 ++++++++--------------------- src/libraries/EventsLib.sol | 5 +- test/LiquidationProtectionTest.sol | 4 +- 3 files changed, 47 insertions(+), 137 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b6fcc1f..14ca176 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -48,24 +48,15 @@ contract LiquidationProtection { constructor(address morpho) { MORPHO = IMorpho(morpho); } - function subscribe( - Id marketId, - SubscriptionParams calldata subscriptionParams - ) public returns (uint256) { + + function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public returns (uint256) { MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); - require( - subscriptionParams.slltv < marketParams.lltv, - "Liquidation threshold higher than market LLTV" - ); + require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - bytes32 subscriptionId = computeSubscriptionId( - msg.sender, - marketId, - nbSubscription - ); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, nbSubscription); subscriptions[subscriptionId] = subscriptionParams; @@ -83,11 +74,7 @@ contract LiquidationProtection { } function unsubscribe(Id marketId, uint256 subscriptionNumber) public { - bytes32 subscriptionId = computeSubscriptionId( - msg.sender, - marketId, - subscriptionNumber - ); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionNumber); delete subscriptions[subscriptionId]; @@ -102,31 +89,16 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - bytes32 subscriptionId = computeSubscriptionId( - borrower, - marketParams.id(), - subscriptionNumber - ); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); - require( - subscriptions[subscriptionId].closeFactor != 0, - "Non-valid subscription" - ); + require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); - require( - UtilsLib.exactlyOneZero(seizedAssets, repaidShares), - "Inconsistent input" - ); + require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); require( - !_isHealthy( - marketParams.id(), - borrower, - collateralPrice, - subscriptions[subscriptionId].slltv - ), + !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].slltv), "Position is healthy" ); @@ -134,77 +106,35 @@ contract LiquidationProtection { Market memory marketState = MORPHO.market(marketParams.id()); { - uint256 liquidationIncentive = subscriptions[subscriptionId] - .liquidationIncentive; + uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp( - collateralPrice, - ORACLE_PRICE_SCALE - ); + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted - .wDivUp(liquidationIncentive) - .toSharesUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ); + repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + marketState.totalBorrowAssets, marketState.totalBorrowShares + ); } else { - seizedAssets = repaidShares - .toAssetsDown( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ) - .wMulDown(liquidationIncentive) - .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - seizedAssets = repaidShares - .toAssetsDown( - marketState.totalBorrowAssets, - marketState.totalBorrowShares - ) - .wMulDown(liquidationIncentive) - .mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) + .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } } // Check if liquidation is ok with close factor - Position memory borrowerPosition = MORPHO.position( - marketParams.id(), - borrower - ); + Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); require( - borrowerPosition.collateral.wMulDown( - subscriptions[subscriptionId].closeFactor - ) > seizedAssets, + borrowerPosition.collateral.wMulDown(subscriptions[subscriptionId].closeFactor) > seizedAssets, "Cannot liquidate more than close factor" ); - bytes memory callbackData = abi.encode( - marketParams, - seizedAssets, - borrower, - msg.sender, - data - ); - (uint256 assets, ) = MORPHO.repay( - marketParams, - 0, - repaidShares, - borrower, - callbackData - ); + bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); + (uint256 assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate( - subscriptionNumber, - assets, - repaidShares, - seizedAssets - ); + emit EventsLib.Liquidate(subscriptionNumber, assets, repaidShares, seizedAssets); } - function onMorphoRepay( - uint256 assets, - bytes calldata callbackData - ) external { + function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { require(msg.sender == address(MORPHO), "Not Morpho"); ( MarketParams memory marketParams, @@ -212,57 +142,40 @@ contract LiquidationProtection { address borrower, address liquidator, bytes memory data - ) = abi.decode( - callbackData, - (MarketParams, uint256, address, address, bytes) - ); - - MORPHO.withdrawCollateral( - marketParams, - seizedAssets, - borrower, - liquidator - ); + ) = abi.decode(callbackData, (MarketParams, uint256, address, address, bytes)); - if (data.length > 0) - IMorphoLiquidateCallback(liquidator).onMorphoLiquidate( - assets, - data - ); + MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); - ERC20(marketParams.loanToken).safeTransferFrom( - liquidator, - address(this), - assets - ); + if (data.length > 0) { + IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(assets, data); + } + + ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), assets); ERC20(marketParams.loanToken).safeApprove(address(MORPHO), assets); } - function computeSubscriptionId( - address borrower, - Id marketId, - uint256 subscriptionNumber - ) public pure returns (bytes32) { + function computeSubscriptionId(address borrower, Id marketId, uint256 subscriptionNumber) + public + pure + returns (bytes32) + { return keccak256(abi.encode(borrower, marketId, subscriptionNumber)); } - function _isHealthy( - Id id, - address borrower, - uint256 collateralPrice, - uint256 ltvThreshold - ) internal view returns (bool) { + function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) + internal + view + returns (bool) + { Position memory borrowerPosition = MORPHO.position(id, borrower); Market memory marketState = MORPHO.market(id); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( - marketState.totalBorrowAssets, - marketState.totalBorrowShares + marketState.totalBorrowAssets, marketState.totalBorrowShares ); - uint256 maxBorrow = uint256(borrowerPosition.collateral) - .mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) - .wMulDown(ltvThreshold); + uint256 maxBorrow = + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); return maxBorrow >= borrowed; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index f157047..5e402a9 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -9,10 +9,7 @@ import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol /// @notice Library exposing events. library EventsLib { event Liquidate( - uint256 indexed subscriptionNumber, - uint256 repaidAssets, - uint256 repaidShares, - uint256 seizedAssets + uint256 indexed subscriptionNumber, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets ); event Subscribe( diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 31be5cb..94c94bc 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -34,7 +34,7 @@ contract LiquidationProtectionTest is Test { market = morpho.idToMarketParams(marketId); loanToken = IERC20(market.loanToken); collateralToken = IERC20(market.collateralToken); - + liquidationProtection = new LiquidationProtection(MORPHO); vm.startPrank(BORROWER); @@ -66,7 +66,7 @@ contract LiquidationProtectionTest is Test { uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); - (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = + (,,, uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = liquidationProtection.subscriptions(subscriptionId); assertEq(params.slltv, slltv); assertEq(params.closeFactor, closeFactor); From f41c875bff90670d4fd5abcaf5ded7a7dca8e226 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Thu, 5 Sep 2024 11:08:38 +0200 Subject: [PATCH 045/182] fix: trim subscription params --- src/LiquidationProtection.sol | 3 --- test/LiquidationProtectionTest.sol | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 14ca176..89f4c4e 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -14,9 +14,6 @@ import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; struct SubscriptionParams { - Id marketId; - address borrower; - bool isValid; uint256 slltv; uint256 closeFactor; uint256 liquidationIncentive; diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 94c94bc..8165f97 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -66,7 +66,7 @@ contract LiquidationProtectionTest is Test { uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); - (,,, uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = + (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = liquidationProtection.subscriptions(subscriptionId); assertEq(params.slltv, slltv); assertEq(params.closeFactor, closeFactor); From 5ce2b2e5d961eb6eba8b8fe39a37c40aa6a34543 Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 5 Sep 2024 15:09:33 +0200 Subject: [PATCH 046/182] feat: require close factor on borrow shares instead of collateral --- src/LiquidationProtection.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 89f4c4e..71374a5 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -121,7 +121,7 @@ contract LiquidationProtection { // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); require( - borrowerPosition.collateral.wMulDown(subscriptions[subscriptionId].closeFactor) > seizedAssets, + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) > repaidShares, "Cannot liquidate more than close factor" ); From c234e35844aa7ba4e44ce00d4ffcd8ab64de7a14 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 10:28:47 +0200 Subject: [PATCH 047/182] refactor: rename slltv into prelltv --- src/LiquidationProtection.sol | 8 ++++---- test/LiquidationProtectionTest.sol | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 71374a5..43cfdf8 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -14,7 +14,7 @@ import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; struct SubscriptionParams { - uint256 slltv; + uint256 prelltv; uint256 closeFactor; uint256 liquidationIncentive; } @@ -49,7 +49,7 @@ contract LiquidationProtection { function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public returns (uint256) { MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); - require(subscriptionParams.slltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); + require(subscriptionParams.prelltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? @@ -61,7 +61,7 @@ contract LiquidationProtection { msg.sender, marketId, nbSubscription, - subscriptionParams.slltv, + subscriptionParams.prelltv, subscriptionParams.closeFactor, subscriptionParams.liquidationIncentive ); @@ -95,7 +95,7 @@ contract LiquidationProtection { MORPHO.accrueInterest(marketParams); require( - !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].slltv), + !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].prelltv), "Position is healthy" ); diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 8165f97..84aca93 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -59,16 +59,16 @@ contract LiquidationProtectionTest is Test { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.slltv = 90 * 10 ** 16; // 90% + params.prelltv = 90 * 10 ** 16; // 90% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); - (uint256 slltv, uint256 closeFactor, uint256 liquidationIncentive) = + (uint256 prelltv, uint256 closeFactor, uint256 liquidationIncentive) = liquidationProtection.subscriptions(subscriptionId); - assertEq(params.slltv, slltv); + assertEq(params.prelltv, prelltv); assertEq(params.closeFactor, closeFactor); assertEq(params.liquidationIncentive, liquidationIncentive); } @@ -77,7 +77,7 @@ contract LiquidationProtectionTest is Test { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.slltv = 90 * 10 ** 16; // 90% + params.prelltv = 90 * 10 ** 16; // 90% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% @@ -95,7 +95,7 @@ contract LiquidationProtectionTest is Test { vm.startPrank(BORROWER); SubscriptionParams memory params; - params.slltv = 10 * 10 ** 16; // 10% + params.prelltv = 10 * 10 ** 16; // 10% params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% From 41e3906b8b2b9d47382d61e690901ad754924d58 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 10:43:55 +0200 Subject: [PATCH 048/182] fix: close factor require condition --- src/LiquidationProtection.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 43cfdf8..017e881 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -121,7 +121,7 @@ contract LiquidationProtection { // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); require( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) > repaidShares, + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, "Cannot liquidate more than close factor" ); From 9f5e68e7a685b05c39512615f990495001e5a462 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 12:44:26 +0200 Subject: [PATCH 049/182] feat: change indexed event for Liquidate --- src/LiquidationProtection.sol | 4 +++- src/libraries/EventsLib.sol | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 017e881..8f48d6f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -128,7 +128,9 @@ contract LiquidationProtection { bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); (uint256 assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate(subscriptionNumber, assets, repaidShares, seizedAssets); + emit EventsLib.Liquidate( + borrower, msg.sender, marketParams.id(), subscriptionNumber, repaidAssets, repaidShares, seizedAssets + ); } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 5e402a9..cd9f1b2 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -9,7 +9,13 @@ import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol /// @notice Library exposing events. library EventsLib { event Liquidate( - uint256 indexed subscriptionNumber, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets + address indexed borrower, + address indexed liquidator, + Id indexed marketId, + uint256 subscriptionNumber, + uint256 repaidAssets, + uint256 repaidShares, + uint256 seizedAssets ); event Subscribe( From 1a03539f19992f300389cf9d154aabdbeeb0d762 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 13:57:05 +0200 Subject: [PATCH 050/182] refactor: newline --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 64f151d..021292d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ docs/ # Dotenv file .env -.prettierignore \ No newline at end of file +.prettierignore From a3827aff0e10b71ceec843b345bd0e7de00d90a7 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 14:02:59 +0200 Subject: [PATCH 051/182] fix: stack too deep --- src/LiquidationProtection.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 8f48d6f..9e5e138 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -32,7 +32,7 @@ contract LiquidationProtection { using SafeTransferLib for ERC20; /* IMMUTABLE */ - IMorpho immutable MORPHO; + IMorpho public immutable MORPHO; /* STORAGE */ mapping(bytes32 => SubscriptionParams) public subscriptions; @@ -124,12 +124,14 @@ contract LiquidationProtection { borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, "Cannot liquidate more than close factor" ); - - bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); - (uint256 assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + uint256 assets; + { + bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); + (assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + } emit EventsLib.Liquidate( - borrower, msg.sender, marketParams.id(), subscriptionNumber, repaidAssets, repaidShares, seizedAssets + borrower, msg.sender, marketParams.id(), subscriptionNumber, assets, repaidShares, seizedAssets ); } From 946f2cc5b20e0d2b1e7b7c86ec40b38356316230 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 14:41:21 +0200 Subject: [PATCH 052/182] refactor: increment trick --- src/LiquidationProtection.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 9e5e138..274bd13 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -65,9 +65,8 @@ contract LiquidationProtection { subscriptionParams.closeFactor, subscriptionParams.liquidationIncentive ); - nbSubscription++; - return nbSubscription - 1; + return nbSubscription++; } function unsubscribe(Id marketId, uint256 subscriptionNumber) public { From 76f228e27cf4d998285e8ff8dee49d0c25535558 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 15:00:56 +0200 Subject: [PATCH 053/182] feat: use marketParams in subscribe for gas opti --- src/LiquidationProtection.sol | 8 +++++--- test/LiquidationProtectionTest.sol | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 274bd13..b8a7a9e 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -46,9 +46,11 @@ contract LiquidationProtection { MORPHO = IMorpho(morpho); } - function subscribe(Id marketId, SubscriptionParams calldata subscriptionParams) public returns (uint256) { - MarketParams memory marketParams = MORPHO.idToMarketParams(marketId); - + function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + public + returns (uint256) + { + Id marketId = marketParams.id(); require(subscriptionParams.prelltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 84aca93..1b17954 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -63,7 +63,7 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); (uint256 prelltv, uint256 closeFactor, uint256 liquidationIncentive) = @@ -81,7 +81,7 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); liquidationProtection.unsubscribe(marketId, subscriptionNumber); @@ -99,7 +99,7 @@ contract LiquidationProtectionTest is Test { params.closeFactor = 10 ** 18; // 100% params.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(marketId, params); + uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); vm.startPrank(LIQUIDATOR); Position memory position = morpho.position(marketId, BORROWER); From e80f558d6536501f2dfda63c2ca0028b305dc55a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 15:17:15 +0200 Subject: [PATCH 054/182] feat: factorize market id --- src/LiquidationProtection.sol | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b8a7a9e..c526e18 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -40,7 +40,6 @@ contract LiquidationProtection { // TODO EIP-712 signature // TODO authorize this contract on morpho - // TODO potential gas opti (keeping marketparams in SubscriptionParams instead of Id?) constructor(address morpho) { MORPHO = IMorpho(morpho); @@ -88,7 +87,7 @@ contract LiquidationProtection { bytes calldata data ) public { bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); - + Id marketId = marketParams.id(); require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); @@ -96,14 +95,13 @@ contract LiquidationProtection { MORPHO.accrueInterest(marketParams); require( - !_isHealthy(marketParams.id(), borrower, collateralPrice, subscriptions[subscriptionId].prelltv), + !_isHealthy(marketId, borrower, collateralPrice, subscriptions[subscriptionId].prelltv), "Position is healthy" ); // Compute seizedAssets or repaidShares and repaidAssets - Market memory marketState = MORPHO.market(marketParams.id()); - { + Market memory marketState = MORPHO.market(marketId); uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); @@ -120,20 +118,17 @@ contract LiquidationProtection { } // Check if liquidation is ok with close factor - Position memory borrowerPosition = MORPHO.position(marketParams.id(), borrower); - require( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, - "Cannot liquidate more than close factor" - ); - uint256 assets; { - bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); - (assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + Position memory borrowerPosition = MORPHO.position(marketId, borrower); + require( + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, + "Cannot liquidate more than close factor" + ); } + bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); + (uint256 assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate( - borrower, msg.sender, marketParams.id(), subscriptionNumber, assets, repaidShares, seizedAssets - ); + emit EventsLib.Liquidate(borrower, msg.sender, marketId, subscriptionNumber, assets, repaidShares, seizedAssets); } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { From 59b1210b5de8affa4f3ea2c6229542f12516d7df Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 15:21:59 +0200 Subject: [PATCH 055/182] feat: replace OZ IERC20 by Solmate ERC20 --- src/interfaces/IERC20.sol | 79 ------------------------------ test/LiquidationProtectionTest.sol | 10 ++-- 2 files changed, 5 insertions(+), 84 deletions(-) delete mode 100644 src/interfaces/IERC20.sol diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol deleted file mode 100644 index fab9250..0000000 --- a/src/interfaces/IERC20.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) - -pragma solidity ^0.8.19; - -/** - * @dev Interface of the ERC-20 standard as defined in the ERC. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the value of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the value of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves a `value` amount of tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 value) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets a `value` amount of tokens as the allowance of `spender` over the - * caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 value) external returns (bool); - - /** - * @dev Moves a `value` amount of tokens from `from` to `to` using the - * allowance mechanism. `value` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 value) external returns (bool); -} diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 1b17954..fa68f25 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -6,7 +6,7 @@ import "../lib/forge-std/src/console.sol"; import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProtection.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {IERC20} from "../src/interfaces/IERC20.sol"; +import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; contract LiquidationProtectionTest is Test { uint256 internal constant BLOCK_TIME = 12; @@ -18,8 +18,8 @@ contract LiquidationProtectionTest is Test { Id internal marketId; MarketParams internal market; IMorpho morpho; - IERC20 loanToken; - IERC20 collateralToken; + ERC20 loanToken; + ERC20 collateralToken; function setUp() public virtual { vm.createSelectFork(vm.rpcUrl("mainnet")); @@ -32,8 +32,8 @@ contract LiquidationProtectionTest is Test { marketId = Id.wrap(0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e); // wstETH/WETH (96.5%) market = morpho.idToMarketParams(marketId); - loanToken = IERC20(market.loanToken); - collateralToken = IERC20(market.collateralToken); + loanToken = ERC20(market.loanToken); + collateralToken = ERC20(market.collateralToken); liquidationProtection = new LiquidationProtection(MORPHO); From 3a42f44b73d8d9e26ec71b3f0fe0c521e3bf4644 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 16:56:10 +0200 Subject: [PATCH 056/182] feat: use custom errors with solidity 0.8.27 --- lib/forge-std | 2 +- lib/morpho-blue | 2 +- src/LiquidationProtection.sol | 22 +++++++++++++++------- src/libraries/ErrorsLib.sol | 20 ++++++++++++++++++++ src/libraries/EventsLib.sol | 2 +- test/LiquidationProtectionTest.sol | 5 +++-- 6 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 src/libraries/ErrorsLib.sol diff --git a/lib/forge-std b/lib/forge-std index 1714bee..1ce7535 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d +Subproject commit 1ce7535a517406b9aec7ea1ea27c1b41376f712c diff --git a/lib/morpho-blue b/lib/morpho-blue index 8e35224..0448402 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 8e35224dfc69eebf68ad67408bb1154173aeed16 +Subproject commit 0448402af51b8293ed36653de43cbee8d4d2bfda diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index c526e18..2377837 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.19; +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"; @@ -12,6 +12,7 @@ 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"; struct SubscriptionParams { uint256 prelltv; @@ -50,7 +51,10 @@ contract LiquidationProtection { returns (uint256) { Id marketId = marketParams.id(); - require(subscriptionParams.prelltv < marketParams.lltv, "Liquidation threshold higher than market LLTV"); + require( + subscriptionParams.prelltv < marketParams.lltv, + ErrorsLib.LowPreLltvError(subscriptionParams.prelltv, marketParams.lltv) + ); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? @@ -88,15 +92,17 @@ contract LiquidationProtection { ) public { bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); Id marketId = marketParams.id(); - require(subscriptions[subscriptionId].closeFactor != 0, "Non-valid subscription"); + require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.NonValidSubscription(subscriptionNumber)); - require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), "Inconsistent input"); + require( + UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) + ); uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); require( !_isHealthy(marketId, borrower, collateralPrice, subscriptions[subscriptionId].prelltv), - "Position is healthy" + ErrorsLib.HealthyPosition() ); // Compute seizedAssets or repaidShares and repaidAssets @@ -122,7 +128,9 @@ contract LiquidationProtection { Position memory borrowerPosition = MORPHO.position(marketId, borrower); require( borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, - "Cannot liquidate more than close factor" + ErrorsLib.CloseFactorError( + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor), repaidShares + ) ); } bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); @@ -132,7 +140,7 @@ contract LiquidationProtection { } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { - require(msg.sender == address(MORPHO), "Not Morpho"); + require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho(msg.sender)); ( MarketParams memory marketParams, uint256 seizedAssets, diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol new file mode 100644 index 0000000..0a50c55 --- /dev/null +++ b/src/libraries/ErrorsLib.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.27; + +/// @title ErrorsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library exposing errors. +library ErrorsLib { + error LowPreLltvError(uint256, uint256); + + error NonValidSubscription(uint256); + + error InconsistentInput(uint256, uint256); + + error HealthyPosition(); + + error CloseFactorError(uint256, uint256); + + error NotMorpho(address); +} diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index cd9f1b2..e7232fc 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; +pragma solidity 0.8.27; import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index fa68f25..5bd0fb6 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; +pragma solidity 0.8.27; import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; @@ -7,6 +7,7 @@ import "../lib/forge-std/src/console.sol"; import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProtection.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; +import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; contract LiquidationProtectionTest is Test { uint256 internal constant BLOCK_TIME = 12; @@ -87,7 +88,7 @@ contract LiquidationProtectionTest is Test { vm.startPrank(LIQUIDATOR); - vm.expectRevert(bytes("Non-valid subscription")); + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonValidSubscription.selector, subscriptionNumber)); liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, 0, hex""); } From d7e9229a4a630b61fa203fb7a7df128da6b0fea3 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 6 Sep 2024 17:12:39 +0200 Subject: [PATCH 057/182] feat: improve refactor of market id --- src/LiquidationProtection.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 2377837..b096a8c 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -90,8 +90,8 @@ contract LiquidationProtection { uint256 repaidShares, bytes calldata data ) public { - bytes32 subscriptionId = computeSubscriptionId(borrower, marketParams.id(), subscriptionNumber); Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.NonValidSubscription(subscriptionNumber)); require( From 7553359f990b7633f7013afa5bf0c71c51e01e74 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:59:47 +0200 Subject: [PATCH 058/182] refactor: rename assets into repaidAssets --- src/LiquidationProtection.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b096a8c..b6b81b5 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -134,12 +134,14 @@ contract LiquidationProtection { ); } bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); - (uint256 assets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate(borrower, msg.sender, marketId, subscriptionNumber, assets, repaidShares, seizedAssets); + emit EventsLib.Liquidate( + borrower, msg.sender, marketId, subscriptionNumber, repaidAssets, repaidShares, seizedAssets + ); } - function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { + function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho(msg.sender)); ( MarketParams memory marketParams, @@ -152,12 +154,12 @@ contract LiquidationProtection { MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); if (data.length > 0) { - IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(assets, data); + IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(repaidAssets, data); } - ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), assets); + ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); - ERC20(marketParams.loanToken).safeApprove(address(MORPHO), assets); + ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } function computeSubscriptionId(address borrower, Id marketId, uint256 subscriptionNumber) From 6a645a6dd3fe6f8b00e0c8874b4e103f275db58e Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:29:52 +0200 Subject: [PATCH 059/182] fix: complete rename of slltv into prelltv --- src/libraries/EventsLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index e7232fc..917ac3d 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -22,7 +22,7 @@ library EventsLib { address indexed borrower, Id indexed marketId, uint256 indexed subscriptionNumber, - uint256 slltv, + uint256 preLltv, uint256 closeFactor, uint256 liquidationIncentive ); From 4a67f67c13822a21c6e62508d21f162dbaeb4652 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:45:06 +0200 Subject: [PATCH 060/182] refactor: one slot storage --- src/LiquidationProtection.sol | 66 ++++++++--------------- src/interfaces/ILiquidationProtection.sol | 10 ++++ src/libraries/ErrorsLib.sol | 2 +- src/libraries/EventsLib.sol | 14 ++--- test/LiquidationProtectionTest.sol | 59 ++++++++++---------- 5 files changed, 66 insertions(+), 85 deletions(-) create mode 100644 src/interfaces/ILiquidationProtection.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b6b81b5..4935058 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -11,15 +11,9 @@ import {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 {EventsLib, SubscriptionParams} from "./libraries/EventsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -struct SubscriptionParams { - uint256 prelltv; - uint256 closeFactor; - uint256 liquidationIncentive; -} - /// @title Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org @@ -36,8 +30,7 @@ contract LiquidationProtection { IMorpho public immutable MORPHO; /* STORAGE */ - mapping(bytes32 => SubscriptionParams) public subscriptions; - uint256 public nbSubscription; + mapping(bytes32 => bool) public subscriptions; // TODO EIP-712 signature // TODO authorize this contract on morpho @@ -46,11 +39,7 @@ contract LiquidationProtection { MORPHO = IMorpho(morpho); } - function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) - public - returns (uint256) - { - Id marketId = marketParams.id(); + function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) public { require( subscriptionParams.prelltv < marketParams.lltv, ErrorsLib.LowPreLltvError(subscriptionParams.prelltv, marketParams.lltv) @@ -58,41 +47,34 @@ contract LiquidationProtection { // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, nbSubscription); - - subscriptions[subscriptionId] = subscriptionParams; + Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); - emit EventsLib.Subscribe( - msg.sender, - marketId, - nbSubscription, - subscriptionParams.prelltv, - subscriptionParams.closeFactor, - subscriptionParams.liquidationIncentive - ); + subscriptions[subscriptionId] = true; - return nbSubscription++; + emit EventsLib.Subscribe(msg.sender, marketId, subscriptionParams); } - function unsubscribe(Id marketId, uint256 subscriptionNumber) public { - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionNumber); + function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) public { + Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); delete subscriptions[subscriptionId]; - emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionNumber); + emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionParams); } function liquidate( - uint256 subscriptionNumber, MarketParams calldata marketParams, + SubscriptionParams calldata subscriptionParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) public { Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); - require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.NonValidSubscription(subscriptionNumber)); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionParams); + require(subscriptions[subscriptionId], ErrorsLib.NonValidSubscription(subscriptionId)); require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) @@ -101,14 +83,13 @@ contract LiquidationProtection { MORPHO.accrueInterest(marketParams); require( - !_isHealthy(marketId, borrower, collateralPrice, subscriptions[subscriptionId].prelltv), - ErrorsLib.HealthyPosition() + !_isHealthy(marketId, borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition() ); // Compute seizedAssets or repaidShares and repaidAssets { Market memory marketState = MORPHO.market(marketId); - uint256 liquidationIncentive = subscriptions[subscriptionId].liquidationIncentive; + uint256 liquidationIncentive = subscriptionParams.liquidationIncentive; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); @@ -121,23 +102,22 @@ contract LiquidationProtection { seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } - } - // Check if liquidation is ok with close factor - { + // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketId, borrower); require( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, + borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor) >= repaidShares, ErrorsLib.CloseFactorError( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor), repaidShares + borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor), repaidShares ) ); } + bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); emit EventsLib.Liquidate( - borrower, msg.sender, marketId, subscriptionNumber, repaidAssets, repaidShares, seizedAssets + borrower, msg.sender, marketId, subscriptionParams, repaidAssets, repaidShares, seizedAssets ); } @@ -162,12 +142,12 @@ contract LiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } - function computeSubscriptionId(address borrower, Id marketId, uint256 subscriptionNumber) + function computeSubscriptionId(address borrower, Id marketId, SubscriptionParams memory subscriptionParams) public pure returns (bytes32) { - return keccak256(abi.encode(borrower, marketId, subscriptionNumber)); + return keccak256(abi.encode(borrower, marketId, subscriptionParams)); } function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol new file mode 100644 index 0000000..74ca448 --- /dev/null +++ b/src/interfaces/ILiquidationProtection.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >= 0.5.0; + +struct SubscriptionParams { + uint256 prelltv; + uint256 closeFactor; + uint256 liquidationIncentive; +} + +// TODO: add ILiquidationProtection interface diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 0a50c55..f1bc31b 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.27; library ErrorsLib { error LowPreLltvError(uint256, uint256); - error NonValidSubscription(uint256); + error NonValidSubscription(bytes32); error InconsistentInput(uint256, uint256); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 917ac3d..521c120 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.27; import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {SubscriptionParams} from "../interfaces/ILiquidationProtection.sol"; /// @title EventsLib /// @author Morpho Labs @@ -12,20 +13,13 @@ library EventsLib { address indexed borrower, address indexed liquidator, Id indexed marketId, - uint256 subscriptionNumber, + SubscriptionParams subscriptionParams, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets ); - event Subscribe( - address indexed borrower, - Id indexed marketId, - uint256 indexed subscriptionNumber, - uint256 preLltv, - uint256 closeFactor, - uint256 liquidationIncentive - ); + event Subscribe(address indexed borrower, Id indexed marketId, SubscriptionParams subscriptionParams); - event Unsubscribe(address indexed borrower, Id indexed marketId, uint256 indexed subscriptionNumber); + event Unsubscribe(address indexed borrower, Id indexed marketId, SubscriptionParams subscriptionParams); } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 5bd0fb6..fca83ba 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -17,7 +17,7 @@ contract LiquidationProtectionTest is Test { LiquidationProtection internal liquidationProtection; Id internal marketId; - MarketParams internal market; + MarketParams internal marketParams; IMorpho morpho; ERC20 loanToken; ERC20 collateralToken; @@ -32,9 +32,9 @@ contract LiquidationProtectionTest is Test { morpho = IMorpho(MORPHO); marketId = Id.wrap(0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e); // wstETH/WETH (96.5%) - market = morpho.idToMarketParams(marketId); - loanToken = ERC20(market.loanToken); - collateralToken = ERC20(market.collateralToken); + marketParams = morpho.idToMarketParams(marketId); + loanToken = ERC20(marketParams.loanToken); + collateralToken = ERC20(marketParams.collateralToken); liquidationProtection = new LiquidationProtection(MORPHO); @@ -44,9 +44,9 @@ contract LiquidationProtectionTest is Test { uint256 collateralAmount = 1 * 10 ** 18; deal(address(collateralToken), BORROWER, collateralAmount); - morpho.supplyCollateral(market, collateralAmount, BORROWER, hex""); + morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); uint256 borrowAmount = 5 * 10 ** 17; - morpho.borrow(market, borrowAmount, 0, BORROWER, BORROWER); + morpho.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); morpho.setAuthorization(address(liquidationProtection), true); @@ -59,51 +59,48 @@ contract LiquidationProtectionTest is Test { function testSetSubscription() public virtual { vm.startPrank(BORROWER); - SubscriptionParams memory params; - params.prelltv = 90 * 10 ** 16; // 90% - params.closeFactor = 10 ** 18; // 100% - params.liquidationIncentive = 10 ** 16; // 1% + SubscriptionParams memory subscriptionParams; + subscriptionParams.prelltv = 90 * 10 ** 16; // 90% + subscriptionParams.closeFactor = 10 ** 18; // 100% + subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); + liquidationProtection.subscribe(marketParams, subscriptionParams); - bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionNumber); - (uint256 prelltv, uint256 closeFactor, uint256 liquidationIncentive) = - liquidationProtection.subscriptions(subscriptionId); - assertEq(params.prelltv, prelltv); - assertEq(params.closeFactor, closeFactor); - assertEq(params.liquidationIncentive, liquidationIncentive); + bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionParams); + assertTrue(liquidationProtection.subscriptions(subscriptionId)); } function testRemoveSubscription() public virtual { vm.startPrank(BORROWER); - SubscriptionParams memory params; - params.prelltv = 90 * 10 ** 16; // 90% - params.closeFactor = 10 ** 18; // 100% - params.liquidationIncentive = 10 ** 16; // 1% + SubscriptionParams memory subscriptionParams; + subscriptionParams.prelltv = 90 * 10 ** 16; // 90% + subscriptionParams.closeFactor = 10 ** 18; // 100% + subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); + liquidationProtection.subscribe(marketParams, subscriptionParams); - liquidationProtection.unsubscribe(marketId, subscriptionNumber); + liquidationProtection.unsubscribe(marketParams, subscriptionParams); vm.startPrank(LIQUIDATOR); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonValidSubscription.selector, subscriptionNumber)); - liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, 0, hex""); + bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionParams); + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonValidSubscription.selector, subscriptionId)); + liquidationProtection.liquidate(marketParams, subscriptionParams, BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { vm.startPrank(BORROWER); - SubscriptionParams memory params; - params.prelltv = 10 * 10 ** 16; // 10% - params.closeFactor = 10 ** 18; // 100% - params.liquidationIncentive = 10 ** 16; // 1% + SubscriptionParams memory subscriptionParams; + subscriptionParams.prelltv = 10 * 10 ** 16; // 10% + subscriptionParams.closeFactor = 10 ** 18; // 100% + subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - uint256 subscriptionNumber = liquidationProtection.subscribe(market, params); + liquidationProtection.subscribe(marketParams, subscriptionParams); vm.startPrank(LIQUIDATOR); Position memory position = morpho.position(marketId, BORROWER); - liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.liquidate(marketParams, subscriptionParams, BORROWER, 0, position.borrowShares, hex""); } } From d2685dc7cae96183a6b9064de7573ab80e577e55 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:50:22 +0200 Subject: [PATCH 061/182] refactor: add parameter name to errors --- src/LiquidationProtection.sol | 2 +- src/libraries/ErrorsLib.sol | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 4935058..3b29554 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -59,7 +59,7 @@ contract LiquidationProtection { Id marketId = marketParams.id(); bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); - delete subscriptions[subscriptionId]; + subscriptions[subscriptionId] = false; emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionParams); } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index f1bc31b..62e4f91 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -6,15 +6,15 @@ pragma solidity 0.8.27; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { - error LowPreLltvError(uint256, uint256); + error LowPreLltvError(uint256 prelltv, uint256 lltv); - error NonValidSubscription(bytes32); + error NonValidSubscription(bytes32 subscriptionId); - error InconsistentInput(uint256, uint256); + error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); error HealthyPosition(); - error CloseFactorError(uint256, uint256); + error CloseFactorError(uint256 repayableShares, uint256 repaidShares); - error NotMorpho(address); + error NotMorpho(address caller); } From 073b1451a194cd35900a39af56c991ee60178f35 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:51:10 +0200 Subject: [PATCH 062/182] refactor: public to external when not needed --- src/LiquidationProtection.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 3b29554..e60e9b7 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -39,7 +39,7 @@ contract LiquidationProtection { MORPHO = IMorpho(morpho); } - function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) public { + function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { require( subscriptionParams.prelltv < marketParams.lltv, ErrorsLib.LowPreLltvError(subscriptionParams.prelltv, marketParams.lltv) @@ -55,7 +55,7 @@ contract LiquidationProtection { emit EventsLib.Subscribe(msg.sender, marketId, subscriptionParams); } - function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) public { + function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { Id marketId = marketParams.id(); bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); @@ -71,7 +71,7 @@ contract LiquidationProtection { uint256 seizedAssets, uint256 repaidShares, bytes calldata data - ) public { + ) external { Id marketId = marketParams.id(); bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionParams); require(subscriptions[subscriptionId], ErrorsLib.NonValidSubscription(subscriptionId)); From 0427c956ff39cccab9e3cbae6725f7f061bdadf1 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 18:53:44 +0200 Subject: [PATCH 063/182] refactor: consistent parameter order for events --- src/LiquidationProtection.sol | 2 +- src/libraries/EventsLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e60e9b7..9992211 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -117,7 +117,7 @@ contract LiquidationProtection { (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); emit EventsLib.Liquidate( - borrower, msg.sender, marketId, subscriptionParams, repaidAssets, repaidShares, seizedAssets + borrower, marketId, subscriptionParams, msg.sender, repaidAssets, repaidShares, seizedAssets ); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 521c120..44fa191 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -11,9 +11,9 @@ import {SubscriptionParams} from "../interfaces/ILiquidationProtection.sol"; library EventsLib { event Liquidate( address indexed borrower, - address indexed liquidator, Id indexed marketId, SubscriptionParams subscriptionParams, + address indexed liquidator, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets From dd4b49283c71736264b3452db52dbf030f1f8341 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Sun, 8 Sep 2024 19:17:42 +0200 Subject: [PATCH 064/182] refactor: a couple of renaming --- src/LiquidationProtection.sol | 31 ++++++++++++++----------------- src/libraries/ErrorsLib.sol | 2 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 9992211..119ff20 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -86,31 +86,29 @@ contract LiquidationProtection { !_isHealthy(marketId, borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition() ); - // Compute seizedAssets or repaidShares and repaidAssets { - Market memory marketState = MORPHO.market(marketId); + // Compute seizedAssets or repaidShares and repaidAssets + Market memory market = MORPHO.market(marketId); uint256 liquidationIncentive = subscriptionParams.liquidationIncentive; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - marketState.totalBorrowAssets, marketState.totalBorrowShares + market.totalBorrowAssets, market.totalBorrowShares ); } else { - seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) - .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - seizedAssets = repaidShares.toAssetsDown(marketState.totalBorrowAssets, marketState.totalBorrowShares) - .wMulDown(liquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( + liquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( + liquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketId, borrower); - require( - borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor) >= repaidShares, - ErrorsLib.CloseFactorError( - borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor), repaidShares - ) - ); + uint256 repayableShares = borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor); + require(repaidShares <= repayableShares, ErrorsLib.CloseFactorError(repaidShares, repayableShares)); } bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); @@ -156,11 +154,10 @@ contract LiquidationProtection { returns (bool) { Position memory borrowerPosition = MORPHO.position(id, borrower); - Market memory marketState = MORPHO.market(id); + Market memory market = MORPHO.market(id); - uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp( - marketState.totalBorrowAssets, marketState.totalBorrowShares - ); + uint256 borrowed = + uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); uint256 maxBorrow = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 62e4f91..edf6eaa 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -14,7 +14,7 @@ library ErrorsLib { error HealthyPosition(); - error CloseFactorError(uint256 repayableShares, uint256 repaidShares); + error CloseFactorError(uint256 repaidShares, uint256 repayableShares); error NotMorpho(address caller); } From ac751bc905ca5d09d1894654dd48217a3c378805 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Mon, 9 Sep 2024 11:22:55 +0200 Subject: [PATCH 065/182] refactor: remove subscriptionId from errors --- src/LiquidationProtection.sol | 2 +- src/libraries/ErrorsLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 119ff20..612fb0d 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -74,7 +74,7 @@ contract LiquidationProtection { ) external { Id marketId = marketParams.id(); bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionParams); - require(subscriptions[subscriptionId], ErrorsLib.NonValidSubscription(subscriptionId)); + require(subscriptions[subscriptionId], ErrorsLib.NonValidSubscription()); require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index edf6eaa..3ee1f6a 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.27; library ErrorsLib { error LowPreLltvError(uint256 prelltv, uint256 lltv); - error NonValidSubscription(bytes32 subscriptionId); + error NonValidSubscription(); error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); From b4b76aa224ecc060bea0c8861abf0fab07208d2c Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Mon, 9 Sep 2024 11:30:47 +0200 Subject: [PATCH 066/182] chore: remove subscriptionId from errors in tests --- test/LiquidationProtectionTest.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index fca83ba..f2fff1a 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -84,8 +84,7 @@ contract LiquidationProtectionTest is Test { vm.startPrank(LIQUIDATOR); - bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionParams); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonValidSubscription.selector, subscriptionId)); + vm.expectRevert(ErrorsLib.NonValidSubscription.selector); liquidationProtection.liquidate(marketParams, subscriptionParams, BORROWER, 0, 0, hex""); } From 1ff45cd5784c7b20ff78be092a980b61a0e24aca Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 9 Sep 2024 15:29:33 +0200 Subject: [PATCH 067/182] refactor: improve errors --- src/LiquidationProtection.sol | 8 ++++---- src/libraries/ErrorsLib.sol | 8 ++++---- test/LiquidationProtectionTest.sol | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b096a8c..58043be 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -53,7 +53,7 @@ contract LiquidationProtection { Id marketId = marketParams.id(); require( subscriptionParams.prelltv < marketParams.lltv, - ErrorsLib.LowPreLltvError(subscriptionParams.prelltv, marketParams.lltv) + ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) ); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? @@ -92,7 +92,7 @@ contract LiquidationProtection { ) public { Id marketId = marketParams.id(); bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); - require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.NonValidSubscription(subscriptionNumber)); + require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.InvalidSubscription(subscriptionNumber)); require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) @@ -128,7 +128,7 @@ contract LiquidationProtection { Position memory borrowerPosition = MORPHO.position(marketId, borrower); require( borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, - ErrorsLib.CloseFactorError( + ErrorsLib.LiquidationTooLarge( borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor), repaidShares ) ); @@ -140,7 +140,7 @@ contract LiquidationProtection { } function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { - require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho(msg.sender)); + require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho()); ( MarketParams memory marketParams, uint256 seizedAssets, diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 0a50c55..85d8051 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -6,15 +6,15 @@ pragma solidity 0.8.27; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { - error LowPreLltvError(uint256, uint256); + error PreLltvTooHigh(uint256, uint256); - error NonValidSubscription(uint256); + error InvalidSubscription(uint256); error InconsistentInput(uint256, uint256); error HealthyPosition(); - error CloseFactorError(uint256, uint256); + error LiquidationTooLarge(uint256, uint256); - error NotMorpho(address); + error NotMorpho(); } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 5bd0fb6..90623e3 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -88,7 +88,7 @@ contract LiquidationProtectionTest is Test { vm.startPrank(LIQUIDATOR); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonValidSubscription.selector, subscriptionNumber)); + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.InvalidSubscription.selector, subscriptionNumber)); liquidationProtection.liquidate(subscriptionNumber, market, BORROWER, 0, 0, hex""); } From f5548d8fe4d6f1ac0bc7bf6307a5cc7d49665457 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 9 Sep 2024 15:51:08 +0200 Subject: [PATCH 068/182] style: forge fmt --- src/LiquidationProtection.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 09cddcc..46d7592 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -119,7 +119,6 @@ contract LiquidationProtection { ); } - function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho()); ( From faea1588e63bfdb28b126841db66eebbe33c7612 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 9 Sep 2024 15:57:27 +0200 Subject: [PATCH 069/182] fix: check invalid subscription using boolean array --- src/LiquidationProtection.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 46d7592..fdcd4fd 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -73,8 +73,8 @@ contract LiquidationProtection { bytes calldata data ) external { Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); - require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.InvalidSubscription()); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionParams); + require(subscriptions[subscriptionId], ErrorsLib.InvalidSubscription()); require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) From abce0a0c9747f5ac3468d8166cee4dc573c7705a Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 13:39:57 +0200 Subject: [PATCH 070/182] feat: implement ILiquidationProtection --- src/LiquidationProtection.sol | 3 ++- src/interfaces/ILiquidationProtection.sol | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index fdcd4fd..9369cf0 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -13,12 +13,13 @@ import {SafeTransferLib} from "../lib/solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {EventsLib, SubscriptionParams} from "./libraries/EventsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; +import {ILiquidationProtection} from "./interfaces/ILiquidationProtection.sol"; /// @title Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Liquidation Protection Contract for Morpho -contract LiquidationProtection { +contract LiquidationProtection is ILiquidationProtection { using MarketParamsLib for MarketParams; using UtilsLib for uint256; using SharesMathLib for uint256; diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index 74ca448..27c9a2f 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -1,10 +1,29 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; +import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + struct SubscriptionParams { uint256 prelltv; uint256 closeFactor; uint256 liquidationIncentive; } -// TODO: add ILiquidationProtection interface +interface ILiquidationProtection { + function MORPHO() external view returns (IMorpho); + + function subscriptions(bytes32 subscriptionId) external view returns (bool); + + function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external; + + function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external; + + function liquidate( + MarketParams calldata marketParams, + SubscriptionParams calldata subscriptionParams, + address borrower, + uint256 seizedAssets, + uint256 repaidShares, + bytes calldata data + ) external; +} From d1ecbbade1854b5a66a9f04ae67b13514ee590c5 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 16:09:22 +0200 Subject: [PATCH 071/182] feat: implement factory contract --- src/LiquidationProtection.sol | 49 +++++++++-------------- src/LiquidationProtectionFactory.sol | 36 +++++++++++++++++ src/interfaces/ILiquidationProtection.sol | 7 ++-- src/libraries/ErrorsLib.sol | 2 + src/libraries/EventsLib.sol | 4 +- test/LiquidationProtectionTest.sol | 40 +++++++++--------- 6 files changed, 84 insertions(+), 54 deletions(-) create mode 100644 src/LiquidationProtectionFactory.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 9369cf0..6a00d96 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -31,51 +31,48 @@ contract LiquidationProtection is ILiquidationProtection { IMorpho public immutable MORPHO; /* STORAGE */ - mapping(bytes32 => bool) public subscriptions; + mapping(address => bool) public subscriptions; + MarketParams public marketParams; + SubscriptionParams public subscriptionParams; // TODO EIP-712 signature // TODO authorize this contract on morpho - constructor(address morpho) { + constructor(MarketParams memory _marketParams, SubscriptionParams memory _subscriptionParams, address morpho) { MORPHO = IMorpho(morpho); - } + marketParams = _marketParams; + subscriptionParams = _subscriptionParams; - function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { + // should close factor be lower than 100% ? + // should there be a max liquidation incentive ? require( subscriptionParams.prelltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) ); - // should close factor be lower than 100% ? - // should there be a max liquidation incentive ? - - Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); + } - subscriptions[subscriptionId] = true; + function subscribe() external { + subscriptions[msg.sender] = true; - emit EventsLib.Subscribe(msg.sender, marketId, subscriptionParams); + emit EventsLib.Subscribe(msg.sender); } - function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { - Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); - - subscriptions[subscriptionId] = false; + function unsubscribe() external { + subscriptions[msg.sender] = false; - emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionParams); + emit EventsLib.Unsubscribe(msg.sender); } function liquidate( - MarketParams calldata marketParams, - SubscriptionParams calldata subscriptionParams, + MarketParams calldata _marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) external { + // TODO require(_marketParams == marketParams) ? Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionParams); - require(subscriptions[subscriptionId], ErrorsLib.InvalidSubscription()); + require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) @@ -123,7 +120,7 @@ contract LiquidationProtection is ILiquidationProtection { function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho()); ( - MarketParams memory marketParams, + MarketParams memory _marketParams, uint256 seizedAssets, address borrower, address liquidator, @@ -141,14 +138,6 @@ contract LiquidationProtection is ILiquidationProtection { ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } - function computeSubscriptionId(address borrower, Id marketId, SubscriptionParams memory subscriptionParams) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(borrower, marketId, subscriptionParams)); - } - function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) internal view diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol new file mode 100644 index 0000000..50c9c94 --- /dev/null +++ b/src/LiquidationProtectionFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.27; + +import {IMorpho, MarketParams} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {LiquidationProtection} from "./LiquidationProtection.sol"; +import {ILiquidationProtection} from "./interfaces/ILiquidationProtection.sol"; +import {ErrorsLib} from "./libraries/ErrorsLib.sol"; +import {EventsLib, SubscriptionParams} from "./libraries/EventsLib.sol"; +/// @title Morpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice The Liquidation Protection Factory Contract for Morpho + +contract LiquidationProtectionFactory { + /* IMMUTABLE */ + IMorpho public immutable MORPHO; + + mapping(address => SubscriptionParams) subscriptions; + + constructor(address morpho) { + require(morpho != address(0), ErrorsLib.ZeroAddress()); + + MORPHO = IMorpho(morpho); + } + + function createSubscription(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + external + returns (ILiquidationProtection liquidationProtection) + { + liquidationProtection = ILiquidationProtection( + address(new LiquidationProtection(marketParams, subscriptionParams, address(MORPHO))) + ); + + // TODO event + } +} diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index 27c9a2f..a171621 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -12,15 +12,14 @@ struct SubscriptionParams { interface ILiquidationProtection { function MORPHO() external view returns (IMorpho); - function subscriptions(bytes32 subscriptionId) external view returns (bool); + function subscriptions(address) external view returns (bool); - function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external; + function subscribe() external; - function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external; + function unsubscribe() external; function liquidate( MarketParams calldata marketParams, - SubscriptionParams calldata subscriptionParams, address borrower, uint256 seizedAssets, uint256 repaidShares, diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 2b320c9..663260c 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -17,4 +17,6 @@ library ErrorsLib { error LiquidationTooLarge(uint256 repaidShares, uint256 repayableShares); error NotMorpho(); + + error ZeroAddress(); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 44fa191..c971924 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,7 +19,7 @@ library EventsLib { uint256 seizedAssets ); - event Subscribe(address indexed borrower, Id indexed marketId, SubscriptionParams subscriptionParams); + event Subscribe(address indexed borrower); - event Unsubscribe(address indexed borrower, Id indexed marketId, SubscriptionParams subscriptionParams); + event Unsubscribe(address indexed borrower); } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 843fe7a..b271d9a 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -4,7 +4,9 @@ pragma solidity 0.8.27; import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; +import {ILiquidationProtection} from "../src/interfaces/ILiquidationProtection.sol"; import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProtection.sol"; +import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; @@ -15,7 +17,8 @@ contract LiquidationProtectionTest is Test { address internal BORROWER; address internal LIQUIDATOR; - LiquidationProtection internal liquidationProtection; + LiquidationProtectionFactory internal factory; + ILiquidationProtection internal liquidationProtection; Id internal marketId; MarketParams internal marketParams; IMorpho morpho; @@ -36,7 +39,7 @@ contract LiquidationProtectionTest is Test { loanToken = ERC20(marketParams.loanToken); collateralToken = ERC20(marketParams.collateralToken); - liquidationProtection = new LiquidationProtection(MORPHO); + factory = new LiquidationProtectionFactory(MORPHO); vm.startPrank(BORROWER); loanToken.approve(address(morpho), type(uint256).max); @@ -47,13 +50,6 @@ contract LiquidationProtectionTest is Test { morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); uint256 borrowAmount = 5 * 10 ** 17; morpho.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); - - morpho.setAuthorization(address(liquidationProtection), true); - - vm.startPrank(LIQUIDATOR); - deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); - loanToken.approve(address(liquidationProtection), type(uint256).max); - collateralToken.approve(address(liquidationProtection), type(uint256).max); } function testSetSubscription() public virtual { @@ -64,10 +60,10 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketParams, subscriptionParams); + liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); - bytes32 subscriptionId = liquidationProtection.computeSubscriptionId(BORROWER, marketId, subscriptionParams); - assertTrue(liquidationProtection.subscriptions(subscriptionId)); + liquidationProtection.subscribe(); + assertTrue(liquidationProtection.subscriptions(BORROWER)); } function testRemoveSubscription() public virtual { @@ -78,14 +74,14 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketParams, subscriptionParams); - - liquidationProtection.unsubscribe(marketParams, subscriptionParams); + liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); + liquidationProtection.subscribe(); + liquidationProtection.unsubscribe(); vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.liquidate(marketParams, subscriptionParams, BORROWER, 0, 0, hex""); + liquidationProtection.liquidate(marketParams, BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { @@ -96,10 +92,18 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection.subscribe(marketParams, subscriptionParams); + liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); + morpho.setAuthorization(address(liquidationProtection), true); + liquidationProtection.subscribe(); vm.startPrank(LIQUIDATOR); + + uint256 borrowAmount = 5 * 10 ** 17; + deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); + loanToken.approve(address(liquidationProtection), type(uint256).max); + collateralToken.approve(address(liquidationProtection), type(uint256).max); + Position memory position = morpho.position(marketId, BORROWER); - liquidationProtection.liquidate(marketParams, subscriptionParams, BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.liquidate(marketParams, BORROWER, 0, position.borrowShares, hex""); } } From ca8bdba5bb4d6502ca7be601e7504a4ea7b982da Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 16:12:20 +0200 Subject: [PATCH 072/182] refactor: change variable name in test --- test/LiquidationProtectionTest.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index b271d9a..3e91a22 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -21,7 +21,7 @@ contract LiquidationProtectionTest is Test { ILiquidationProtection internal liquidationProtection; Id internal marketId; MarketParams internal marketParams; - IMorpho morpho; + IMorpho MORPHO; ERC20 loanToken; ERC20 collateralToken; @@ -31,25 +31,25 @@ contract LiquidationProtectionTest is Test { BORROWER = makeAddr("Borrower"); LIQUIDATOR = makeAddr("Liquidator"); - address MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; - morpho = IMorpho(MORPHO); + address morpho = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + MORPHO = IMorpho(morpho); marketId = Id.wrap(0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e); // wstETH/WETH (96.5%) - marketParams = morpho.idToMarketParams(marketId); + marketParams = MORPHO.idToMarketParams(marketId); loanToken = ERC20(marketParams.loanToken); collateralToken = ERC20(marketParams.collateralToken); - factory = new LiquidationProtectionFactory(MORPHO); + factory = new LiquidationProtectionFactory(address(MORPHO)); vm.startPrank(BORROWER); - loanToken.approve(address(morpho), type(uint256).max); - collateralToken.approve(address(morpho), type(uint256).max); + loanToken.approve(address(MORPHO), type(uint256).max); + collateralToken.approve(address(MORPHO), type(uint256).max); uint256 collateralAmount = 1 * 10 ** 18; deal(address(collateralToken), BORROWER, collateralAmount); - morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); + MORPHO.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); uint256 borrowAmount = 5 * 10 ** 17; - morpho.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); + MORPHO.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); } function testSetSubscription() public virtual { @@ -93,7 +93,7 @@ contract LiquidationProtectionTest is Test { subscriptionParams.liquidationIncentive = 10 ** 16; // 1% liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); - morpho.setAuthorization(address(liquidationProtection), true); + MORPHO.setAuthorization(address(liquidationProtection), true); liquidationProtection.subscribe(); vm.startPrank(LIQUIDATOR); @@ -103,7 +103,7 @@ contract LiquidationProtectionTest is Test { loanToken.approve(address(liquidationProtection), type(uint256).max); collateralToken.approve(address(liquidationProtection), type(uint256).max); - Position memory position = morpho.position(marketId, BORROWER); + Position memory position = MORPHO.position(marketId, BORROWER); liquidationProtection.liquidate(marketParams, BORROWER, 0, position.borrowShares, hex""); } } From 97eb391d090e2824e9b253e43a4aac16ef8d2fd3 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 17:48:02 +0200 Subject: [PATCH 073/182] feat: implement CreateSubscription event --- src/LiquidationProtectionFactory.sol | 2 +- src/libraries/EventsLib.sol | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index 50c9c94..7b02ae8 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -31,6 +31,6 @@ contract LiquidationProtectionFactory { address(new LiquidationProtection(marketParams, subscriptionParams, address(MORPHO))) ); - // TODO event + emit EventsLib.CreateSubscription(address(liquidationProtection), marketParams, subscriptionParams); } } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index c971924..fd81eb2 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -22,4 +22,8 @@ library EventsLib { event Subscribe(address indexed borrower); event Unsubscribe(address indexed borrower); + + event CreateSubscription( + address indexed subscription, MarketParams marketParams, SubscriptionParams subscriptionParams + ); } From 7fa0cd3a915c4c0fd6595fddae80ff3edadab433 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 17:52:15 +0200 Subject: [PATCH 074/182] feat: approve morpho at contract creation --- src/LiquidationProtection.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 6a00d96..d506e8e 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -49,6 +49,8 @@ contract LiquidationProtection is ILiquidationProtection { subscriptionParams.prelltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) ); + + ERC20(marketParams.loanToken).safeApprove(address(MORPHO), type(uint256).max); } function subscribe() external { @@ -134,8 +136,6 @@ contract LiquidationProtection is ILiquidationProtection { } ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); - - ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); } function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) From 8b1168a04bc28f2226d96a136bfb7664990e945f Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 17:59:03 +0200 Subject: [PATCH 075/182] refactor: remove unused arg --- src/LiquidationProtection.sol | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index d506e8e..2199430 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -111,7 +111,7 @@ contract LiquidationProtection is ILiquidationProtection { require(repaidShares <= repayableShares, ErrorsLib.LiquidationTooLarge(repaidShares, repayableShares)); } - bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); + bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); emit EventsLib.Liquidate( @@ -121,13 +121,8 @@ contract LiquidationProtection is ILiquidationProtection { function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho()); - ( - MarketParams memory _marketParams, - uint256 seizedAssets, - address borrower, - address liquidator, - bytes memory data - ) = abi.decode(callbackData, (MarketParams, uint256, address, address, bytes)); + (uint256 seizedAssets, address borrower, address liquidator, bytes memory data) = + abi.decode(callbackData, (uint256, address, address, bytes)); MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); From 3da07b1d44444361c2a6c471611960e1012eaaf1 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 18:20:14 +0200 Subject: [PATCH 076/182] style: rename variable and function --- src/LiquidationProtection.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 2199430..d58d84a 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -82,9 +82,7 @@ contract LiquidationProtection is ILiquidationProtection { uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); - require( - !_isHealthy(marketId, borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition() - ); + require(_isPreliquidable(borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition()); { // Compute seizedAssets or repaidShares and repaidAssets @@ -133,11 +131,12 @@ contract LiquidationProtection is ILiquidationProtection { ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); } - function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) + function _isPreliquidable(address borrower, uint256 collateralPrice, uint256 ltvThreshold) internal view returns (bool) { + Id id = marketParams.id(); Position memory borrowerPosition = MORPHO.position(id, borrower); Market memory market = MORPHO.market(id); @@ -146,6 +145,6 @@ contract LiquidationProtection is ILiquidationProtection { uint256 maxBorrow = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); - return maxBorrow >= borrowed; + return maxBorrow < borrowed; } } From f3f65ae1d3204fdcf9ca7e230519c3be6b534724 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 18:31:11 +0200 Subject: [PATCH 077/182] feat: implement factory interface --- src/LiquidationProtectionFactory.sol | 4 +++- src/interfaces/ILiquidationProtectionFactory.sol | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/ILiquidationProtectionFactory.sol diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index 7b02ae8..da61308 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -6,12 +6,14 @@ import {LiquidationProtection} from "./LiquidationProtection.sol"; import {ILiquidationProtection} from "./interfaces/ILiquidationProtection.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib, SubscriptionParams} from "./libraries/EventsLib.sol"; +import {ILiquidationProtectionFactory} from "./interfaces/ILiquidationProtectionFactory.sol"; + /// @title Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Liquidation Protection Factory Contract for Morpho -contract LiquidationProtectionFactory { +contract LiquidationProtectionFactory is ILiquidationProtectionFactory { /* IMMUTABLE */ IMorpho public immutable MORPHO; diff --git a/src/interfaces/ILiquidationProtectionFactory.sol b/src/interfaces/ILiquidationProtectionFactory.sol new file mode 100644 index 0000000..c91032c --- /dev/null +++ b/src/interfaces/ILiquidationProtectionFactory.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >= 0.5.0; + +import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {SubscriptionParams} from "../libraries/EventsLib.sol"; +import {ILiquidationProtection} from "./ILiquidationProtection.sol"; + +interface ILiquidationProtectionFactory { + function MORPHO() external view returns (IMorpho); + + function createSubscription(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + external + returns (ILiquidationProtection liquidationProtection); +} From 285338ff4dc0d0b3b2750ff484b70b376876f007 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 10 Sep 2024 18:35:57 +0200 Subject: [PATCH 078/182] refactor: import --- src/LiquidationProtection.sol | 4 ++-- src/LiquidationProtectionFactory.sol | 4 ++-- src/interfaces/ILiquidationProtectionFactory.sol | 3 +-- test/LiquidationProtectionTest.sol | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index d58d84a..fff8ecf 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -11,9 +11,9 @@ import {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, SubscriptionParams} from "./libraries/EventsLib.sol"; +import {EventsLib} from "./libraries/EventsLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -import {ILiquidationProtection} from "./interfaces/ILiquidationProtection.sol"; +import {ILiquidationProtection, SubscriptionParams} from "./interfaces/ILiquidationProtection.sol"; /// @title Morpho /// @author Morpho Labs diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index da61308..8839fd4 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.27; import {IMorpho, MarketParams} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {LiquidationProtection} from "./LiquidationProtection.sol"; -import {ILiquidationProtection} from "./interfaces/ILiquidationProtection.sol"; +import {ILiquidationProtection, SubscriptionParams} from "./interfaces/ILiquidationProtection.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -import {EventsLib, SubscriptionParams} from "./libraries/EventsLib.sol"; +import {EventsLib} from "./libraries/EventsLib.sol"; import {ILiquidationProtectionFactory} from "./interfaces/ILiquidationProtectionFactory.sol"; /// @title Morpho diff --git a/src/interfaces/ILiquidationProtectionFactory.sol b/src/interfaces/ILiquidationProtectionFactory.sol index c91032c..9ef8e94 100644 --- a/src/interfaces/ILiquidationProtectionFactory.sol +++ b/src/interfaces/ILiquidationProtectionFactory.sol @@ -2,8 +2,7 @@ pragma solidity >= 0.5.0; import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {SubscriptionParams} from "../libraries/EventsLib.sol"; -import {ILiquidationProtection} from "./ILiquidationProtection.sol"; +import {ILiquidationProtection, SubscriptionParams} from "./ILiquidationProtection.sol"; interface ILiquidationProtectionFactory { function MORPHO() external view returns (IMorpho); diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 3e91a22..1638144 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.27; import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; -import {ILiquidationProtection} from "../src/interfaces/ILiquidationProtection.sol"; -import {LiquidationProtection, SubscriptionParams} from "../src/LiquidationProtection.sol"; +import {ILiquidationProtection, SubscriptionParams} from "../src/interfaces/ILiquidationProtection.sol"; +import {LiquidationProtection} from "../src/LiquidationProtection.sol"; import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; From 7cfeb90cc97c04b24abadcc79a7f843352e36cf9 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 10:00:22 +0200 Subject: [PATCH 079/182] style: renaming --- src/LiquidationProtection.sol | 4 ++-- src/LiquidationProtectionFactory.sol | 4 ++-- src/interfaces/ILiquidationProtectionFactory.sol | 2 +- src/libraries/EventsLib.sol | 2 +- test/LiquidationProtectionTest.sol | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index fff8ecf..c1c7b5b 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -82,7 +82,7 @@ contract LiquidationProtection is ILiquidationProtection { uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); - require(_isPreliquidable(borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition()); + require(_isPreLiquidatable(borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition()); { // Compute seizedAssets or repaidShares and repaidAssets @@ -131,7 +131,7 @@ contract LiquidationProtection is ILiquidationProtection { ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); } - function _isPreliquidable(address borrower, uint256 collateralPrice, uint256 ltvThreshold) + function _isPreLiquidatable(address borrower, uint256 collateralPrice, uint256 ltvThreshold) internal view returns (bool) diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index 8839fd4..6ca83ee 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -25,7 +25,7 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { MORPHO = IMorpho(morpho); } - function createSubscription(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external returns (ILiquidationProtection liquidationProtection) { @@ -33,6 +33,6 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { address(new LiquidationProtection(marketParams, subscriptionParams, address(MORPHO))) ); - emit EventsLib.CreateSubscription(address(liquidationProtection), marketParams, subscriptionParams); + emit EventsLib.CreatePreLiquidation(address(liquidationProtection), marketParams, subscriptionParams); } } diff --git a/src/interfaces/ILiquidationProtectionFactory.sol b/src/interfaces/ILiquidationProtectionFactory.sol index 9ef8e94..a5f1c56 100644 --- a/src/interfaces/ILiquidationProtectionFactory.sol +++ b/src/interfaces/ILiquidationProtectionFactory.sol @@ -7,7 +7,7 @@ import {ILiquidationProtection, SubscriptionParams} from "./ILiquidationProtecti interface ILiquidationProtectionFactory { function MORPHO() external view returns (IMorpho); - function createSubscription(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external returns (ILiquidationProtection liquidationProtection); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index fd81eb2..015de5f 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -23,7 +23,7 @@ library EventsLib { event Unsubscribe(address indexed borrower); - event CreateSubscription( + event CreatePreLiquidation( address indexed subscription, MarketParams marketParams, SubscriptionParams subscriptionParams ); } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 1638144..3c80e59 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -60,7 +60,7 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); + liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); liquidationProtection.subscribe(); assertTrue(liquidationProtection.subscriptions(BORROWER)); @@ -74,7 +74,7 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); + liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); liquidationProtection.subscribe(); liquidationProtection.unsubscribe(); @@ -92,7 +92,7 @@ contract LiquidationProtectionTest is Test { subscriptionParams.closeFactor = 10 ** 18; // 100% subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - liquidationProtection = factory.createSubscription(marketParams, subscriptionParams); + liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); MORPHO.setAuthorization(address(liquidationProtection), true); liquidationProtection.subscribe(); From 9942b46e1b44bf5da97a9e551464cb37906e343c Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 14:14:21 +0200 Subject: [PATCH 080/182] test: remove fork in test and redeploy contracts from scratch --- src/mocks/ERC20Mock.sol | 21 ++++++ src/mocks/IrmMock.sol | 25 +++++++ src/mocks/MorphoImport.sol | 7 ++ src/mocks/OracleMock.sol | 12 ++++ test/BaseTest.sol | 66 ++++++++++++++++++ test/LiquidationProtectionFactoryTest.sol | 22 ++++++ test/LiquidationProtectionTest.sol | 81 +++++++---------------- 7 files changed, 178 insertions(+), 56 deletions(-) create mode 100644 src/mocks/ERC20Mock.sol create mode 100644 src/mocks/IrmMock.sol create mode 100644 src/mocks/MorphoImport.sol create mode 100644 src/mocks/OracleMock.sol create mode 100644 test/BaseTest.sol create mode 100644 test/LiquidationProtectionFactoryTest.sol diff --git a/src/mocks/ERC20Mock.sol b/src/mocks/ERC20Mock.sol new file mode 100644 index 0000000..7f5e1e6 --- /dev/null +++ b/src/mocks/ERC20Mock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {ERC20} from "../../lib/solmate/src/tokens/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } + + function setBalance(address account, uint256 amount) external { + _burn(account, balanceOf[account]); + _mint(account, amount); + } +} diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol new file mode 100644 index 0000000..1b797ab --- /dev/null +++ b/src/mocks/IrmMock.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IIrm} from "../../lib/morpho-blue/src/interfaces/IIrm.sol"; +import {MarketParams, Market} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + +import {MathLib} from "../../lib/morpho-blue/src/libraries/MathLib.sol"; + +contract IrmMock is IIrm { + using MathLib for uint128; + + uint256 public apr; + + function setApr(uint256 newApr) external { + apr = newApr; + } + + function borrowRateView(MarketParams memory, Market memory) public view returns (uint256) { + return apr / 365 days; + } + + function borrowRate(MarketParams memory marketParams, Market memory market) external view returns (uint256) { + return borrowRateView(marketParams, market); + } +} diff --git a/src/mocks/MorphoImport.sol b/src/mocks/MorphoImport.sol new file mode 100644 index 0000000..bf778e1 --- /dev/null +++ b/src/mocks/MorphoImport.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.19; +// Force foundry to compile Morpho Blue even though it's not imported by Metamorpho or by the tests. +// Morpho Blue will be compiled with its own solidity version. +// The resulting bytecode is then loaded by BaseTest.sol. + +import "../../lib/morpho-blue/src/Morpho.sol"; diff --git a/src/mocks/OracleMock.sol b/src/mocks/OracleMock.sol new file mode 100644 index 0000000..ffccee1 --- /dev/null +++ b/src/mocks/OracleMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IOracle} from "../../lib/morpho-blue/src/interfaces/IOracle.sol"; + +contract OracleMock is IOracle { + uint256 public price; + + function setPrice(uint256 newPrice) external { + price = newPrice; + } +} diff --git a/test/BaseTest.sol b/test/BaseTest.sol new file mode 100644 index 0000000..e0e0aac --- /dev/null +++ b/test/BaseTest.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import "../lib/forge-std/src/Test.sol"; +import "../lib/forge-std/src/console2.sol"; + +import {ERC20Mock} from "../src/mocks/ERC20Mock.sol"; +import {IrmMock} from "../src/mocks/IrmMock.sol"; +import {OracleMock} from "../src/mocks/OracleMock.sol"; + +import {MarketParams, IMorpho} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; +import {ORACLE_PRICE_SCALE} from "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; + +contract BaseTest is Test { + address internal SUPPLIER = makeAddr("Supplier"); + address internal BORROWER = makeAddr("Borrower"); + address internal LIQUIDATOR = makeAddr("Liquidator"); + address internal MORPHO_OWNER = makeAddr("MorphoOwner"); + address internal MORPHO_FEE_RECIPIENT = makeAddr("MorphoFeeRecipient"); + + IMorpho internal MORPHO = IMorpho(deployCode("Morpho.sol", abi.encode(MORPHO_OWNER))); + ERC20Mock internal loanToken = new ERC20Mock("loan", "B", 18); + ERC20Mock internal collateralToken = new ERC20Mock("collateral", "C", 18); + OracleMock internal oracle = new OracleMock(); + IrmMock internal irm = new IrmMock(); + uint256 internal lltv = 0.8 ether; // 80% + + MarketParams internal market; + + function setUp() public virtual { + vm.label(address(MORPHO), "Morpho"); + vm.label(address(loanToken), "Loan"); + vm.label(address(collateralToken), "Collateral"); + vm.label(address(oracle), "Oracle"); + vm.label(address(irm), "Irm"); + + oracle.setPrice(ORACLE_PRICE_SCALE); + + irm.setApr(0.5 ether); // 50%. + + vm.startPrank(MORPHO_OWNER); + MORPHO.enableIrm(address(irm)); + MORPHO.setFeeRecipient(MORPHO_FEE_RECIPIENT); + + MORPHO.enableLltv(lltv); + vm.stopPrank(); + + market = MarketParams({ + loanToken: address(loanToken), + collateralToken: address(collateralToken), + oracle: address(oracle), + irm: address(irm), + lltv: lltv // 80 % + }); + + MORPHO.createMarket(market); + + vm.startPrank(SUPPLIER); + loanToken.approve(address(MORPHO), type(uint256).max); + vm.stopPrank(); + + vm.prank(BORROWER); + collateralToken.approve(address(MORPHO), type(uint256).max); + } +} diff --git a/test/LiquidationProtectionFactoryTest.sol b/test/LiquidationProtectionFactoryTest.sol new file mode 100644 index 0000000..3e14b90 --- /dev/null +++ b/test/LiquidationProtectionFactoryTest.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import "./BaseTest.sol"; +import {SubscriptionParams, ILiquidationProtection} from "../src/interfaces/ILiquidationProtection.sol"; +import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; + +contract LiquidationProtectionFactoryTest is BaseTest { + LiquidationProtectionFactory factory; + + function setUp() public override { + super.setUp(); + + factory = new LiquidationProtectionFactory(address(MORPHO)); + } + + function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { + vm.assume(subscription.prelltv < lltv); + + ILiquidationProtection liquidationProtection = factory.createPreLiquidation(market, subscription); + } +} diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 3c80e59..294edf0 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -4,106 +4,75 @@ pragma solidity 0.8.27; import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; +import "./BaseTest.sol"; + import {ILiquidationProtection, SubscriptionParams} from "../src/interfaces/ILiquidationProtection.sol"; import {LiquidationProtection} from "../src/LiquidationProtection.sol"; import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; +import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; -contract LiquidationProtectionTest is Test { - uint256 internal constant BLOCK_TIME = 12; - - address internal BORROWER; - address internal LIQUIDATOR; +contract LiquidationProtectionTest is BaseTest { + using MarketParamsLib for MarketParams; LiquidationProtectionFactory internal factory; ILiquidationProtection internal liquidationProtection; - Id internal marketId; - MarketParams internal marketParams; - IMorpho MORPHO; - ERC20 loanToken; - ERC20 collateralToken; + SubscriptionParams subscription; - function setUp() public virtual { - vm.createSelectFork(vm.rpcUrl("mainnet")); + function setUp() public override { + super.setUp(); - BORROWER = makeAddr("Borrower"); - LIQUIDATOR = makeAddr("Liquidator"); - - address morpho = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; - MORPHO = IMorpho(morpho); + factory = new LiquidationProtectionFactory(address(MORPHO)); + subscription = SubscriptionParams(0.7 ether, 1 ether, 0.01 ether); // prelltv=70%, closrfactor=100%, incentive=1% + liquidationProtection = factory.createPreLiquidation(market, subscription); - marketId = Id.wrap(0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e); // wstETH/WETH (96.5%) - marketParams = MORPHO.idToMarketParams(marketId); - loanToken = ERC20(marketParams.loanToken); - collateralToken = ERC20(marketParams.collateralToken); + uint256 collateralAmount = 1 ether; + deal(address(collateralToken), BORROWER, collateralAmount); + vm.prank(BORROWER); + MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); - factory = new LiquidationProtectionFactory(address(MORPHO)); + uint256 borrowAmount = 0.75 ether; + deal(address(loanToken), SUPPLIER, borrowAmount); - vm.startPrank(BORROWER); - loanToken.approve(address(MORPHO), type(uint256).max); - collateralToken.approve(address(MORPHO), type(uint256).max); + vm.prank(SUPPLIER); + MORPHO.supply(market, borrowAmount, 0, SUPPLIER, hex""); - uint256 collateralAmount = 1 * 10 ** 18; - deal(address(collateralToken), BORROWER, collateralAmount); - MORPHO.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); - uint256 borrowAmount = 5 * 10 ** 17; - MORPHO.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); + vm.prank(BORROWER); + MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); } function testSetSubscription() public virtual { vm.startPrank(BORROWER); - - SubscriptionParams memory subscriptionParams; - subscriptionParams.prelltv = 90 * 10 ** 16; // 90% - subscriptionParams.closeFactor = 10 ** 18; // 100% - subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - - liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); - liquidationProtection.subscribe(); assertTrue(liquidationProtection.subscriptions(BORROWER)); } function testRemoveSubscription() public virtual { vm.startPrank(BORROWER); - - SubscriptionParams memory subscriptionParams; - subscriptionParams.prelltv = 90 * 10 ** 16; // 90% - subscriptionParams.closeFactor = 10 ** 18; // 100% - subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - - liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); liquidationProtection.subscribe(); liquidationProtection.unsubscribe(); vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.liquidate(marketParams, BORROWER, 0, 0, hex""); + liquidationProtection.liquidate(market, BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { vm.startPrank(BORROWER); - - SubscriptionParams memory subscriptionParams; - subscriptionParams.prelltv = 10 * 10 ** 16; // 10% - subscriptionParams.closeFactor = 10 ** 18; // 100% - subscriptionParams.liquidationIncentive = 10 ** 16; // 1% - - liquidationProtection = factory.createPreLiquidation(marketParams, subscriptionParams); MORPHO.setAuthorization(address(liquidationProtection), true); liquidationProtection.subscribe(); vm.startPrank(LIQUIDATOR); - uint256 borrowAmount = 5 * 10 ** 17; + uint256 borrowAmount = 0.75 ether; deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); loanToken.approve(address(liquidationProtection), type(uint256).max); collateralToken.approve(address(liquidationProtection), type(uint256).max); - Position memory position = MORPHO.position(marketId, BORROWER); - liquidationProtection.liquidate(marketParams, BORROWER, 0, position.borrowShares, hex""); + Position memory position = MORPHO.position(market.id(), BORROWER); + liquidationProtection.liquidate(market, BORROWER, 0, position.borrowShares, hex""); } } From e1a4deec25368245521ca0ae668a7f4ae65ad359 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 16:38:49 +0200 Subject: [PATCH 081/182] feat: immutable market id --- src/LiquidationProtection.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index c1c7b5b..ba9c866 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -29,7 +29,7 @@ contract LiquidationProtection is ILiquidationProtection { /* IMMUTABLE */ IMorpho public immutable MORPHO; - + Id public immutable marketId; /* STORAGE */ mapping(address => bool) public subscriptions; MarketParams public marketParams; @@ -42,7 +42,7 @@ contract LiquidationProtection is ILiquidationProtection { MORPHO = IMorpho(morpho); marketParams = _marketParams; subscriptionParams = _subscriptionParams; - + marketId = _marketParams.id(); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? require( @@ -73,7 +73,6 @@ contract LiquidationProtection is ILiquidationProtection { bytes calldata data ) external { // TODO require(_marketParams == marketParams) ? - Id marketId = marketParams.id(); require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); require( @@ -136,9 +135,8 @@ contract LiquidationProtection is ILiquidationProtection { view returns (bool) { - Id id = marketParams.id(); - Position memory borrowerPosition = MORPHO.position(id, borrower); - Market memory market = MORPHO.market(id); + Position memory borrowerPosition = MORPHO.position(marketId, borrower); + Market memory market = MORPHO.market(marketId); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); From 17f1e598a5f83c9fa3d0f8ca7cd821fa4256f9fb Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 16:42:29 +0200 Subject: [PATCH 082/182] refactor: remove unused arg --- src/LiquidationProtection.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index ba9c866..c277202 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -81,7 +81,7 @@ contract LiquidationProtection is ILiquidationProtection { uint256 collateralPrice = IOracle(marketParams.oracle).price(); MORPHO.accrueInterest(marketParams); - require(_isPreLiquidatable(borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition()); + require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.HealthyPosition()); { // Compute seizedAssets or repaidShares and repaidAssets @@ -130,18 +130,14 @@ contract LiquidationProtection is ILiquidationProtection { ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); } - function _isPreLiquidatable(address borrower, uint256 collateralPrice, uint256 ltvThreshold) - internal - view - returns (bool) - { + function _isPreLiquidatable(address borrower, uint256 collateralPrice) internal view returns (bool) { Position memory borrowerPosition = MORPHO.position(marketId, borrower); Market memory market = MORPHO.market(marketId); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 maxBorrow = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); + uint256 maxBorrow = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) + .wMulDown(subscriptionParams.prelltv); return maxBorrow < borrowed; } From cdff984373b07fb01491523e73f7c0eb9f164261 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 16:59:19 +0200 Subject: [PATCH 083/182] feat: implement mapping in factory --- src/LiquidationProtectionFactory.sol | 13 ++++++++++++- src/libraries/ErrorsLib.sol | 2 ++ test/LiquidationProtectionFactoryTest.sol | 3 +-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index 6ca83ee..80e75de 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -17,7 +17,7 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { /* IMMUTABLE */ IMorpho public immutable MORPHO; - mapping(address => SubscriptionParams) subscriptions; + mapping(bytes32 => ILiquidationProtection) subscriptions; constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); @@ -29,10 +29,21 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { external returns (ILiquidationProtection liquidationProtection) { + bytes32 preLiquidationId = getPreLiquidationId(marketParams, subscriptionParams); + require(address(subscriptions[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); + liquidationProtection = ILiquidationProtection( address(new LiquidationProtection(marketParams, subscriptionParams, address(MORPHO))) ); + subscriptions[preLiquidationId] = liquidationProtection; emit EventsLib.CreatePreLiquidation(address(liquidationProtection), marketParams, subscriptionParams); } + + function getPreLiquidationId(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + internal + returns (bytes32) + { + return keccak256(abi.encode(marketParams, subscriptionParams)); + } } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 663260c..8f0e5d1 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -19,4 +19,6 @@ library ErrorsLib { error NotMorpho(); error ZeroAddress(); + + error RedundantMarket(); } diff --git a/test/LiquidationProtectionFactoryTest.sol b/test/LiquidationProtectionFactoryTest.sol index 3e14b90..1ce7732 100644 --- a/test/LiquidationProtectionFactoryTest.sol +++ b/test/LiquidationProtectionFactoryTest.sol @@ -10,13 +10,12 @@ contract LiquidationProtectionFactoryTest is BaseTest { function setUp() public override { super.setUp(); - - factory = new LiquidationProtectionFactory(address(MORPHO)); } function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { vm.assume(subscription.prelltv < lltv); + factory = new LiquidationProtectionFactory(address(MORPHO)); ILiquidationProtection liquidationProtection = factory.createPreLiquidation(market, subscription); } } From 5e1ca490a51f69b66db1c7fed42f8f35748c36b7 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 11 Sep 2024 17:28:27 +0200 Subject: [PATCH 084/182] feat: deconstruct subscription params for immutability --- src/LiquidationProtection.sol | 28 +++++++++++++++------------- src/libraries/EventsLib.sol | 1 - 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index c277202..523bf48 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -30,10 +30,13 @@ contract LiquidationProtection is ILiquidationProtection { /* IMMUTABLE */ IMorpho public immutable MORPHO; Id public immutable marketId; + uint256 public immutable prelltv; + uint256 public immutable closeFactor; + uint256 public immutable liquidationIncentive; + /* STORAGE */ mapping(address => bool) public subscriptions; MarketParams public marketParams; - SubscriptionParams public subscriptionParams; // TODO EIP-712 signature // TODO authorize this contract on morpho @@ -41,14 +44,15 @@ contract LiquidationProtection is ILiquidationProtection { constructor(MarketParams memory _marketParams, SubscriptionParams memory _subscriptionParams, address morpho) { MORPHO = IMorpho(morpho); marketParams = _marketParams; - subscriptionParams = _subscriptionParams; + + prelltv = _subscriptionParams.prelltv; + closeFactor = _subscriptionParams.closeFactor; + liquidationIncentive = _subscriptionParams.liquidationIncentive; + marketId = _marketParams.id(); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - require( - subscriptionParams.prelltv < marketParams.lltv, - ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) - ); + require(prelltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh(prelltv, marketParams.lltv)); ERC20(marketParams.loanToken).safeApprove(address(MORPHO), type(uint256).max); } @@ -86,7 +90,7 @@ contract LiquidationProtection is ILiquidationProtection { { // Compute seizedAssets or repaidShares and repaidAssets Market memory market = MORPHO.market(marketId); - uint256 liquidationIncentive = subscriptionParams.liquidationIncentive; + uint256 liquidationIncentive = liquidationIncentive; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); @@ -104,16 +108,14 @@ contract LiquidationProtection is ILiquidationProtection { // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketId, borrower); - uint256 repayableShares = borrowerPosition.borrowShares.wMulDown(subscriptionParams.closeFactor); + uint256 repayableShares = borrowerPosition.borrowShares.wMulDown(closeFactor); require(repaidShares <= repayableShares, ErrorsLib.LiquidationTooLarge(repaidShares, repayableShares)); } bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate( - borrower, marketId, subscriptionParams, msg.sender, repaidAssets, repaidShares, seizedAssets - ); + emit EventsLib.Liquidate(borrower, marketId, msg.sender, repaidAssets, repaidShares, seizedAssets); } function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { @@ -136,8 +138,8 @@ contract LiquidationProtection is ILiquidationProtection { uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 maxBorrow = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) - .wMulDown(subscriptionParams.prelltv); + uint256 maxBorrow = + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(prelltv); return maxBorrow < borrowed; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 015de5f..af7b40c 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -12,7 +12,6 @@ library EventsLib { event Liquidate( address indexed borrower, Id indexed marketId, - SubscriptionParams subscriptionParams, address indexed liquidator, uint256 repaidAssets, uint256 repaidShares, From 91c2c05d918d699f36ad933ca586ad9f8ee9f2ea Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 12 Sep 2024 17:08:07 +0200 Subject: [PATCH 085/182] feat: deconstruct market params for immutability --- src/LiquidationProtection.sol | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 523bf48..fca2a99 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -30,31 +30,42 @@ contract LiquidationProtection is ILiquidationProtection { /* IMMUTABLE */ IMorpho public immutable MORPHO; Id public immutable marketId; + uint256 public immutable prelltv; uint256 public immutable closeFactor; uint256 public immutable liquidationIncentive; + uint256 public immutable lltv; + + address immutable collateralToken; + address immutable loanToken; + address immutable irm; + address immutable oracle; /* STORAGE */ mapping(address => bool) public subscriptions; - MarketParams public marketParams; // TODO EIP-712 signature // TODO authorize this contract on morpho constructor(MarketParams memory _marketParams, SubscriptionParams memory _subscriptionParams, address morpho) { MORPHO = IMorpho(morpho); - marketParams = _marketParams; prelltv = _subscriptionParams.prelltv; closeFactor = _subscriptionParams.closeFactor; liquidationIncentive = _subscriptionParams.liquidationIncentive; + lltv = _marketParams.lltv; + collateralToken = _marketParams.collateralToken; + loanToken = _marketParams.loanToken; + irm = _marketParams.irm; + oracle = _marketParams.oracle; + marketId = _marketParams.id(); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - require(prelltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh(prelltv, marketParams.lltv)); + require(prelltv < lltv, ErrorsLib.PreLltvTooHigh(prelltv, lltv)); - ERC20(marketParams.loanToken).safeApprove(address(MORPHO), type(uint256).max); + ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } function subscribe() external { @@ -82,8 +93,9 @@ contract LiquidationProtection is ILiquidationProtection { require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) ); - uint256 collateralPrice = IOracle(marketParams.oracle).price(); + uint256 collateralPrice = IOracle(oracle).price(); + MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); MORPHO.accrueInterest(marketParams); require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.HealthyPosition()); @@ -123,13 +135,14 @@ contract LiquidationProtection is ILiquidationProtection { (uint256 seizedAssets, address borrower, address liquidator, bytes memory data) = abi.decode(callbackData, (uint256, address, address, bytes)); + MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); if (data.length > 0) { IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(repaidAssets, data); } - ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); + ERC20(loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); } function _isPreLiquidatable(address borrower, uint256 collateralPrice) internal view returns (bool) { From cff7db6e013a945f9526e41750e7539fb16586ca Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 12 Sep 2024 17:13:44 +0200 Subject: [PATCH 086/182] feat: use getPreLiquidationid as pure fn --- src/LiquidationProtectionFactory.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LiquidationProtectionFactory.sol b/src/LiquidationProtectionFactory.sol index 80e75de..9162a37 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/LiquidationProtectionFactory.sol @@ -42,6 +42,7 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { function getPreLiquidationId(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) internal + pure returns (bytes32) { return keccak256(abi.encode(marketParams, subscriptionParams)); From c7a4dbb27c1ec4eccd6ca02af723f650429ad58b Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 12 Sep 2024 17:28:27 +0200 Subject: [PATCH 087/182] style: rename LiquidationTooLarge error --- src/LiquidationProtection.sol | 2 +- src/libraries/ErrorsLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index fca2a99..b8d6ab0 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -121,7 +121,7 @@ contract LiquidationProtection is ILiquidationProtection { // Check if liquidation is ok with close factor Position memory borrowerPosition = MORPHO.position(marketId, borrower); uint256 repayableShares = borrowerPosition.borrowShares.wMulDown(closeFactor); - require(repaidShares <= repayableShares, ErrorsLib.LiquidationTooLarge(repaidShares, repayableShares)); + require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); } bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 8f0e5d1..588349a 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -14,7 +14,7 @@ library ErrorsLib { error HealthyPosition(); - error LiquidationTooLarge(uint256 repaidShares, uint256 repayableShares); + error PreLiquidationTooLarge(uint256 repaidShares, uint256 repayableShares); error NotMorpho(); From a533cf6ac196ed21b277ce54e865bd47f27e61ad Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 12 Sep 2024 17:37:35 +0200 Subject: [PATCH 088/182] style: rename HealthyPosition error to NotPreLiquidatablePosition --- src/LiquidationProtection.sol | 2 +- src/libraries/ErrorsLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b8d6ab0..6dda8fa 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -97,7 +97,7 @@ contract LiquidationProtection is ILiquidationProtection { MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); MORPHO.accrueInterest(marketParams); - require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.HealthyPosition()); + require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.NotPreLiquidatablePosition()); { // Compute seizedAssets or repaidShares and repaidAssets diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 588349a..6953ead 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -12,7 +12,7 @@ library ErrorsLib { error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); - error HealthyPosition(); + error NotPreLiquidatablePosition(); error PreLiquidationTooLarge(uint256 repaidShares, uint256 repayableShares); From 574546b49cf0377a8da57ea126235683ebad53f3 Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 12 Sep 2024 18:23:22 +0200 Subject: [PATCH 089/182] style: rename liquidationIncentive --- src/LiquidationProtection.sol | 11 +++++------ src/interfaces/ILiquidationProtection.sol | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 6dda8fa..16dc5ce 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -33,7 +33,7 @@ contract LiquidationProtection is ILiquidationProtection { uint256 public immutable prelltv; uint256 public immutable closeFactor; - uint256 public immutable liquidationIncentive; + uint256 public immutable preLiquidationIncentive; uint256 public immutable lltv; address immutable collateralToken; @@ -52,7 +52,7 @@ contract LiquidationProtection is ILiquidationProtection { prelltv = _subscriptionParams.prelltv; closeFactor = _subscriptionParams.closeFactor; - liquidationIncentive = _subscriptionParams.liquidationIncentive; + preLiquidationIncentive = _subscriptionParams.preLiquidationIncentive; lltv = _marketParams.lltv; collateralToken = _marketParams.collateralToken; @@ -102,19 +102,18 @@ contract LiquidationProtection is ILiquidationProtection { { // Compute seizedAssets or repaidShares and repaidAssets Market memory market = MORPHO.market(marketId); - uint256 liquidationIncentive = liquidationIncentive; if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + repaidShares = seizedAssetsQuoted.wDivUp(preLiquidationIncentive).toSharesUp( market.totalBorrowAssets, market.totalBorrowShares ); } else { seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive + preLiquidationIncentive ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive + preLiquidationIncentive ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index a171621..7f0b747 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -6,7 +6,7 @@ import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorph struct SubscriptionParams { uint256 prelltv; uint256 closeFactor; - uint256 liquidationIncentive; + uint256 preLiquidationIncentive; } interface ILiquidationProtection { From 10ac40d47353cfed8353a7db399ed269a3be1a9f Mon Sep 17 00:00:00 2001 From: MathisGD Date: Thu, 12 Sep 2024 18:36:44 +0200 Subject: [PATCH 090/182] refactor: little improvements --- src/LiquidationProtection.sol | 47 ++++++++--------------- src/interfaces/ILiquidationProtection.sol | 8 +--- test/LiquidationProtectionTest.sol | 4 +- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 6dda8fa..8c4577f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -80,14 +80,7 @@ contract LiquidationProtection is ILiquidationProtection { emit EventsLib.Unsubscribe(msg.sender); } - function liquidate( - MarketParams calldata _marketParams, - address borrower, - uint256 seizedAssets, - uint256 repaidShares, - bytes calldata data - ) external { - // TODO require(_marketParams == marketParams) ? + function liquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); require( @@ -99,31 +92,23 @@ contract LiquidationProtection is ILiquidationProtection { MORPHO.accrueInterest(marketParams); require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.NotPreLiquidatablePosition()); - { - // Compute seizedAssets or repaidShares and repaidAssets - Market memory market = MORPHO.market(marketId); - uint256 liquidationIncentive = liquidationIncentive; - if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - - repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - market.totalBorrowAssets, market.totalBorrowShares - ); - } else { - seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive - ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive - ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - } - - // Check if liquidation is ok with close factor - Position memory borrowerPosition = MORPHO.position(marketId, borrower); - uint256 repayableShares = borrowerPosition.borrowShares.wMulDown(closeFactor); - require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); + Market memory market = MORPHO.market(marketId); + if (seizedAssets > 0) { + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); + + repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + market.totalBorrowAssets, market.totalBorrowShares + ); + } else { + seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( + liquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } + // Check if liquidation is ok with close factor + uint256 repayableShares = MORPHO.position(marketId, borrower).borrowShares.wMulDown(closeFactor); + 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); diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index a171621..9680df9 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -18,11 +18,5 @@ interface ILiquidationProtection { function unsubscribe() external; - function liquidate( - MarketParams calldata marketParams, - address borrower, - uint256 seizedAssets, - uint256 repaidShares, - bytes calldata data - ) external; + function liquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 294edf0..4dc6587 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -57,7 +57,7 @@ contract LiquidationProtectionTest is BaseTest { vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.liquidate(market, BORROWER, 0, 0, hex""); + liquidationProtection.liquidate(BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { @@ -73,6 +73,6 @@ contract LiquidationProtectionTest is BaseTest { collateralToken.approve(address(liquidationProtection), type(uint256).max); Position memory position = MORPHO.position(market.id(), BORROWER); - liquidationProtection.liquidate(market, BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.liquidate(BORROWER, 0, position.borrowShares, hex""); } } From 01fc6eade5ea3da7930caf313c183fa5b61fdacb Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 10:37:55 +0200 Subject: [PATCH 091/182] style: rename liquidationIncentive --- src/LiquidationProtection.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index a860b33..41bb3f1 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -96,12 +96,12 @@ contract LiquidationProtection is ILiquidationProtection { if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + repaidShares = seizedAssetsQuoted.wDivUp(preLiquidationIncentive).toSharesUp( market.totalBorrowAssets, market.totalBorrowShares ); } else { seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive + preLiquidationIncentive ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } From f3c4c923370129d2574ba52c9afea0d5e63cc356 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 10:58:25 +0200 Subject: [PATCH 092/182] style: rename maxBorrow --- src/LiquidationProtection.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 41bb3f1..b9dbcee 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -136,9 +136,9 @@ contract LiquidationProtection is ILiquidationProtection { uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 maxBorrow = + uint256 preLiquidationBorrowThreshold = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(prelltv); - return maxBorrow < borrowed; + return preLiquidationBorrowThreshold < borrowed; } } From abbb45d8b635b9e6ce288542321e560f8b338113 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 11:11:35 +0200 Subject: [PATCH 093/182] style: rename callback --- src/LiquidationProtection.sol | 4 ++-- src/interfaces/IPreLiquidationCallback.sol | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/IPreLiquidationCallback.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b9dbcee..73ee171 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -5,7 +5,6 @@ import {Id, MarketParams, IMorpho, Position, Market} from "../lib/morpho-blue/sr import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {UtilsLib} from "../lib/morpho-blue/src/libraries/UtilsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; -import {IMorphoLiquidateCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; import {MathLib} from "../lib/morpho-blue/src/libraries/MathLib.sol"; import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol"; @@ -13,6 +12,7 @@ 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 {ILiquidationProtection, SubscriptionParams} from "./interfaces/ILiquidationProtection.sol"; /// @title Morpho @@ -124,7 +124,7 @@ contract LiquidationProtection is ILiquidationProtection { MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); if (data.length > 0) { - IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(repaidAssets, data); + IPreLiquidationCallback(liquidator).onPreLiquidate(repaidAssets, data); } ERC20(loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); diff --git a/src/interfaces/IPreLiquidationCallback.sol b/src/interfaces/IPreLiquidationCallback.sol new file mode 100644 index 0000000..6982780 --- /dev/null +++ b/src/interfaces/IPreLiquidationCallback.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >= 0.5.0; + +interface IPreLiquidationCallback { + function onPreLiquidate(uint256 repaidAssets, bytes calldata data) external; +} From 9c45be17726415d63d947a6722a0f0dd29a82379 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 11:35:45 +0200 Subject: [PATCH 094/182] style: rename liquidate into preliquidate --- src/LiquidationProtection.sol | 2 +- src/interfaces/ILiquidationProtection.sol | 2 +- test/LiquidationProtectionTest.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 73ee171..078a943 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -80,7 +80,7 @@ contract LiquidationProtection is ILiquidationProtection { emit EventsLib.Unsubscribe(msg.sender); } - function liquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { + function preliquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); require( diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index c83969b..9d90a13 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -18,5 +18,5 @@ interface ILiquidationProtection { function unsubscribe() external; - function liquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; + function preliquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 4dc6587..58f5c2f 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -57,7 +57,7 @@ contract LiquidationProtectionTest is BaseTest { vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.liquidate(BORROWER, 0, 0, hex""); + liquidationProtection.preliquidate(BORROWER, 0, 0, hex""); } function testSoftLiquidation() public virtual { @@ -73,6 +73,6 @@ contract LiquidationProtectionTest is BaseTest { collateralToken.approve(address(liquidationProtection), type(uint256).max); Position memory position = MORPHO.position(market.id(), BORROWER); - liquidationProtection.liquidate(BORROWER, 0, position.borrowShares, hex""); + liquidationProtection.preliquidate(BORROWER, 0, position.borrowShares, hex""); } } From f92fc2b72f0b6c941fe03205de22f268b665917b Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 12:21:34 +0200 Subject: [PATCH 095/182] test: implement fuzz test --- test/LiquidationProtectionTest.sol | 75 ++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 58f5c2f..1e9c5d0 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -7,50 +7,46 @@ import "../lib/forge-std/src/console.sol"; import "./BaseTest.sol"; import {ILiquidationProtection, SubscriptionParams} from "../src/interfaces/ILiquidationProtection.sol"; +import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {LiquidationProtection} from "../src/LiquidationProtection.sol"; import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; +import {MathLib, WAD} from "../lib/morpho-blue/src/libraries/MathLib.sol"; +import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol"; contract LiquidationProtectionTest is BaseTest { using MarketParamsLib for MarketParams; + using SharesMathLib for uint256; + using MathLib for uint256; + using MathLib for uint128; LiquidationProtectionFactory internal factory; ILiquidationProtection internal liquidationProtection; - SubscriptionParams subscription; function setUp() public override { super.setUp(); factory = new LiquidationProtectionFactory(address(MORPHO)); - subscription = SubscriptionParams(0.7 ether, 1 ether, 0.01 ether); // prelltv=70%, closrfactor=100%, incentive=1% - liquidationProtection = factory.createPreLiquidation(market, subscription); - - uint256 collateralAmount = 1 ether; - deal(address(collateralToken), BORROWER, collateralAmount); - vm.prank(BORROWER); - MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); - - uint256 borrowAmount = 0.75 ether; - deal(address(loanToken), SUPPLIER, borrowAmount); - - vm.prank(SUPPLIER); - MORPHO.supply(market, borrowAmount, 0, SUPPLIER, hex""); - - vm.prank(BORROWER); - MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); } - function testSetSubscription() public virtual { + function testSetSubscription(SubscriptionParams calldata subscription) public virtual { + vm.assume(subscription.prelltv < market.lltv); + liquidationProtection = factory.createPreLiquidation(market, subscription); + vm.startPrank(BORROWER); liquidationProtection.subscribe(); assertTrue(liquidationProtection.subscriptions(BORROWER)); } - function testRemoveSubscription() public virtual { + function testRemoveSubscription(SubscriptionParams calldata subscription) public virtual { + vm.assume(subscription.prelltv < market.lltv); + liquidationProtection = factory.createPreLiquidation(market, subscription); + vm.startPrank(BORROWER); + liquidationProtection.subscribe(); liquidationProtection.unsubscribe(); @@ -60,19 +56,50 @@ contract LiquidationProtectionTest is BaseTest { liquidationProtection.preliquidate(BORROWER, 0, 0, hex""); } - function testSoftLiquidation() public virtual { + function testSoftLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) + public + virtual + { + subscription.prelltv = bound(subscription.prelltv, WAD / 100, market.lltv - 1); + subscription.closeFactor = bound(subscription.closeFactor, WAD / 100, WAD - 1); + subscription.preLiquidationIncentive = bound(subscription.preLiquidationIncentive, 1, WAD / 10); + collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); + + liquidationProtection = factory.createPreLiquidation(market, subscription); + + uint256 collateralPrice = IOracle(market.oracle).price(); + uint256 maxBorrow = + collateralAmount.mulDivDown(IOracle(market.oracle).price(), ORACLE_PRICE_SCALE).wMulDown(market.lltv); + uint256 minBorrow = + collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(subscription.prelltv); + borrowAmount = bound(borrowAmount, minBorrow + 1, maxBorrow); + + deal(address(loanToken), SUPPLIER, borrowAmount); + vm.prank(SUPPLIER); + MORPHO.supply(market, uint128(borrowAmount), 0, SUPPLIER, hex""); + + deal(address(collateralToken), BORROWER, collateralAmount); vm.startPrank(BORROWER); + MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); + MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(liquidationProtection), true); + liquidationProtection.subscribe(); vm.startPrank(LIQUIDATOR); - - uint256 borrowAmount = 0.75 ether; - deal(address(loanToken), LIQUIDATOR, 2 * borrowAmount); + deal(address(loanToken), LIQUIDATOR, type(uint256).max); loanToken.approve(address(liquidationProtection), type(uint256).max); collateralToken.approve(address(liquidationProtection), type(uint256).max); Position memory position = MORPHO.position(market.id(), BORROWER); - liquidationProtection.preliquidate(BORROWER, 0, position.borrowShares, hex""); + Market memory m = MORPHO.market(market.id()); + + uint256 repayableShares = position.borrowShares.wMulDown(subscription.closeFactor); + uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( + subscription.preLiquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + vm.assume(seizedAssets > 0); + + liquidationProtection.preliquidate(BORROWER, 0, repayableShares, hex""); } } From ab62aa0e937d8b1e3727d8db887b3776be50b63f Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 13:59:22 +0200 Subject: [PATCH 096/182] style: rename Liquidate event --- src/LiquidationProtection.sol | 2 +- src/libraries/EventsLib.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 078a943..e93e420 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -112,7 +112,7 @@ contract LiquidationProtection is ILiquidationProtection { bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.Liquidate(borrower, marketId, msg.sender, repaidAssets, repaidShares, seizedAssets); + emit EventsLib.PreLiquidate(borrower, marketId, msg.sender, repaidAssets, repaidShares, seizedAssets); } function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index af7b40c..d2ad913 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -9,7 +9,7 @@ import {SubscriptionParams} from "../interfaces/ILiquidationProtection.sol"; /// @custom:contact security@morpho.org /// @notice Library exposing events. library EventsLib { - event Liquidate( + event PreLiquidate( address indexed borrower, Id indexed marketId, address indexed liquidator, From 61b0b673ec847642ee5a8f796ce2476e752acf9a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:01:07 +0200 Subject: [PATCH 097/182] feat: enable ir in foundry config --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index a20daa6..94c3f0f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,7 @@ src = "src" out = "out" libs = ["lib"] +via_ir = true [profile.default.rpc_endpoints] mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" From 1e076fd5e0cb0b42376ed9c73995a0e1968c2be2 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:05:28 +0200 Subject: [PATCH 098/182] style: rename preliquidate --- src/LiquidationProtection.sol | 2 +- src/interfaces/ILiquidationProtection.sol | 2 +- test/LiquidationProtectionTest.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e93e420..1e80366 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -80,7 +80,7 @@ contract LiquidationProtection is ILiquidationProtection { emit EventsLib.Unsubscribe(msg.sender); } - function preliquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { + function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); require( diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index 9d90a13..c584347 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -18,5 +18,5 @@ interface ILiquidationProtection { function unsubscribe() external; - function preliquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; + function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 1e9c5d0..96b9e91 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -53,7 +53,7 @@ contract LiquidationProtectionTest is BaseTest { vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.preliquidate(BORROWER, 0, 0, hex""); + liquidationProtection.preLiquidate(BORROWER, 0, 0, hex""); } function testSoftLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) @@ -100,6 +100,6 @@ contract LiquidationProtectionTest is BaseTest { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); - liquidationProtection.preliquidate(BORROWER, 0, repayableShares, hex""); + liquidationProtection.preLiquidate(BORROWER, 0, repayableShares, hex""); } } From cefddb0afc6b828aca12300bcf80c495cf69ea0a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:08:11 +0200 Subject: [PATCH 099/182] style: rename maxBorrow --- src/LiquidationProtection.sol | 4 ++-- test/LiquidationProtectionTest.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index 1e80366..b80925f 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -136,9 +136,9 @@ contract LiquidationProtection is ILiquidationProtection { uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 preLiquidationBorrowThreshold = + uint256 borrowThreshold = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(prelltv); - return preLiquidationBorrowThreshold < borrowed; + return borrowThreshold < borrowed; } } diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index 96b9e91..f0b4dcb 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -68,11 +68,11 @@ contract LiquidationProtectionTest is BaseTest { liquidationProtection = factory.createPreLiquidation(market, subscription); uint256 collateralPrice = IOracle(market.oracle).price(); - uint256 maxBorrow = + uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown(IOracle(market.oracle).price(), ORACLE_PRICE_SCALE).wMulDown(market.lltv); - uint256 minBorrow = + uint256 borrowPreLiquidationThreshold = collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(subscription.prelltv); - borrowAmount = bound(borrowAmount, minBorrow + 1, maxBorrow); + borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); deal(address(loanToken), SUPPLIER, borrowAmount); vm.prank(SUPPLIER); From 481b88c3383fed8ca77c3622da0c07f8deb85447 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:15:56 +0200 Subject: [PATCH 100/182] refactor: implement setSubscription --- src/LiquidationProtection.sol | 12 +++--------- src/interfaces/ILiquidationProtection.sol | 4 +--- src/libraries/EventsLib.sol | 4 +--- test/LiquidationProtectionTest.sol | 8 ++++---- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index b80925f..e12d0c6 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -68,16 +68,10 @@ contract LiquidationProtection is ILiquidationProtection { ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } - function subscribe() external { - subscriptions[msg.sender] = true; + function setSubscription(bool status) external { + subscriptions[msg.sender] = status; - emit EventsLib.Subscribe(msg.sender); - } - - function unsubscribe() external { - subscriptions[msg.sender] = false; - - emit EventsLib.Unsubscribe(msg.sender); + emit EventsLib.SetSubscription(msg.sender, status); } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index c584347..5a12e31 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -14,9 +14,7 @@ interface ILiquidationProtection { function subscriptions(address) external view returns (bool); - function subscribe() external; - - function unsubscribe() external; + function setSubscription(bool) external; function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index d2ad913..a9d054b 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -18,9 +18,7 @@ library EventsLib { uint256 seizedAssets ); - event Subscribe(address indexed borrower); - - event Unsubscribe(address indexed borrower); + event SetSubscription(address indexed borrower, bool status); event CreatePreLiquidation( address indexed subscription, MarketParams marketParams, SubscriptionParams subscriptionParams diff --git a/test/LiquidationProtectionTest.sol b/test/LiquidationProtectionTest.sol index f0b4dcb..650d557 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/LiquidationProtectionTest.sol @@ -37,7 +37,7 @@ contract LiquidationProtectionTest is BaseTest { liquidationProtection = factory.createPreLiquidation(market, subscription); vm.startPrank(BORROWER); - liquidationProtection.subscribe(); + liquidationProtection.setSubscription(true); assertTrue(liquidationProtection.subscriptions(BORROWER)); } @@ -47,8 +47,8 @@ contract LiquidationProtectionTest is BaseTest { vm.startPrank(BORROWER); - liquidationProtection.subscribe(); - liquidationProtection.unsubscribe(); + liquidationProtection.setSubscription(true); + liquidationProtection.setSubscription(false); vm.startPrank(LIQUIDATOR); @@ -84,7 +84,7 @@ contract LiquidationProtectionTest is BaseTest { MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(liquidationProtection), true); - liquidationProtection.subscribe(); + liquidationProtection.setSubscription(true); vm.startPrank(LIQUIDATOR); deal(address(loanToken), LIQUIDATOR, type(uint256).max); From 6adab2d2d5398b1c698be62c811ed41f9c9c6e90 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:50:50 +0200 Subject: [PATCH 101/182] feat: complete interface --- src/LiquidationProtection.sol | 8 ++++---- src/interfaces/ILiquidationProtection.sol | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol index e12d0c6..ad0d7bd 100644 --- a/src/LiquidationProtection.sol +++ b/src/LiquidationProtection.sol @@ -36,10 +36,10 @@ contract LiquidationProtection is ILiquidationProtection { uint256 public immutable preLiquidationIncentive; uint256 public immutable lltv; - address immutable collateralToken; - address immutable loanToken; - address immutable irm; - address immutable oracle; + address public immutable collateralToken; + address public immutable loanToken; + address public immutable irm; + address public immutable oracle; /* STORAGE */ mapping(address => bool) public subscriptions; diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/ILiquidationProtection.sol index 5a12e31..9c04404 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/ILiquidationProtection.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; -import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {Id, MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; struct SubscriptionParams { uint256 prelltv; @@ -11,6 +11,15 @@ struct SubscriptionParams { interface ILiquidationProtection { function MORPHO() external view returns (IMorpho); + function marketId() external view returns (Id); + function prelltv() external view returns (uint256); + function closeFactor() external view returns (uint256); + function preLiquidationIncentive() external view returns (uint256); + function lltv() external view returns (uint256); + function collateralToken() external view returns (address); + function loanToken() external view returns (address); + function irm() external view returns (address); + function oracle() external view returns (address); function subscriptions(address) external view returns (bool); From 25b80bfffc1bab42fe8c331a9949aca2c62b9250 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 15:51:28 +0200 Subject: [PATCH 102/182] test: improve factory tests --- test/LiquidationProtectionFactoryTest.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/LiquidationProtectionFactoryTest.sol b/test/LiquidationProtectionFactoryTest.sol index 1ce7732..c4872b9 100644 --- a/test/LiquidationProtectionFactoryTest.sol +++ b/test/LiquidationProtectionFactoryTest.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "./BaseTest.sol"; import {SubscriptionParams, ILiquidationProtection} from "../src/interfaces/ILiquidationProtection.sol"; import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; +import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; contract LiquidationProtectionFactoryTest is BaseTest { LiquidationProtectionFactory factory; @@ -12,10 +13,27 @@ contract LiquidationProtectionFactoryTest is BaseTest { super.setUp(); } + function testFactoryAddressZero() public { + vm.expectRevert(ErrorsLib.ZeroAddress.selector); + new LiquidationProtectionFactory(address(0)); + } + function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { vm.assume(subscription.prelltv < lltv); factory = new LiquidationProtectionFactory(address(MORPHO)); ILiquidationProtection liquidationProtection = factory.createPreLiquidation(market, subscription); + + assert(liquidationProtection.MORPHO() == MORPHO); + + assert(liquidationProtection.prelltv() == subscription.prelltv); + assert(liquidationProtection.closeFactor() == subscription.closeFactor); + assert(liquidationProtection.preLiquidationIncentive() == subscription.preLiquidationIncentive); + + assert(liquidationProtection.lltv() == market.lltv); + assert(liquidationProtection.collateralToken() == market.collateralToken); + assert(liquidationProtection.loanToken() == market.loanToken); + assert(liquidationProtection.irm() == market.irm); + assert(liquidationProtection.oracle() == market.oracle); } } From 16f7f464b12e9e49c97ccd325bb32528a4d4ba27 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:03:56 +0200 Subject: [PATCH 103/182] refactor: rename liquidationProtection --- ...ationProtection.sol => PreLiquidation.sol} | 6 +-- ...nFactory.sol => PreLiquidationFactory.sol} | 22 +++++----- ...tionProtection.sol => IPreLiquidation.sol} | 2 +- ...Factory.sol => IPreLiquidationFactory.sol} | 6 +-- src/libraries/EventsLib.sol | 2 +- test/LiquidationProtectionFactoryTest.sol | 39 ------------------ test/PreLiquidationFactoryTest.sol | 39 ++++++++++++++++++ ...tectionTest.sol => PreLiquidationTest.sol} | 40 +++++++++---------- 8 files changed, 77 insertions(+), 79 deletions(-) rename src/{LiquidationProtection.sol => PreLiquidation.sol} (96%) rename src/{LiquidationProtectionFactory.sol => PreLiquidationFactory.sol} (56%) rename src/interfaces/{ILiquidationProtection.sol => IPreLiquidation.sol} (96%) rename src/interfaces/{ILiquidationProtectionFactory.sol => IPreLiquidationFactory.sol} (64%) delete mode 100644 test/LiquidationProtectionFactoryTest.sol create mode 100644 test/PreLiquidationFactoryTest.sol rename test/{LiquidationProtectionTest.sol => PreLiquidationTest.sol} (70%) diff --git a/src/LiquidationProtection.sol b/src/PreLiquidation.sol similarity index 96% rename from src/LiquidationProtection.sol rename to src/PreLiquidation.sol index ad0d7bd..a9cfb1e 100644 --- a/src/LiquidationProtection.sol +++ b/src/PreLiquidation.sol @@ -13,13 +13,13 @@ 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 {ILiquidationProtection, SubscriptionParams} from "./interfaces/ILiquidationProtection.sol"; +import {IPreLiquidation, SubscriptionParams} from "./interfaces/IPreLiquidation.sol"; /// @title Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Liquidation Protection Contract for Morpho -contract LiquidationProtection is ILiquidationProtection { +/// @notice The Pre Liquidation Contract for Morpho +contract PreLiquidation is IPreLiquidation { using MarketParamsLib for MarketParams; using UtilsLib for uint256; using SharesMathLib for uint256; diff --git a/src/LiquidationProtectionFactory.sol b/src/PreLiquidationFactory.sol similarity index 56% rename from src/LiquidationProtectionFactory.sol rename to src/PreLiquidationFactory.sol index 9162a37..c0bf402 100644 --- a/src/LiquidationProtectionFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -2,22 +2,22 @@ pragma solidity 0.8.27; import {IMorpho, MarketParams} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {LiquidationProtection} from "./LiquidationProtection.sol"; -import {ILiquidationProtection, SubscriptionParams} from "./interfaces/ILiquidationProtection.sol"; +import {PreLiquidation} from "./PreLiquidation.sol"; +import {IPreLiquidation, SubscriptionParams} from "./interfaces/IPreLiquidation.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; -import {ILiquidationProtectionFactory} from "./interfaces/ILiquidationProtectionFactory.sol"; +import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; /// @title Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Liquidation Protection Factory Contract for Morpho +/// @notice The Pre Liquidation Factory Contract for Morpho -contract LiquidationProtectionFactory is ILiquidationProtectionFactory { +contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ IMorpho public immutable MORPHO; - mapping(bytes32 => ILiquidationProtection) subscriptions; + mapping(bytes32 => IPreLiquidation) subscriptions; constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); @@ -27,17 +27,15 @@ contract LiquidationProtectionFactory is ILiquidationProtectionFactory { function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external - returns (ILiquidationProtection liquidationProtection) + returns (IPreLiquidation preLiquidation) { bytes32 preLiquidationId = getPreLiquidationId(marketParams, subscriptionParams); require(address(subscriptions[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); - liquidationProtection = ILiquidationProtection( - address(new LiquidationProtection(marketParams, subscriptionParams, address(MORPHO))) - ); - subscriptions[preLiquidationId] = liquidationProtection; + preLiquidation = IPreLiquidation(address(new PreLiquidation(marketParams, subscriptionParams, address(MORPHO)))); + subscriptions[preLiquidationId] = preLiquidation; - emit EventsLib.CreatePreLiquidation(address(liquidationProtection), marketParams, subscriptionParams); + emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, subscriptionParams); } function getPreLiquidationId(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) diff --git a/src/interfaces/ILiquidationProtection.sol b/src/interfaces/IPreLiquidation.sol similarity index 96% rename from src/interfaces/ILiquidationProtection.sol rename to src/interfaces/IPreLiquidation.sol index 9c04404..64a57c1 100644 --- a/src/interfaces/ILiquidationProtection.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -9,7 +9,7 @@ struct SubscriptionParams { uint256 preLiquidationIncentive; } -interface ILiquidationProtection { +interface IPreLiquidation { function MORPHO() external view returns (IMorpho); function marketId() external view returns (Id); function prelltv() external view returns (uint256); diff --git a/src/interfaces/ILiquidationProtectionFactory.sol b/src/interfaces/IPreLiquidationFactory.sol similarity index 64% rename from src/interfaces/ILiquidationProtectionFactory.sol rename to src/interfaces/IPreLiquidationFactory.sol index a5f1c56..7444dae 100644 --- a/src/interfaces/ILiquidationProtectionFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -2,12 +2,12 @@ pragma solidity >= 0.5.0; import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {ILiquidationProtection, SubscriptionParams} from "./ILiquidationProtection.sol"; +import {IPreLiquidation, SubscriptionParams} from "./IPreLiquidation.sol"; -interface ILiquidationProtectionFactory { +interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external - returns (ILiquidationProtection liquidationProtection); + returns (IPreLiquidation preLiquidation); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index a9d054b..5026cb4 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {SubscriptionParams} from "../interfaces/ILiquidationProtection.sol"; +import {SubscriptionParams} from "../interfaces/IPreLiquidation.sol"; /// @title EventsLib /// @author Morpho Labs diff --git a/test/LiquidationProtectionFactoryTest.sol b/test/LiquidationProtectionFactoryTest.sol deleted file mode 100644 index c4872b9..0000000 --- a/test/LiquidationProtectionFactoryTest.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import "./BaseTest.sol"; -import {SubscriptionParams, ILiquidationProtection} from "../src/interfaces/ILiquidationProtection.sol"; -import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; -import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; - -contract LiquidationProtectionFactoryTest is BaseTest { - LiquidationProtectionFactory factory; - - function setUp() public override { - super.setUp(); - } - - function testFactoryAddressZero() public { - vm.expectRevert(ErrorsLib.ZeroAddress.selector); - new LiquidationProtectionFactory(address(0)); - } - - function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { - vm.assume(subscription.prelltv < lltv); - - factory = new LiquidationProtectionFactory(address(MORPHO)); - ILiquidationProtection liquidationProtection = factory.createPreLiquidation(market, subscription); - - assert(liquidationProtection.MORPHO() == MORPHO); - - assert(liquidationProtection.prelltv() == subscription.prelltv); - assert(liquidationProtection.closeFactor() == subscription.closeFactor); - assert(liquidationProtection.preLiquidationIncentive() == subscription.preLiquidationIncentive); - - assert(liquidationProtection.lltv() == market.lltv); - assert(liquidationProtection.collateralToken() == market.collateralToken); - assert(liquidationProtection.loanToken() == market.loanToken); - assert(liquidationProtection.irm() == market.irm); - assert(liquidationProtection.oracle() == market.oracle); - } -} diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol new file mode 100644 index 0000000..f5edda8 --- /dev/null +++ b/test/PreLiquidationFactoryTest.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import "./BaseTest.sol"; +import {SubscriptionParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; +import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; +import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; + +contract PreLiquidationFactoryTest is BaseTest { + PreLiquidationFactory factory; + + function setUp() public override { + super.setUp(); + } + + function testFactoryAddressZero() public { + vm.expectRevert(ErrorsLib.ZeroAddress.selector); + new PreLiquidationFactory(address(0)); + } + + function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { + vm.assume(subscription.prelltv < lltv); + + factory = new PreLiquidationFactory(address(MORPHO)); + IPreLiquidation preLiquidation = factory.createPreLiquidation(market, subscription); + + assert(preLiquidation.MORPHO() == MORPHO); + + assert(preLiquidation.prelltv() == subscription.prelltv); + assert(preLiquidation.closeFactor() == subscription.closeFactor); + assert(preLiquidation.preLiquidationIncentive() == subscription.preLiquidationIncentive); + + assert(preLiquidation.lltv() == market.lltv); + assert(preLiquidation.collateralToken() == market.collateralToken); + assert(preLiquidation.loanToken() == market.loanToken); + assert(preLiquidation.irm() == market.irm); + assert(preLiquidation.oracle() == market.oracle); + } +} diff --git a/test/LiquidationProtectionTest.sol b/test/PreLiquidationTest.sol similarity index 70% rename from test/LiquidationProtectionTest.sol rename to test/PreLiquidationTest.sol index 650d557..6fafb13 100644 --- a/test/LiquidationProtectionTest.sol +++ b/test/PreLiquidationTest.sol @@ -6,10 +6,10 @@ import "../lib/forge-std/src/console.sol"; import "./BaseTest.sol"; -import {ILiquidationProtection, SubscriptionParams} from "../src/interfaces/ILiquidationProtection.sol"; +import {IPreLiquidation, SubscriptionParams} from "../src/interfaces/IPreLiquidation.sol"; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; -import {LiquidationProtection} from "../src/LiquidationProtection.sol"; -import {LiquidationProtectionFactory} from "../src/LiquidationProtectionFactory.sol"; +import {PreLiquidation} from "../src/PreLiquidation.sol"; +import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ERC20} from "../lib/solmate/src/tokens/ERC20.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; @@ -17,43 +17,43 @@ import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib. import {MathLib, WAD} from "../lib/morpho-blue/src/libraries/MathLib.sol"; import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol"; -contract LiquidationProtectionTest is BaseTest { +contract PreLiquidationTest is BaseTest { using MarketParamsLib for MarketParams; using SharesMathLib for uint256; using MathLib for uint256; using MathLib for uint128; - LiquidationProtectionFactory internal factory; - ILiquidationProtection internal liquidationProtection; + PreLiquidationFactory internal factory; + IPreLiquidation internal preLiquidation; function setUp() public override { super.setUp(); - factory = new LiquidationProtectionFactory(address(MORPHO)); + factory = new PreLiquidationFactory(address(MORPHO)); } function testSetSubscription(SubscriptionParams calldata subscription) public virtual { vm.assume(subscription.prelltv < market.lltv); - liquidationProtection = factory.createPreLiquidation(market, subscription); + preLiquidation = factory.createPreLiquidation(market, subscription); vm.startPrank(BORROWER); - liquidationProtection.setSubscription(true); - assertTrue(liquidationProtection.subscriptions(BORROWER)); + preLiquidation.setSubscription(true); + assertTrue(preLiquidation.subscriptions(BORROWER)); } function testRemoveSubscription(SubscriptionParams calldata subscription) public virtual { vm.assume(subscription.prelltv < market.lltv); - liquidationProtection = factory.createPreLiquidation(market, subscription); + preLiquidation = factory.createPreLiquidation(market, subscription); vm.startPrank(BORROWER); - liquidationProtection.setSubscription(true); - liquidationProtection.setSubscription(false); + preLiquidation.setSubscription(true); + preLiquidation.setSubscription(false); vm.startPrank(LIQUIDATOR); vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - liquidationProtection.preLiquidate(BORROWER, 0, 0, hex""); + preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); } function testSoftLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) @@ -65,7 +65,7 @@ contract LiquidationProtectionTest is BaseTest { subscription.preLiquidationIncentive = bound(subscription.preLiquidationIncentive, 1, WAD / 10); collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); - liquidationProtection = factory.createPreLiquidation(market, subscription); + preLiquidation = factory.createPreLiquidation(market, subscription); uint256 collateralPrice = IOracle(market.oracle).price(); uint256 borrowLiquidationThreshold = @@ -82,14 +82,14 @@ contract LiquidationProtectionTest is BaseTest { vm.startPrank(BORROWER); MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); - MORPHO.setAuthorization(address(liquidationProtection), true); + MORPHO.setAuthorization(address(preLiquidation), true); - liquidationProtection.setSubscription(true); + preLiquidation.setSubscription(true); vm.startPrank(LIQUIDATOR); deal(address(loanToken), LIQUIDATOR, type(uint256).max); - loanToken.approve(address(liquidationProtection), type(uint256).max); - collateralToken.approve(address(liquidationProtection), type(uint256).max); + loanToken.approve(address(preLiquidation), type(uint256).max); + collateralToken.approve(address(preLiquidation), type(uint256).max); Position memory position = MORPHO.position(market.id(), BORROWER); Market memory m = MORPHO.market(market.id()); @@ -100,6 +100,6 @@ contract LiquidationProtectionTest is BaseTest { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); - liquidationProtection.preLiquidate(BORROWER, 0, repayableShares, hex""); + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); } } From 404b93114f9ca641365db371fb6262cf27541347 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:17:49 +0200 Subject: [PATCH 104/182] refactor: rename test function --- test/PreLiquidationTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 6fafb13..0b1465a 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -56,7 +56,7 @@ contract PreLiquidationTest is BaseTest { preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); } - function testSoftLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) + function testPreLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) public virtual { From ea06a08713ac03df4266a112926c68f73c9b7b6a Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:22:16 +0200 Subject: [PATCH 105/182] doc: remove comment --- test/BaseTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BaseTest.sol b/test/BaseTest.sol index e0e0aac..9daa4af 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -51,7 +51,7 @@ contract BaseTest is Test { collateralToken: address(collateralToken), oracle: address(oracle), irm: address(irm), - lltv: lltv // 80 % + lltv: lltv }); MORPHO.createMarket(market); From 57318a86d156ca195d9ec4555f085f3920599b26 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:50:08 +0200 Subject: [PATCH 106/182] test: improve factory test --- src/PreLiquidationFactory.sol | 2 +- src/interfaces/IPreLiquidationFactory.sol | 2 ++ test/PreLiquidationFactoryTest.sol | 14 +++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index c0bf402..6dba002 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -17,7 +17,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ IMorpho public immutable MORPHO; - mapping(bytes32 => IPreLiquidation) subscriptions; + mapping(bytes32 => IPreLiquidation) public subscriptions; constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 7444dae..e0ca4be 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -7,6 +7,8 @@ import {IPreLiquidation, SubscriptionParams} from "./IPreLiquidation.sol"; interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); + function subscriptions(bytes32) external view returns (IPreLiquidation); + function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external returns (IPreLiquidation preLiquidation); diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index f5edda8..7a285fc 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -18,7 +18,7 @@ contract PreLiquidationFactoryTest is BaseTest { new PreLiquidationFactory(address(0)); } - function testCreatePreLiquidation(SubscriptionParams calldata subscription) public { + function testCreatePreLiquidation(SubscriptionParams memory subscription) public { vm.assume(subscription.prelltv < lltv); factory = new PreLiquidationFactory(address(MORPHO)); @@ -35,5 +35,17 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.loanToken() == market.loanToken); assert(preLiquidation.irm() == market.irm); assert(preLiquidation.oracle() == market.oracle); + + MarketParams memory _market = market; + bytes32 subscriptionId = getPreLiquidationId(_market, subscription); + assert(factory.subscriptions(subscriptionId) == preLiquidation); + } + + function getPreLiquidationId(MarketParams memory marketParams, SubscriptionParams memory subscriptionParams) + internal + pure + returns (bytes32) + { + return keccak256(abi.encode(marketParams, subscriptionParams)); } } From 31a0c15065b5a31227600da948d0cb222973ab4c Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:51:21 +0200 Subject: [PATCH 107/182] refactor: rename factory mapping --- src/PreLiquidationFactory.sol | 6 +++--- src/interfaces/IPreLiquidationFactory.sol | 2 +- test/PreLiquidationFactoryTest.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 6dba002..6d03908 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -17,7 +17,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ IMorpho public immutable MORPHO; - mapping(bytes32 => IPreLiquidation) public subscriptions; + mapping(bytes32 => IPreLiquidation) public preliquidations; constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); @@ -30,10 +30,10 @@ contract PreLiquidationFactory is IPreLiquidationFactory { returns (IPreLiquidation preLiquidation) { bytes32 preLiquidationId = getPreLiquidationId(marketParams, subscriptionParams); - require(address(subscriptions[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); + require(address(preliquidations[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); preLiquidation = IPreLiquidation(address(new PreLiquidation(marketParams, subscriptionParams, address(MORPHO)))); - subscriptions[preLiquidationId] = preLiquidation; + preliquidations[preLiquidationId] = preLiquidation; emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, subscriptionParams); } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index e0ca4be..d9dbf88 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -7,7 +7,7 @@ import {IPreLiquidation, SubscriptionParams} from "./IPreLiquidation.sol"; interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); - function subscriptions(bytes32) external view returns (IPreLiquidation); + function preliquidations(bytes32) external view returns (IPreLiquidation); function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 7a285fc..0b88f6a 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -38,7 +38,7 @@ contract PreLiquidationFactoryTest is BaseTest { MarketParams memory _market = market; bytes32 subscriptionId = getPreLiquidationId(_market, subscription); - assert(factory.subscriptions(subscriptionId) == preLiquidation); + assert(factory.preliquidations(subscriptionId) == preLiquidation); } function getPreLiquidationId(MarketParams memory marketParams, SubscriptionParams memory subscriptionParams) From ac626220efdecb581451c4c337a62ffa455ba3a4 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 16:59:07 +0200 Subject: [PATCH 108/182] refactor: small improvement --- src/PreLiquidation.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index a9cfb1e..16a084f 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -31,15 +31,17 @@ contract PreLiquidation is IPreLiquidation { IMorpho public immutable MORPHO; Id public immutable marketId; - uint256 public immutable prelltv; - uint256 public immutable closeFactor; - uint256 public immutable preLiquidationIncentive; - uint256 public immutable lltv; - + // Market parameters address public immutable collateralToken; address public immutable loanToken; address public immutable irm; address public immutable oracle; + uint256 public immutable lltv; + + // Subscription parameters + uint256 public immutable prelltv; + uint256 public immutable closeFactor; + uint256 public immutable preLiquidationIncentive; /* STORAGE */ mapping(address => bool) public subscriptions; From 612cd9971205aaac2bf910efd9931dd7d185c0bc Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 17:06:09 +0200 Subject: [PATCH 109/182] refactor: rename SetSubscribe event --- src/PreLiquidation.sol | 20 ++++++++++---------- src/libraries/EventsLib.sol | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 16a084f..27fd457 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -32,10 +32,10 @@ contract PreLiquidation is IPreLiquidation { Id public immutable marketId; // Market parameters - address public immutable collateralToken; address public immutable loanToken; - address public immutable irm; + address public immutable collateralToken; address public immutable oracle; + address public immutable irm; uint256 public immutable lltv; // Subscription parameters @@ -52,17 +52,17 @@ contract PreLiquidation is IPreLiquidation { constructor(MarketParams memory _marketParams, SubscriptionParams memory _subscriptionParams, address morpho) { MORPHO = IMorpho(morpho); + loanToken = _marketParams.loanToken; + collateralToken = _marketParams.collateralToken; + oracle = _marketParams.oracle; + irm = _marketParams.irm; + lltv = _marketParams.lltv; + marketId = _marketParams.id(); + prelltv = _subscriptionParams.prelltv; closeFactor = _subscriptionParams.closeFactor; preLiquidationIncentive = _subscriptionParams.preLiquidationIncentive; - lltv = _marketParams.lltv; - collateralToken = _marketParams.collateralToken; - loanToken = _marketParams.loanToken; - irm = _marketParams.irm; - oracle = _marketParams.oracle; - - marketId = _marketParams.id(); // should close factor be lower than 100% ? // should there be a max liquidation incentive ? require(prelltv < lltv, ErrorsLib.PreLltvTooHigh(prelltv, lltv)); @@ -73,7 +73,7 @@ contract PreLiquidation is IPreLiquidation { function setSubscription(bool status) external { subscriptions[msg.sender] = status; - emit EventsLib.SetSubscription(msg.sender, status); + emit EventsLib.SetIsSubscribed(msg.sender, status); } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 5026cb4..54db689 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -18,7 +18,7 @@ library EventsLib { uint256 seizedAssets ); - event SetSubscription(address indexed borrower, bool status); + event SetIsSubscribed(address indexed borrower, bool status); event CreatePreLiquidation( address indexed subscription, MarketParams marketParams, SubscriptionParams subscriptionParams From 69865de5d2e2f656e99848890cfb4eaf800e4a49 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 17:15:01 +0200 Subject: [PATCH 110/182] doc: natspec --- src/PreLiquidationFactory.sol | 15 ++++++++++++++- src/interfaces/IPreLiquidationFactory.sol | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 6d03908..918c359 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -12,19 +12,30 @@ import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Pre Liquidation Factory Contract for Morpho - contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ + + /// @inheritdoc IPreLiquidationFactory IMorpho public immutable MORPHO; + /* STORAGE */ + + /// @inheritdoc IPreLiquidationFactory mapping(bytes32 => IPreLiquidation) public preliquidations; + /* CONSTRUCTOR */ + + /// @dev Initializes the contract. + /// @param morpho The address of the Morpho contract. constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); MORPHO = IMorpho(morpho); } + /* EXTERNAL */ + + /// @inheritdoc IPreLiquidationFactory function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external returns (IPreLiquidation preLiquidation) @@ -38,6 +49,8 @@ contract PreLiquidationFactory is IPreLiquidationFactory { emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, subscriptionParams); } + /* INTERNAL */ + function getPreLiquidationId(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) internal pure diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index d9dbf88..8148a0a 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -4,11 +4,20 @@ pragma solidity >= 0.5.0; import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IPreLiquidation, SubscriptionParams} from "./IPreLiquidation.sol"; +/// @title IPreLiquidationFactory +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Interface of PreLiquidation's factory. interface IPreLiquidationFactory { + /// @notice The address of the Morpho contract. function MORPHO() external view returns (IMorpho); + /// @notice The contract address created for a specific preLiquidationId. function preliquidations(bytes32) external view returns (IPreLiquidation); + /// @notice Creates a PreLiquidation contract. + /// @param marketParams The Morpho market for PreLiquidations. + /// @param subscriptionParams The PreLiquidation params for the PreLiquidation contract. function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external returns (IPreLiquidation preLiquidation); From fd3af59becd84cd52d41b17d719f1b073ef26f0c Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 17:22:18 +0200 Subject: [PATCH 111/182] refactor: rename SubscriptionParams => PreLiquidationParams --- src/PreLiquidation.sol | 10 +++--- src/PreLiquidationFactory.sol | 21 +++++++------ src/interfaces/IPreLiquidation.sol | 2 +- src/interfaces/IPreLiquidationFactory.sol | 11 ++++--- src/libraries/EventsLib.sol | 4 +-- test/PreLiquidationFactoryTest.sol | 22 +++++++------- test/PreLiquidationTest.sol | 37 ++++++++++++----------- 7 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 27fd457..ccd147f 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -13,7 +13,7 @@ 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, SubscriptionParams} from "./interfaces/IPreLiquidation.sol"; +import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidation.sol"; /// @title Morpho /// @author Morpho Labs @@ -49,7 +49,7 @@ contract PreLiquidation is IPreLiquidation { // TODO EIP-712 signature // TODO authorize this contract on morpho - constructor(MarketParams memory _marketParams, SubscriptionParams memory _subscriptionParams, address morpho) { + constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { MORPHO = IMorpho(morpho); loanToken = _marketParams.loanToken; @@ -59,9 +59,9 @@ contract PreLiquidation is IPreLiquidation { lltv = _marketParams.lltv; marketId = _marketParams.id(); - prelltv = _subscriptionParams.prelltv; - closeFactor = _subscriptionParams.closeFactor; - preLiquidationIncentive = _subscriptionParams.preLiquidationIncentive; + prelltv = _preLiquidationParams.prelltv; + closeFactor = _preLiquidationParams.closeFactor; + preLiquidationIncentive = _preLiquidationParams.preLiquidationIncentive; // should close factor be lower than 100% ? // should there be a max liquidation incentive ? diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 918c359..3d09971 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.27; import {IMorpho, MarketParams} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {PreLiquidation} from "./PreLiquidation.sol"; -import {IPreLiquidation, SubscriptionParams} from "./interfaces/IPreLiquidation.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"; @@ -36,26 +36,27 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* EXTERNAL */ /// @inheritdoc IPreLiquidationFactory - function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) - external - returns (IPreLiquidation preLiquidation) - { - bytes32 preLiquidationId = getPreLiquidationId(marketParams, subscriptionParams); + function createPreLiquidation( + MarketParams calldata marketParams, + PreLiquidationParams calldata preLiquidationParams + ) external returns (IPreLiquidation preLiquidation) { + bytes32 preLiquidationId = getPreLiquidationId(marketParams, preLiquidationParams); require(address(preliquidations[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); - preLiquidation = IPreLiquidation(address(new PreLiquidation(marketParams, subscriptionParams, address(MORPHO)))); + preLiquidation = + IPreLiquidation(address(new PreLiquidation(marketParams, preLiquidationParams, address(MORPHO)))); preliquidations[preLiquidationId] = preLiquidation; - emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, subscriptionParams); + emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, preLiquidationParams); } /* INTERNAL */ - function getPreLiquidationId(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) + function getPreLiquidationId(MarketParams calldata marketParams, PreLiquidationParams calldata preLiquidationParams) internal pure returns (bytes32) { - return keccak256(abi.encode(marketParams, subscriptionParams)); + return keccak256(abi.encode(marketParams, preLiquidationParams)); } } diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 64a57c1..7b4a7ae 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -3,7 +3,7 @@ pragma solidity >= 0.5.0; import {Id, MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -struct SubscriptionParams { +struct PreLiquidationParams { uint256 prelltv; uint256 closeFactor; uint256 preLiquidationIncentive; diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 8148a0a..586a449 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -2,7 +2,7 @@ pragma solidity >= 0.5.0; import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {IPreLiquidation, SubscriptionParams} from "./IPreLiquidation.sol"; +import {IPreLiquidation, PreLiquidationParams} from "./IPreLiquidation.sol"; /// @title IPreLiquidationFactory /// @author Morpho Labs @@ -17,8 +17,9 @@ interface IPreLiquidationFactory { /// @notice Creates a PreLiquidation contract. /// @param marketParams The Morpho market for PreLiquidations. - /// @param subscriptionParams The PreLiquidation params for the PreLiquidation contract. - function createPreLiquidation(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) - external - returns (IPreLiquidation preLiquidation); + /// @param preLiquidationParams The PreLiquidation params for the PreLiquidation contract. + function createPreLiquidation( + MarketParams calldata marketParams, + PreLiquidationParams calldata preLiquidationParams + ) external returns (IPreLiquidation preLiquidation); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 54db689..668ea21 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.27; import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -import {SubscriptionParams} from "../interfaces/IPreLiquidation.sol"; +import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; /// @title EventsLib /// @author Morpho Labs @@ -21,6 +21,6 @@ library EventsLib { event SetIsSubscribed(address indexed borrower, bool status); event CreatePreLiquidation( - address indexed subscription, MarketParams marketParams, SubscriptionParams subscriptionParams + address indexed preLiquidationContract, MarketParams marketParams, PreLiquidationParams preLiquidationParams ); } diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 0b88f6a..1cef61b 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "./BaseTest.sol"; -import {SubscriptionParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; +import {PreLiquidationParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; @@ -18,17 +18,17 @@ contract PreLiquidationFactoryTest is BaseTest { new PreLiquidationFactory(address(0)); } - function testCreatePreLiquidation(SubscriptionParams memory subscription) public { - vm.assume(subscription.prelltv < lltv); + function testCreatePreLiquidation(PreLiquidationParams memory preLiquidationParams) public { + vm.assume(preLiquidationParams.prelltv < lltv); factory = new PreLiquidationFactory(address(MORPHO)); - IPreLiquidation preLiquidation = factory.createPreLiquidation(market, subscription); + IPreLiquidation preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); assert(preLiquidation.MORPHO() == MORPHO); - assert(preLiquidation.prelltv() == subscription.prelltv); - assert(preLiquidation.closeFactor() == subscription.closeFactor); - assert(preLiquidation.preLiquidationIncentive() == subscription.preLiquidationIncentive); + assert(preLiquidation.prelltv() == preLiquidationParams.prelltv); + assert(preLiquidation.closeFactor() == preLiquidationParams.closeFactor); + assert(preLiquidation.preLiquidationIncentive() == preLiquidationParams.preLiquidationIncentive); assert(preLiquidation.lltv() == market.lltv); assert(preLiquidation.collateralToken() == market.collateralToken); @@ -37,15 +37,15 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.oracle() == market.oracle); MarketParams memory _market = market; - bytes32 subscriptionId = getPreLiquidationId(_market, subscription); - assert(factory.preliquidations(subscriptionId) == preLiquidation); + bytes32 preLiquidationId = getPreLiquidationId(_market, preLiquidationParams); + assert(factory.preliquidations(preLiquidationId) == preLiquidation); } - function getPreLiquidationId(MarketParams memory marketParams, SubscriptionParams memory subscriptionParams) + function getPreLiquidationId(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) internal pure returns (bytes32) { - return keccak256(abi.encode(marketParams, subscriptionParams)); + return keccak256(abi.encode(marketParams, preLiquidationParams)); } } diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 0b1465a..15c80e6 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -6,7 +6,7 @@ import "../lib/forge-std/src/console.sol"; import "./BaseTest.sol"; -import {IPreLiquidation, SubscriptionParams} from "../src/interfaces/IPreLiquidation.sol"; +import {IPreLiquidation, PreLiquidationParams} from "../src/interfaces/IPreLiquidation.sol"; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {PreLiquidation} from "../src/PreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; @@ -32,18 +32,18 @@ contract PreLiquidationTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); } - function testSetSubscription(SubscriptionParams calldata subscription) public virtual { - vm.assume(subscription.prelltv < market.lltv); - preLiquidation = factory.createPreLiquidation(market, subscription); + function testSetSubscription(PreLiquidationParams calldata preLiquidationParams) public virtual { + vm.assume(preLiquidationParams.prelltv < market.lltv); + preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); vm.startPrank(BORROWER); preLiquidation.setSubscription(true); assertTrue(preLiquidation.subscriptions(BORROWER)); } - function testRemoveSubscription(SubscriptionParams calldata subscription) public virtual { - vm.assume(subscription.prelltv < market.lltv); - preLiquidation = factory.createPreLiquidation(market, subscription); + function testRemoveSubscription(PreLiquidationParams calldata preLiquidationParams) public virtual { + vm.assume(preLiquidationParams.prelltv < market.lltv); + preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); vm.startPrank(BORROWER); @@ -56,22 +56,23 @@ contract PreLiquidationTest is BaseTest { preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); } - function testPreLiquidation(SubscriptionParams memory subscription, uint256 collateralAmount, uint256 borrowAmount) - public - virtual - { - subscription.prelltv = bound(subscription.prelltv, WAD / 100, market.lltv - 1); - subscription.closeFactor = bound(subscription.closeFactor, WAD / 100, WAD - 1); - subscription.preLiquidationIncentive = bound(subscription.preLiquidationIncentive, 1, WAD / 10); + function testPreLiquidation( + PreLiquidationParams memory preLiquidationParams, + uint256 collateralAmount, + uint256 borrowAmount + ) public virtual { + preLiquidationParams.prelltv = bound(preLiquidationParams.prelltv, WAD / 100, market.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); - preLiquidation = factory.createPreLiquidation(market, subscription); + preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); uint256 collateralPrice = IOracle(market.oracle).price(); uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown(IOracle(market.oracle).price(), ORACLE_PRICE_SCALE).wMulDown(market.lltv); uint256 borrowPreLiquidationThreshold = - collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(subscription.prelltv); + collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.prelltv); borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); deal(address(loanToken), SUPPLIER, borrowAmount); @@ -94,9 +95,9 @@ contract PreLiquidationTest is BaseTest { Position memory position = MORPHO.position(market.id(), BORROWER); Market memory m = MORPHO.market(market.id()); - uint256 repayableShares = position.borrowShares.wMulDown(subscription.closeFactor); + uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( - subscription.preLiquidationIncentive + preLiquidationParams.preLiquidationIncentive ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); From 17aa460320e77ed2560b83d4c280b4c1486b04ef Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 18:17:30 +0200 Subject: [PATCH 112/182] feat: remove subscription from PreLiquidation --- src/PreLiquidation.sol | 11 ----------- src/interfaces/IPreLiquidation.sol | 4 ---- src/libraries/ErrorsLib.sol | 2 -- test/PreLiquidationTest.sol | 26 -------------------------- 4 files changed, 43 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index ccd147f..345fafb 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -43,9 +43,6 @@ contract PreLiquidation is IPreLiquidation { uint256 public immutable closeFactor; uint256 public immutable preLiquidationIncentive; - /* STORAGE */ - mapping(address => bool) public subscriptions; - // TODO EIP-712 signature // TODO authorize this contract on morpho @@ -70,15 +67,7 @@ contract PreLiquidation is IPreLiquidation { ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } - function setSubscription(bool status) external { - subscriptions[msg.sender] = status; - - emit EventsLib.SetIsSubscribed(msg.sender, status); - } - function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { - require(subscriptions[borrower], ErrorsLib.InvalidSubscription()); - require( UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) ); diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 7b4a7ae..e658b7b 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -21,9 +21,5 @@ interface IPreLiquidation { function irm() external view returns (address); function oracle() external view returns (address); - function subscriptions(address) external view returns (bool); - - function setSubscription(bool) external; - function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 6953ead..b419f33 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,8 +8,6 @@ pragma solidity 0.8.27; library ErrorsLib { error PreLltvTooHigh(uint256 prelltv, uint256 lltv); - error InvalidSubscription(); - error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); error NotPreLiquidatablePosition(); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 15c80e6..35f043c 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -32,30 +32,6 @@ contract PreLiquidationTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); } - function testSetSubscription(PreLiquidationParams calldata preLiquidationParams) public virtual { - vm.assume(preLiquidationParams.prelltv < market.lltv); - preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); - - vm.startPrank(BORROWER); - preLiquidation.setSubscription(true); - assertTrue(preLiquidation.subscriptions(BORROWER)); - } - - function testRemoveSubscription(PreLiquidationParams calldata preLiquidationParams) public virtual { - vm.assume(preLiquidationParams.prelltv < market.lltv); - preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); - - vm.startPrank(BORROWER); - - preLiquidation.setSubscription(true); - preLiquidation.setSubscription(false); - - vm.startPrank(LIQUIDATOR); - - vm.expectRevert(ErrorsLib.InvalidSubscription.selector); - preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); - } - function testPreLiquidation( PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount, @@ -85,8 +61,6 @@ contract PreLiquidationTest is BaseTest { MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(preLiquidation), true); - preLiquidation.setSubscription(true); - vm.startPrank(LIQUIDATOR); deal(address(loanToken), LIQUIDATOR, type(uint256).max); loanToken.approve(address(preLiquidation), type(uint256).max); From 76cba25763dbb09ecbddfe1c6ac851b65ebf1719 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 13 Sep 2024 18:18:12 +0200 Subject: [PATCH 113/182] refactor: remove unused event --- src/libraries/EventsLib.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 668ea21..2c47cfa 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -18,8 +18,6 @@ library EventsLib { uint256 seizedAssets ); - event SetIsSubscribed(address indexed borrower, bool status); - event CreatePreLiquidation( address indexed preLiquidationContract, MarketParams marketParams, PreLiquidationParams preLiquidationParams ); From f3ceea24a6ac4ceeb7b56175866d475142de742e Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 16 Sep 2024 10:04:48 +0200 Subject: [PATCH 114/182] refactor: renaming --- src/PreLiquidation.sol | 10 +++++----- src/PreLiquidationFactory.sol | 7 +++---- src/interfaces/IPreLiquidation.sol | 13 ++++++++----- src/interfaces/IPreLiquidationFactory.sol | 2 +- src/libraries/ErrorsLib.sol | 2 +- src/libraries/EventsLib.sol | 2 +- test/PreLiquidationFactoryTest.sol | 6 +++--- test/PreLiquidationTest.sol | 4 ++-- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 345fafb..7e74e3a 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -38,8 +38,8 @@ contract PreLiquidation is IPreLiquidation { address public immutable irm; uint256 public immutable lltv; - // Subscription parameters - uint256 public immutable prelltv; + // Pre-liquidation parameters + uint256 public immutable preLltv; uint256 public immutable closeFactor; uint256 public immutable preLiquidationIncentive; @@ -56,13 +56,13 @@ contract PreLiquidation is IPreLiquidation { lltv = _marketParams.lltv; marketId = _marketParams.id(); - prelltv = _preLiquidationParams.prelltv; + preLltv = _preLiquidationParams.preLltv; closeFactor = _preLiquidationParams.closeFactor; preLiquidationIncentive = _preLiquidationParams.preLiquidationIncentive; // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - require(prelltv < lltv, ErrorsLib.PreLltvTooHigh(prelltv, lltv)); + require(preLltv < lltv, ErrorsLib.PreLltvTooHigh(preLltv, lltv)); ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } @@ -122,7 +122,7 @@ contract PreLiquidation is IPreLiquidation { uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); uint256 borrowThreshold = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(prelltv); + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLltv); return borrowThreshold < borrowed; } diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 3d09971..fea402e 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -21,11 +21,10 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* STORAGE */ /// @inheritdoc IPreLiquidationFactory - mapping(bytes32 => IPreLiquidation) public preliquidations; + mapping(bytes32 => IPreLiquidation) public preLiquidations; /* CONSTRUCTOR */ - /// @dev Initializes the contract. /// @param morpho The address of the Morpho contract. constructor(address morpho) { require(morpho != address(0), ErrorsLib.ZeroAddress()); @@ -41,11 +40,11 @@ contract PreLiquidationFactory is IPreLiquidationFactory { PreLiquidationParams calldata preLiquidationParams ) external returns (IPreLiquidation preLiquidation) { bytes32 preLiquidationId = getPreLiquidationId(marketParams, preLiquidationParams); - require(address(preliquidations[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); + require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); preLiquidation = IPreLiquidation(address(new PreLiquidation(marketParams, preLiquidationParams, address(MORPHO)))); - preliquidations[preLiquidationId] = preLiquidation; + preLiquidations[preLiquidationId] = preLiquidation; emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, preLiquidationParams); } diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index e658b7b..55df262 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -4,22 +4,25 @@ pragma solidity >= 0.5.0; import {Id, MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; struct PreLiquidationParams { - uint256 prelltv; + uint256 preLltv; uint256 closeFactor; uint256 preLiquidationIncentive; } interface IPreLiquidation { function MORPHO() external view returns (IMorpho); + function marketId() external view returns (Id); - function prelltv() external view returns (uint256); + + function preLltv() external view returns (uint256); function closeFactor() external view returns (uint256); function preLiquidationIncentive() external view returns (uint256); - function lltv() external view returns (uint256); - function collateralToken() external view returns (address); + function loanToken() external view returns (address); - function irm() external view returns (address); + function collateralToken() external view returns (address); function oracle() external view returns (address); + function irm() external view returns (address); + function lltv() external view returns (uint256); function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 586a449..6fa1a09 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -13,7 +13,7 @@ interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); /// @notice The contract address created for a specific preLiquidationId. - function preliquidations(bytes32) external view returns (IPreLiquidation); + function preLiquidations(bytes32) external view returns (IPreLiquidation); /// @notice Creates a PreLiquidation contract. /// @param marketParams The Morpho market for PreLiquidations. diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index b419f33..ea7af97 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -6,7 +6,7 @@ pragma solidity 0.8.27; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { - error PreLltvTooHigh(uint256 prelltv, uint256 lltv); + error PreLltvTooHigh(uint256 preLltv, uint256 lltv); error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 2c47cfa..0408d4d 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -19,6 +19,6 @@ library EventsLib { ); event CreatePreLiquidation( - address indexed preLiquidationContract, MarketParams marketParams, PreLiquidationParams preLiquidationParams + address indexed preLiquidation, MarketParams marketParams, PreLiquidationParams preLiquidationParams ); } diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 1cef61b..3603c90 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -19,14 +19,14 @@ contract PreLiquidationFactoryTest is BaseTest { } function testCreatePreLiquidation(PreLiquidationParams memory preLiquidationParams) public { - vm.assume(preLiquidationParams.prelltv < lltv); + vm.assume(preLiquidationParams.preLltv < lltv); factory = new PreLiquidationFactory(address(MORPHO)); IPreLiquidation preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); assert(preLiquidation.MORPHO() == MORPHO); - assert(preLiquidation.prelltv() == preLiquidationParams.prelltv); + assert(preLiquidation.preLltv() == preLiquidationParams.preLltv); assert(preLiquidation.closeFactor() == preLiquidationParams.closeFactor); assert(preLiquidation.preLiquidationIncentive() == preLiquidationParams.preLiquidationIncentive); @@ -38,7 +38,7 @@ contract PreLiquidationFactoryTest is BaseTest { MarketParams memory _market = market; bytes32 preLiquidationId = getPreLiquidationId(_market, preLiquidationParams); - assert(factory.preliquidations(preLiquidationId) == preLiquidation); + assert(factory.preLiquidations(preLiquidationId) == preLiquidation); } function getPreLiquidationId(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 35f043c..fe20dc4 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -37,7 +37,7 @@ contract PreLiquidationTest is BaseTest { uint256 collateralAmount, uint256 borrowAmount ) public virtual { - preLiquidationParams.prelltv = bound(preLiquidationParams.prelltv, WAD / 100, market.lltv - 1); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, market.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -48,7 +48,7 @@ contract PreLiquidationTest is BaseTest { uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown(IOracle(market.oracle).price(), ORACLE_PRICE_SCALE).wMulDown(market.lltv); uint256 borrowPreLiquidationThreshold = - collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.prelltv); + collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv); borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); deal(address(loanToken), SUPPLIER, borrowAmount); From 06afb2b0b781827cedb630875a07e7de5c4c670b Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 16 Sep 2024 10:11:03 +0200 Subject: [PATCH 115/182] feat: change getSubscriptionId into public --- src/PreLiquidationFactory.sol | 2 +- test/PreLiquidationFactoryTest.sol | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index fea402e..681a45c 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -52,7 +52,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* INTERNAL */ function getPreLiquidationId(MarketParams calldata marketParams, PreLiquidationParams calldata preLiquidationParams) - internal + public pure returns (bytes32) { diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 3603c90..bd56796 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -37,15 +37,7 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.oracle() == market.oracle); MarketParams memory _market = market; - bytes32 preLiquidationId = getPreLiquidationId(_market, preLiquidationParams); + bytes32 preLiquidationId = factory.getPreLiquidationId(_market, preLiquidationParams); assert(factory.preLiquidations(preLiquidationId) == preLiquidation); } - - function getPreLiquidationId(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) - internal - pure - returns (bytes32) - { - return keccak256(abi.encode(marketParams, preLiquidationParams)); - } } From 1937397152eaa117d4d3bc1420099845a3173299 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 16 Sep 2024 17:23:03 +0200 Subject: [PATCH 116/182] test(preliquidation): improve tests --- test/PreLiquidationTest.sol | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index fe20dc4..d04db83 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -32,6 +32,15 @@ contract PreLiquidationTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); } + function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, market.lltv, type(uint256).max); + + vm.expectRevert( + abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector, preLiquidationParams.preLltv, market.lltv) + ); + factory.createPreLiquidation(market, preLiquidationParams); + } + function testPreLiquidation( PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount, @@ -58,14 +67,20 @@ contract PreLiquidationTest is BaseTest { deal(address(collateralToken), BORROWER, collateralAmount); vm.startPrank(BORROWER); MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); - MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); - MORPHO.setAuthorization(address(preLiquidation), true); vm.startPrank(LIQUIDATOR); deal(address(loanToken), LIQUIDATOR, type(uint256).max); loanToken.approve(address(preLiquidation), type(uint256).max); collateralToken.approve(address(preLiquidation), type(uint256).max); + vm.expectRevert(ErrorsLib.NotPreLiquidatablePosition.selector); + preLiquidation.preLiquidate(BORROWER, 0, 1, hex""); + + vm.startPrank(BORROWER); + MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); + MORPHO.setAuthorization(address(preLiquidation), true); + + vm.startPrank(LIQUIDATOR); Position memory position = MORPHO.position(market.id(), BORROWER); Market memory m = MORPHO.market(market.id()); @@ -75,6 +90,12 @@ contract PreLiquidationTest is BaseTest { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.InconsistentInput.selector, 0, 0)); + preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); + + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.InconsistentInput.selector, seizedAssets, repayableShares)); + preLiquidation.preLiquidate(BORROWER, seizedAssets, repayableShares, hex""); + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); } } From dead6a34644b8c39b715f039617a6278ff1d0dec Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:13:33 +0200 Subject: [PATCH 117/182] doc: remove outdated comment --- src/LiquidationProtection.sol | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/LiquidationProtection.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol new file mode 100644 index 0000000..bb34521 --- /dev/null +++ b/src/LiquidationProtection.sol @@ -0,0 +1,167 @@ +// 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 {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; +import {IMorphoLiquidateCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; +import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; +import {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, SubscriptionParams} from "./libraries/EventsLib.sol"; +import {ErrorsLib} from "./libraries/ErrorsLib.sol"; + +/// @title Morpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice The Liquidation Protection Contract for Morpho +contract LiquidationProtection { + using MarketParamsLib for MarketParams; + using UtilsLib for uint256; + using SharesMathLib for uint256; + using MathLib for uint256; + using MathLib for uint128; + using SafeTransferLib for ERC20; + + /* IMMUTABLE */ + IMorpho public immutable MORPHO; + + /* STORAGE */ + mapping(bytes32 => bool) public subscriptions; + + constructor(address morpho) { + MORPHO = IMorpho(morpho); + } + + function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { + require( + subscriptionParams.prelltv < marketParams.lltv, + ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) + ); + // should close factor be lower than 100% ? + // should there be a max liquidation incentive ? + + Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); + + subscriptions[subscriptionId] = true; + + emit EventsLib.Subscribe(msg.sender, marketId, subscriptionParams); + } + + function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { + Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); + + subscriptions[subscriptionId] = false; + + emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionParams); + } + + function liquidate( + MarketParams calldata marketParams, + SubscriptionParams calldata subscriptionParams, + address borrower, + uint256 seizedAssets, + uint256 repaidShares, + bytes calldata data + ) external { + Id marketId = marketParams.id(); + bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); + require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.InvalidSubscription(subscriptionNumber)); + + require( + UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) + ); + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + + MORPHO.accrueInterest(marketParams); + require( + !_isHealthy(marketId, borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition() + ); + + { + // Compute seizedAssets or repaidShares and repaidAssets + Market memory market = MORPHO.market(marketId); + uint256 liquidationIncentive = subscriptionParams.liquidationIncentive; + if (seizedAssets > 0) { + uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); + + repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( + market.totalBorrowAssets, market.totalBorrowShares + ); + } else { + seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( + liquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( + liquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + } + + // Check if liquidation is ok with close factor + Position memory borrowerPosition = MORPHO.position(marketId, borrower); + require( + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, + ErrorsLib.LiquidationTooLarge( + borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor), repaidShares + ) + ); + } + + bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); + (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); + + emit EventsLib.Liquidate( + borrower, marketId, subscriptionParams, msg.sender, repaidAssets, repaidShares, seizedAssets + ); + } + + function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { + require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho(msg.sender)); + ( + MarketParams memory marketParams, + uint256 seizedAssets, + address borrower, + address liquidator, + bytes memory data + ) = abi.decode(callbackData, (MarketParams, uint256, address, address, bytes)); + + MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); + + if (data.length > 0) { + IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(repaidAssets, data); + } + + ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); + + ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); + } + + function computeSubscriptionId(address borrower, Id marketId, SubscriptionParams memory subscriptionParams) + public + pure + returns (bytes32) + { + return keccak256(abi.encode(borrower, marketId, subscriptionParams)); + } + + function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) + internal + view + returns (bool) + { + Position memory borrowerPosition = MORPHO.position(id, borrower); + Market memory market = MORPHO.market(id); + + uint256 borrowed = + uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); + uint256 maxBorrow = + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); + + return maxBorrow >= borrowed; + } +} From 830d0baea8faf8ce8a3dce4d58793aae08efeef5 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:16:33 +0200 Subject: [PATCH 118/182] doc: remove comment --- src/PreLiquidationFactory.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 681a45c..baf0c92 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -49,8 +49,6 @@ contract PreLiquidationFactory is IPreLiquidationFactory { emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, preLiquidationParams); } - /* INTERNAL */ - function getPreLiquidationId(MarketParams calldata marketParams, PreLiquidationParams calldata preLiquidationParams) public pure From a1b4f8e09b3a1d2d0d24c4f79e9c79b6b13da437 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:37:34 +0200 Subject: [PATCH 119/182] refactor: remove file --- src/LiquidationProtection.sol | 167 ---------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 src/LiquidationProtection.sol diff --git a/src/LiquidationProtection.sol b/src/LiquidationProtection.sol deleted file mode 100644 index bb34521..0000000 --- a/src/LiquidationProtection.sol +++ /dev/null @@ -1,167 +0,0 @@ -// 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 {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; -import {IMorphoLiquidateCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; -import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; -import {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, SubscriptionParams} from "./libraries/EventsLib.sol"; -import {ErrorsLib} from "./libraries/ErrorsLib.sol"; - -/// @title Morpho -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice The Liquidation Protection Contract for Morpho -contract LiquidationProtection { - using MarketParamsLib for MarketParams; - using UtilsLib for uint256; - using SharesMathLib for uint256; - using MathLib for uint256; - using MathLib for uint128; - using SafeTransferLib for ERC20; - - /* IMMUTABLE */ - IMorpho public immutable MORPHO; - - /* STORAGE */ - mapping(bytes32 => bool) public subscriptions; - - constructor(address morpho) { - MORPHO = IMorpho(morpho); - } - - function subscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { - require( - subscriptionParams.prelltv < marketParams.lltv, - ErrorsLib.PreLltvTooHigh(subscriptionParams.prelltv, marketParams.lltv) - ); - // should close factor be lower than 100% ? - // should there be a max liquidation incentive ? - - Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); - - subscriptions[subscriptionId] = true; - - emit EventsLib.Subscribe(msg.sender, marketId, subscriptionParams); - } - - function unsubscribe(MarketParams calldata marketParams, SubscriptionParams calldata subscriptionParams) external { - Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(msg.sender, marketId, subscriptionParams); - - subscriptions[subscriptionId] = false; - - emit EventsLib.Unsubscribe(msg.sender, marketId, subscriptionParams); - } - - function liquidate( - MarketParams calldata marketParams, - SubscriptionParams calldata subscriptionParams, - address borrower, - uint256 seizedAssets, - uint256 repaidShares, - bytes calldata data - ) external { - Id marketId = marketParams.id(); - bytes32 subscriptionId = computeSubscriptionId(borrower, marketId, subscriptionNumber); - require(subscriptions[subscriptionId].closeFactor != 0, ErrorsLib.InvalidSubscription(subscriptionNumber)); - - require( - UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) - ); - uint256 collateralPrice = IOracle(marketParams.oracle).price(); - - MORPHO.accrueInterest(marketParams); - require( - !_isHealthy(marketId, borrower, collateralPrice, subscriptionParams.prelltv), ErrorsLib.HealthyPosition() - ); - - { - // Compute seizedAssets or repaidShares and repaidAssets - Market memory market = MORPHO.market(marketId); - uint256 liquidationIncentive = subscriptionParams.liquidationIncentive; - if (seizedAssets > 0) { - uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - - repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentive).toSharesUp( - market.totalBorrowAssets, market.totalBorrowShares - ); - } else { - seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive - ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - liquidationIncentive - ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); - } - - // Check if liquidation is ok with close factor - Position memory borrowerPosition = MORPHO.position(marketId, borrower); - require( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor) >= repaidShares, - ErrorsLib.LiquidationTooLarge( - borrowerPosition.borrowShares.wMulDown(subscriptions[subscriptionId].closeFactor), repaidShares - ) - ); - } - - bytes memory callbackData = abi.encode(marketParams, seizedAssets, borrower, msg.sender, data); - (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - - emit EventsLib.Liquidate( - borrower, marketId, subscriptionParams, msg.sender, repaidAssets, repaidShares, seizedAssets - ); - } - - function onMorphoRepay(uint256 assets, bytes calldata callbackData) external { - require(msg.sender == address(MORPHO), ErrorsLib.NotMorpho(msg.sender)); - ( - MarketParams memory marketParams, - uint256 seizedAssets, - address borrower, - address liquidator, - bytes memory data - ) = abi.decode(callbackData, (MarketParams, uint256, address, address, bytes)); - - MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); - - if (data.length > 0) { - IMorphoLiquidateCallback(liquidator).onMorphoLiquidate(repaidAssets, data); - } - - ERC20(marketParams.loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); - - ERC20(marketParams.loanToken).safeApprove(address(MORPHO), repaidAssets); - } - - function computeSubscriptionId(address borrower, Id marketId, SubscriptionParams memory subscriptionParams) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(borrower, marketId, subscriptionParams)); - } - - function _isHealthy(Id id, address borrower, uint256 collateralPrice, uint256 ltvThreshold) - internal - view - returns (bool) - { - Position memory borrowerPosition = MORPHO.position(id, borrower); - Market memory market = MORPHO.market(id); - - uint256 borrowed = - uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 maxBorrow = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(ltvThreshold); - - return maxBorrow >= borrowed; - } -} From f95e854f6211af15b3fb0717f4aa5cb09af02d0f Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:48:32 +0200 Subject: [PATCH 120/182] refactor: change event and error --- src/PreLiquidationFactory.sol | 2 +- src/libraries/ErrorsLib.sol | 2 +- src/libraries/EventsLib.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index baf0c92..b0504fc 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -40,7 +40,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { PreLiquidationParams calldata preLiquidationParams ) external returns (IPreLiquidation preLiquidation) { bytes32 preLiquidationId = getPreLiquidationId(marketParams, preLiquidationParams); - require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.RedundantMarket()); + require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.PreLiquidationAlreadyExists()); preLiquidation = IPreLiquidation(address(new PreLiquidation(marketParams, preLiquidationParams, address(MORPHO)))); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index ea7af97..834454e 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -18,5 +18,5 @@ library ErrorsLib { error ZeroAddress(); - error RedundantMarket(); + error PreLiquidationAlreadyExists(); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 0408d4d..304ebd4 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -10,9 +10,9 @@ import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; /// @notice Library exposing events. library EventsLib { event PreLiquidate( - address indexed borrower, Id indexed marketId, address indexed liquidator, + address indexed borrower, uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets From a0cd90deda2566fa4105036f0615742b101c1d18 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:49:45 +0200 Subject: [PATCH 121/182] refactor: move require --- src/PreLiquidation.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 7e74e3a..06557aa 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -47,6 +47,8 @@ contract PreLiquidation is IPreLiquidation { // TODO authorize this contract on morpho constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { + require(preLltv < lltv, ErrorsLib.PreLltvTooHigh(preLltv, lltv)); + MORPHO = IMorpho(morpho); loanToken = _marketParams.loanToken; @@ -62,7 +64,6 @@ contract PreLiquidation is IPreLiquidation { // should close factor be lower than 100% ? // should there be a max liquidation incentive ? - require(preLltv < lltv, ErrorsLib.PreLltvTooHigh(preLltv, lltv)); ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } @@ -97,7 +98,7 @@ contract PreLiquidation is IPreLiquidation { bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); (uint256 repaidAssets,) = MORPHO.repay(marketParams, 0, repaidShares, borrower, callbackData); - emit EventsLib.PreLiquidate(borrower, marketId, msg.sender, repaidAssets, repaidShares, seizedAssets); + emit EventsLib.PreLiquidate(marketId, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets); } function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { From c9a53bdc3151f5d0e5ee56345ced693fd17c40b3 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:50:21 +0200 Subject: [PATCH 122/182] doc: remove outdated comment --- src/PreLiquidation.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 06557aa..e4a3656 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -43,9 +43,6 @@ contract PreLiquidation is IPreLiquidation { uint256 public immutable closeFactor; uint256 public immutable preLiquidationIncentive; - // TODO EIP-712 signature - // TODO authorize this contract on morpho - constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { require(preLltv < lltv, ErrorsLib.PreLltvTooHigh(preLltv, lltv)); From b5532a949bb62ebf0ba84b6ddf00ab7ad103f1fb Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:55:19 +0200 Subject: [PATCH 123/182] refactor: inequality --- src/PreLiquidation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index e4a3656..c09fd17 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -122,6 +122,6 @@ contract PreLiquidation is IPreLiquidation { uint256 borrowThreshold = uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLltv); - return borrowThreshold < borrowed; + return borrowed > borrowThreshold; } } From eb4e0880cf8affbc305f2110571720f8d8f60995 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 17 Sep 2024 18:59:25 +0200 Subject: [PATCH 124/182] fix: require params --- src/PreLiquidation.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index c09fd17..2628c3e 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -44,7 +44,10 @@ contract PreLiquidation is IPreLiquidation { uint256 public immutable preLiquidationIncentive; constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { - require(preLltv < lltv, ErrorsLib.PreLltvTooHigh(preLltv, lltv)); + require( + _preLiquidationParams.preLltv < _marketParams.lltv, + ErrorsLib.PreLltvTooHigh(_preLiquidationParams.preLltv, _marketParams.lltv) + ); MORPHO = IMorpho(morpho); From 6b9e1bd4521a272697ce8492cef66d3b355d2944 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 18 Sep 2024 09:44:38 +0200 Subject: [PATCH 125/182] refactor: remove arg from InconsistentInput error --- src/PreLiquidation.sol | 4 +--- src/libraries/ErrorsLib.sol | 2 +- test/PreLiquidationTest.sol | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 2628c3e..68754e3 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -69,9 +69,7 @@ contract PreLiquidation is IPreLiquidation { } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { - require( - UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput(seizedAssets, repaidShares) - ); + require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); uint256 collateralPrice = IOracle(oracle).price(); MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 834454e..873330a 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.27; library ErrorsLib { error PreLltvTooHigh(uint256 preLltv, uint256 lltv); - error InconsistentInput(uint256 seizedAssets, uint256 repaidShares); + error InconsistentInput(); error NotPreLiquidatablePosition(); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index d04db83..f093e45 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -90,10 +90,10 @@ contract PreLiquidationTest is BaseTest { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.InconsistentInput.selector, 0, 0)); + vm.expectRevert(ErrorsLib.InconsistentInput.selector); preLiquidation.preLiquidate(BORROWER, 0, 0, hex""); - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.InconsistentInput.selector, seizedAssets, repayableShares)); + vm.expectRevert(ErrorsLib.InconsistentInput.selector); preLiquidation.preLiquidate(BORROWER, seizedAssets, repayableShares, hex""); preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); From da099a00896d0de3ab469cc338a3956fcb1e60e6 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 18 Sep 2024 10:03:45 +0200 Subject: [PATCH 126/182] feat: implement NonexistentMarket error --- src/PreLiquidation.sol | 8 +++++--- src/libraries/ErrorsLib.sol | 4 ++++ test/PreLiquidationTest.sol | 8 ++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 68754e3..0246294 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -44,19 +44,21 @@ contract PreLiquidation is IPreLiquidation { uint256 public immutable preLiquidationIncentive; constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { + MORPHO = IMorpho(morpho); + marketId = _marketParams.id(); + + require(MORPHO.market(marketId).lastUpdate != 0, ErrorsLib.NonexistentMarket(marketId)); + require( _preLiquidationParams.preLltv < _marketParams.lltv, ErrorsLib.PreLltvTooHigh(_preLiquidationParams.preLltv, _marketParams.lltv) ); - MORPHO = IMorpho(morpho); - loanToken = _marketParams.loanToken; collateralToken = _marketParams.collateralToken; oracle = _marketParams.oracle; irm = _marketParams.irm; lltv = _marketParams.lltv; - marketId = _marketParams.id(); preLltv = _preLiquidationParams.preLltv; closeFactor = _preLiquidationParams.closeFactor; diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 873330a..b5dbb5f 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.27; +import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + /// @title ErrorsLib /// @author Morpho Labs /// @custom:contact security@morpho.org @@ -19,4 +21,6 @@ library ErrorsLib { error ZeroAddress(); error PreLiquidationAlreadyExists(); + + error NonexistentMarket(Id); } diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index f093e45..e4c60a9 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -41,6 +41,14 @@ contract PreLiquidationTest is BaseTest { factory.createPreLiquidation(market, preLiquidationParams); } + function testNonexistentMarket(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) + public + virtual + { + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector, marketParams.id())); + factory.createPreLiquidation(marketParams, preLiquidationParams); + } + function testPreLiquidation( PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount, From 276ed5dc2f24b9ce243d2bb46ada93fe528c1783 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 18 Sep 2024 17:19:32 +0200 Subject: [PATCH 127/182] doc: remove comment --- src/PreLiquidation.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 0246294..d4a25c4 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -14,12 +14,13 @@ 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 Morpho /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Pre Liquidation Contract for Morpho -contract PreLiquidation is IPreLiquidation { +contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using MarketParamsLib for MarketParams; using UtilsLib for uint256; using SharesMathLib for uint256; @@ -64,9 +65,6 @@ contract PreLiquidation is IPreLiquidation { closeFactor = _preLiquidationParams.closeFactor; preLiquidationIncentive = _preLiquidationParams.preLiquidationIncentive; - // should close factor be lower than 100% ? - // should there be a max liquidation incentive ? - ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); } From 3083486bd1bec8f06118505c0ceccc5788888b4e Mon Sep 17 00:00:00 2001 From: peyha Date: Thu, 19 Sep 2024 16:12:23 +0200 Subject: [PATCH 128/182] test: implement callback test --- test/PreLiquidationTest.sol | 88 +++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index e4c60a9..36b7b41 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -7,6 +7,7 @@ import "../lib/forge-std/src/console.sol"; import "./BaseTest.sol"; import {IPreLiquidation, PreLiquidationParams} from "../src/interfaces/IPreLiquidation.sol"; +import {IPreLiquidationCallback} from "../src/interfaces/IPreLiquidationCallback.sol"; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {PreLiquidation} from "../src/PreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; @@ -17,7 +18,7 @@ import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib. import {MathLib, WAD} from "../lib/morpho-blue/src/libraries/MathLib.sol"; import {SharesMathLib} from "../lib/morpho-blue/src/libraries/SharesMathLib.sol"; -contract PreLiquidationTest is BaseTest { +contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { using MarketParamsLib for MarketParams; using SharesMathLib for uint256; using MathLib for uint256; @@ -32,28 +33,12 @@ contract PreLiquidationTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); } - function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { - preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, market.lltv, type(uint256).max); - - vm.expectRevert( - abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector, preLiquidationParams.preLltv, market.lltv) - ); - factory.createPreLiquidation(market, preLiquidationParams); - } - - function testNonexistentMarket(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) - public - virtual - { - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector, marketParams.id())); - factory.createPreLiquidation(marketParams, preLiquidationParams); - } - - function testPreLiquidation( + function preparePreLiquidation( PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount, - uint256 borrowAmount - ) public virtual { + uint256 borrowAmount, + address liquidator + ) public returns (uint256) { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, market.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); @@ -76,8 +61,8 @@ contract PreLiquidationTest is BaseTest { vm.startPrank(BORROWER); MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); - vm.startPrank(LIQUIDATOR); - deal(address(loanToken), LIQUIDATOR, type(uint256).max); + vm.startPrank(liquidator); + deal(address(loanToken), liquidator, type(uint256).max); loanToken.approve(address(preLiquidation), type(uint256).max); collateralToken.approve(address(preLiquidation), type(uint256).max); @@ -87,6 +72,35 @@ contract PreLiquidationTest is BaseTest { vm.startPrank(BORROWER); MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(preLiquidation), true); + vm.stopPrank(); + + return collateralPrice; + } + + function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, market.lltv, type(uint256).max); + + vm.expectRevert( + abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector, preLiquidationParams.preLltv, market.lltv) + ); + factory.createPreLiquidation(market, preLiquidationParams); + } + + function testNonexistentMarket(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) + public + virtual + { + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector, marketParams.id())); + factory.createPreLiquidation(marketParams, preLiquidationParams); + } + + function testPreLiquidation( + PreLiquidationParams memory preLiquidationParams, + uint256 collateralAmount, + uint256 borrowAmount + ) public virtual { + uint256 collateralPrice = + preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, LIQUIDATOR); vm.startPrank(LIQUIDATOR); Position memory position = MORPHO.position(market.id(), BORROWER); @@ -106,4 +120,32 @@ contract PreLiquidationTest is BaseTest { preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); } + + function testPreLiquidationCallback( + PreLiquidationParams memory preLiquidationParams, + uint256 collateralAmount, + uint256 borrowAmount + ) public virtual { + uint256 collateralPrice = + preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, address(this)); + + Position memory position = MORPHO.position(market.id(), BORROWER); + Market memory m = MORPHO.market(market.id()); + + uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); + uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( + preLiquidationParams.preLiquidationIncentive + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + vm.assume(seizedAssets > 0); + + bytes memory data = abi.encode(this.testPreLiquidationCallback.selector, hex""); + + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, data); + } + + function onPreLiquidate(uint256 repaidAssets, bytes memory data) external { + bytes4 selector; + (selector,) = abi.decode(data, (bytes4, bytes)); + require(selector == this.testPreLiquidationCallback.selector); + } } From af51a56555b9a73c11e194c88b5dd083a6c21249 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Fri, 20 Sep 2024 14:32:36 +0200 Subject: [PATCH 129/182] refactor: minor improvements --- src/PreLiquidation.sol | 74 +++++++++++------------ src/PreLiquidationFactory.sol | 24 ++++---- src/interfaces/IPreLiquidation.sol | 18 +++--- src/interfaces/IPreLiquidationFactory.sol | 11 ++-- src/libraries/ErrorsLib.sol | 4 +- src/libraries/EventsLib.sol | 4 +- test/BaseTest.sol | 12 ++-- test/PreLiquidationFactoryTest.sol | 23 +++---- test/PreLiquidationTest.sol | 49 +++++++-------- 9 files changed, 108 insertions(+), 111 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index d4a25c4..172faf0 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -30,73 +30,71 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { /* IMMUTABLE */ IMorpho public immutable MORPHO; - Id public immutable marketId; + Id public immutable ID; // Market parameters - address public immutable loanToken; - address public immutable collateralToken; - address public immutable oracle; - address public immutable irm; - uint256 public immutable lltv; + address public immutable LOAN_TOKEN; + address public immutable COLLATERAL_TOKEN; + address public immutable ORACLE; + address public immutable IRM; + uint256 public immutable LLTV; // Pre-liquidation parameters - uint256 public immutable preLltv; - uint256 public immutable closeFactor; - uint256 public immutable preLiquidationIncentive; + uint256 public immutable PRE_LLTV; + uint256 public immutable CLOSE_FACTOR; + uint256 public immutable PRE_LIQUIDATION_INCENTIVE; - constructor(MarketParams memory _marketParams, PreLiquidationParams memory _preLiquidationParams, address morpho) { - MORPHO = IMorpho(morpho); - marketId = _marketParams.id(); + constructor(Id id, PreLiquidationParams memory _preLiquidationParams, address morpho) { + require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); + MarketParams memory marketParams = IMorpho(morpho).idToMarketParams(id); + require(_preLiquidationParams.preLltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh()); - require(MORPHO.market(marketId).lastUpdate != 0, ErrorsLib.NonexistentMarket(marketId)); + MORPHO = IMorpho(morpho); - require( - _preLiquidationParams.preLltv < _marketParams.lltv, - ErrorsLib.PreLltvTooHigh(_preLiquidationParams.preLltv, _marketParams.lltv) - ); + ID = id; - loanToken = _marketParams.loanToken; - collateralToken = _marketParams.collateralToken; - oracle = _marketParams.oracle; - irm = _marketParams.irm; - lltv = _marketParams.lltv; + LOAN_TOKEN = marketParams.loanToken; + COLLATERAL_TOKEN = marketParams.collateralToken; + ORACLE = marketParams.oracle; + IRM = marketParams.irm; + LLTV = marketParams.lltv; - preLltv = _preLiquidationParams.preLltv; - closeFactor = _preLiquidationParams.closeFactor; - preLiquidationIncentive = _preLiquidationParams.preLiquidationIncentive; + PRE_LLTV = _preLiquidationParams.preLltv; + CLOSE_FACTOR = _preLiquidationParams.closeFactor; + PRE_LIQUIDATION_INCENTIVE = _preLiquidationParams.preLiquidationIncentive; - ERC20(loanToken).safeApprove(address(MORPHO), type(uint256).max); + ERC20(marketParams.loanToken).safeApprove(morpho, type(uint256).max); } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); - uint256 collateralPrice = IOracle(oracle).price(); + uint256 collateralPrice = IOracle(ORACLE).price(); - MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); + MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); MORPHO.accrueInterest(marketParams); require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.NotPreLiquidatablePosition()); - Market memory market = MORPHO.market(marketId); + Market memory market = MORPHO.market(ID); if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted.wDivUp(preLiquidationIncentive).toSharesUp( + repaidShares = seizedAssetsQuoted.wDivUp(PRE_LIQUIDATION_INCENTIVE).toSharesUp( market.totalBorrowAssets, market.totalBorrowShares ); } else { seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - preLiquidationIncentive + PRE_LIQUIDATION_INCENTIVE ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } // Check if liquidation is ok with close factor - uint256 repayableShares = MORPHO.position(marketId, borrower).borrowShares.wMulDown(closeFactor); + uint256 repayableShares = MORPHO.position(ID, borrower).borrowShares.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(marketId, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets); + emit EventsLib.PreLiquidate(ID, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets); } function onMorphoRepay(uint256 repaidAssets, bytes calldata callbackData) external { @@ -104,24 +102,24 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { (uint256 seizedAssets, address borrower, address liquidator, bytes memory data) = abi.decode(callbackData, (uint256, address, address, bytes)); - MarketParams memory marketParams = MarketParams(loanToken, collateralToken, oracle, irm, lltv); + MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); if (data.length > 0) { IPreLiquidationCallback(liquidator).onPreLiquidate(repaidAssets, data); } - ERC20(loanToken).safeTransferFrom(liquidator, address(this), repaidAssets); + ERC20(LOAN_TOKEN).safeTransferFrom(liquidator, address(this), repaidAssets); } function _isPreLiquidatable(address borrower, uint256 collateralPrice) internal view returns (bool) { - Position memory borrowerPosition = MORPHO.position(marketId, borrower); - Market memory market = MORPHO.market(marketId); + Position memory borrowerPosition = MORPHO.position(ID, borrower); + Market memory market = MORPHO.market(ID); uint256 borrowed = uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); uint256 borrowThreshold = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLltv); + uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(PRE_LLTV); return borrowed > borrowThreshold; } diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index b0504fc..2ba9f7f 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.27; -import {IMorpho, MarketParams} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; +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"; @@ -35,25 +35,27 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* EXTERNAL */ /// @inheritdoc IPreLiquidationFactory - function createPreLiquidation( - MarketParams calldata marketParams, - PreLiquidationParams calldata preLiquidationParams - ) external returns (IPreLiquidation preLiquidation) { - bytes32 preLiquidationId = getPreLiquidationId(marketParams, preLiquidationParams); + function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) + external + returns (IPreLiquidation) + { + bytes32 preLiquidationId = getPreLiquidationId(id, preLiquidationParams); require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.PreLiquidationAlreadyExists()); - preLiquidation = - IPreLiquidation(address(new PreLiquidation(marketParams, preLiquidationParams, address(MORPHO)))); + IPreLiquidation preLiquidation = + IPreLiquidation(address(new PreLiquidation(id, preLiquidationParams, address(MORPHO)))); preLiquidations[preLiquidationId] = preLiquidation; - emit EventsLib.CreatePreLiquidation(address(preLiquidation), marketParams, preLiquidationParams); + emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); + + return preLiquidation; } - function getPreLiquidationId(MarketParams calldata marketParams, PreLiquidationParams calldata preLiquidationParams) + function getPreLiquidationId(Id id, PreLiquidationParams calldata preLiquidationParams) public pure returns (bytes32) { - return keccak256(abi.encode(marketParams, preLiquidationParams)); + return keccak256(abi.encode(id, preLiquidationParams)); } } diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 55df262..0afb070 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -12,17 +12,17 @@ struct PreLiquidationParams { interface IPreLiquidation { function MORPHO() external view returns (IMorpho); - function marketId() external view returns (Id); + function ID() external view returns (Id); - function preLltv() external view returns (uint256); - function closeFactor() external view returns (uint256); - function preLiquidationIncentive() external view returns (uint256); + function PRE_LLTV() external view returns (uint256); + function CLOSE_FACTOR() external view returns (uint256); + function PRE_LIQUIDATION_INCENTIVE() external view returns (uint256); - function loanToken() external view returns (address); - function collateralToken() external view returns (address); - function oracle() external view returns (address); - function irm() external view returns (address); - function lltv() external view returns (uint256); + function LOAN_TOKEN() external view returns (address); + function COLLATERAL_TOKEN() external view returns (address); + function ORACLE() external view returns (address); + function IRM() external view returns (address); + function LLTV() external view returns (uint256); function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external; } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 6fa1a09..97b29c5 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; -import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {Id, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IPreLiquidation, PreLiquidationParams} from "./IPreLiquidation.sol"; /// @title IPreLiquidationFactory @@ -16,10 +16,9 @@ interface IPreLiquidationFactory { function preLiquidations(bytes32) external view returns (IPreLiquidation); /// @notice Creates a PreLiquidation contract. - /// @param marketParams The Morpho market for PreLiquidations. + /// @param id The Morpho market for PreLiquidations. /// @param preLiquidationParams The PreLiquidation params for the PreLiquidation contract. - function createPreLiquidation( - MarketParams calldata marketParams, - PreLiquidationParams calldata preLiquidationParams - ) external returns (IPreLiquidation preLiquidation); + function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) + external + returns (IPreLiquidation preLiquidation); } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index b5dbb5f..975dade 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,7 +8,7 @@ import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { - error PreLltvTooHigh(uint256 preLltv, uint256 lltv); + error PreLltvTooHigh(); error InconsistentInput(); @@ -22,5 +22,5 @@ library ErrorsLib { error PreLiquidationAlreadyExists(); - error NonexistentMarket(Id); + error NonexistentMarket(); } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 304ebd4..f7a63df 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -18,7 +18,5 @@ library EventsLib { uint256 seizedAssets ); - event CreatePreLiquidation( - address indexed preLiquidation, MarketParams marketParams, PreLiquidationParams preLiquidationParams - ); + event CreatePreLiquidation(address indexed preLiquidation, Id id, PreLiquidationParams preLiquidationParams); } diff --git a/test/BaseTest.sol b/test/BaseTest.sol index 9daa4af..df4c2f2 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -8,11 +8,13 @@ import {ERC20Mock} from "../src/mocks/ERC20Mock.sol"; import {IrmMock} from "../src/mocks/IrmMock.sol"; import {OracleMock} from "../src/mocks/OracleMock.sol"; -import {MarketParams, IMorpho} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {MarketParams, IMorpho, Id} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; import {ORACLE_PRICE_SCALE} from "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; contract BaseTest is Test { + using MarketParamsLib for MarketParams; + address internal SUPPLIER = makeAddr("Supplier"); address internal BORROWER = makeAddr("Borrower"); address internal LIQUIDATOR = makeAddr("Liquidator"); @@ -26,7 +28,8 @@ contract BaseTest is Test { IrmMock internal irm = new IrmMock(); uint256 internal lltv = 0.8 ether; // 80% - MarketParams internal market; + MarketParams internal marketParams; + Id internal id; function setUp() public virtual { vm.label(address(MORPHO), "Morpho"); @@ -46,15 +49,16 @@ contract BaseTest is Test { MORPHO.enableLltv(lltv); vm.stopPrank(); - market = MarketParams({ + marketParams = MarketParams({ loanToken: address(loanToken), collateralToken: address(collateralToken), oracle: address(oracle), irm: address(irm), lltv: lltv }); + id = marketParams.id(); - MORPHO.createMarket(market); + MORPHO.createMarket(marketParams); vm.startPrank(SUPPLIER); loanToken.approve(address(MORPHO), type(uint256).max); diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index bd56796..93368c4 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -7,6 +7,8 @@ import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; contract PreLiquidationFactoryTest is BaseTest { + using MarketParamsLib for MarketParams; + PreLiquidationFactory factory; function setUp() public override { @@ -22,22 +24,21 @@ contract PreLiquidationFactoryTest is BaseTest { vm.assume(preLiquidationParams.preLltv < lltv); factory = new PreLiquidationFactory(address(MORPHO)); - IPreLiquidation preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); + IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); assert(preLiquidation.MORPHO() == MORPHO); - assert(preLiquidation.preLltv() == preLiquidationParams.preLltv); - assert(preLiquidation.closeFactor() == preLiquidationParams.closeFactor); - assert(preLiquidation.preLiquidationIncentive() == preLiquidationParams.preLiquidationIncentive); + assert(preLiquidation.PRE_LLTV() == preLiquidationParams.preLltv); + assert(preLiquidation.CLOSE_FACTOR() == preLiquidationParams.closeFactor); + assert(preLiquidation.PRE_LIQUIDATION_INCENTIVE() == preLiquidationParams.preLiquidationIncentive); - assert(preLiquidation.lltv() == market.lltv); - assert(preLiquidation.collateralToken() == market.collateralToken); - assert(preLiquidation.loanToken() == market.loanToken); - assert(preLiquidation.irm() == market.irm); - assert(preLiquidation.oracle() == market.oracle); + assert(preLiquidation.LLTV() == marketParams.lltv); + assert(preLiquidation.COLLATERAL_TOKEN() == marketParams.collateralToken); + assert(preLiquidation.LOAN_TOKEN() == marketParams.loanToken); + assert(preLiquidation.IRM() == marketParams.irm); + assert(preLiquidation.ORACLE() == marketParams.oracle); - MarketParams memory _market = market; - bytes32 preLiquidationId = factory.getPreLiquidationId(_market, preLiquidationParams); + bytes32 preLiquidationId = factory.getPreLiquidationId(id, preLiquidationParams); assert(factory.preLiquidations(preLiquidationId) == preLiquidation); } } diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 36b7b41..d5419a1 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -39,27 +39,28 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 borrowAmount, address liquidator ) public returns (uint256) { - preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, market.lltv - 1); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); - preLiquidation = factory.createPreLiquidation(market, preLiquidationParams); + preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); - uint256 collateralPrice = IOracle(market.oracle).price(); - uint256 borrowLiquidationThreshold = - collateralAmount.mulDivDown(IOracle(market.oracle).price(), ORACLE_PRICE_SCALE).wMulDown(market.lltv); + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown( + IOracle(marketParams.oracle).price(), ORACLE_PRICE_SCALE + ).wMulDown(marketParams.lltv); uint256 borrowPreLiquidationThreshold = collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv); borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); deal(address(loanToken), SUPPLIER, borrowAmount); vm.prank(SUPPLIER); - MORPHO.supply(market, uint128(borrowAmount), 0, SUPPLIER, hex""); + MORPHO.supply(marketParams, uint128(borrowAmount), 0, SUPPLIER, hex""); deal(address(collateralToken), BORROWER, collateralAmount); vm.startPrank(BORROWER); - MORPHO.supplyCollateral(market, collateralAmount, BORROWER, hex""); + MORPHO.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); vm.startPrank(liquidator); deal(address(loanToken), liquidator, type(uint256).max); @@ -70,7 +71,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidation.preLiquidate(BORROWER, 0, 1, hex""); vm.startPrank(BORROWER); - MORPHO.borrow(market, borrowAmount, 0, BORROWER, BORROWER); + MORPHO.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(preLiquidation), true); vm.stopPrank(); @@ -78,20 +79,15 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { } function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { - preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, market.lltv, type(uint256).max); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, marketParams.lltv, type(uint256).max); - vm.expectRevert( - abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector, preLiquidationParams.preLltv, market.lltv) - ); - factory.createPreLiquidation(market, preLiquidationParams); + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector)); + factory.createPreLiquidation(id, preLiquidationParams); } - function testNonexistentMarket(MarketParams memory marketParams, PreLiquidationParams memory preLiquidationParams) - public - virtual - { - vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector, marketParams.id())); - factory.createPreLiquidation(marketParams, preLiquidationParams); + function testNonexistentMarket(Id _id, PreLiquidationParams memory preLiquidationParams) public virtual { + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector)); + factory.createPreLiquidation(_id, preLiquidationParams); } function testPreLiquidation( @@ -103,8 +99,8 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, LIQUIDATOR); vm.startPrank(LIQUIDATOR); - Position memory position = MORPHO.position(market.id(), BORROWER); - Market memory m = MORPHO.market(market.id()); + Position memory position = MORPHO.position(id, BORROWER); + Market memory m = MORPHO.market(id); uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( @@ -129,13 +125,12 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 collateralPrice = preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, address(this)); - Position memory position = MORPHO.position(market.id(), BORROWER); - Market memory m = MORPHO.market(market.id()); + Position memory position = MORPHO.position(marketParams.id(), BORROWER); + Market memory market = MORPHO.market(marketParams.id()); uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); - uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( - preLiquidationParams.preLiquidationIncentive - ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + uint256 seizedAssets = uint256(repayableShares).toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares) + .wMulDown(preLiquidationParams.preLiquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); bytes memory data = abi.encode(this.testPreLiquidationCallback.selector, hex""); @@ -143,7 +138,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidation.preLiquidate(BORROWER, 0, repayableShares, data); } - function onPreLiquidate(uint256 repaidAssets, bytes memory data) external { + function onPreLiquidate(uint256, bytes memory data) external pure { bytes4 selector; (selector,) = abi.decode(data, (bytes4, bytes)); require(selector == this.testPreLiquidationCallback.selector); From 12b5f8237b35fcc12bd5624c8896a6aee77e6400 Mon Sep 17 00:00:00 2001 From: MathisGD Date: Fri, 20 Sep 2024 14:36:37 +0200 Subject: [PATCH 130/182] chore: useless import --- src/interfaces/IPreLiquidation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 0afb070..4d4a8fd 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; -import {Id, MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {Id, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; struct PreLiquidationParams { uint256 preLltv; From 374ecdbe551a2dc929e4d5336efbfc37c3a5a361 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 20 Sep 2024 17:11:42 +0200 Subject: [PATCH 131/182] feat: implement preliquidation oracle --- src/PreLiquidation.sol | 4 +++- src/interfaces/IPreLiquidation.sol | 2 ++ test/PreLiquidationTest.sol | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 172faf0..bd1d27f 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -43,6 +43,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { uint256 public immutable PRE_LLTV; uint256 public immutable CLOSE_FACTOR; uint256 public immutable PRE_LIQUIDATION_INCENTIVE; + address public immutable PRE_LIQUIDATION_ORACLE; constructor(Id id, PreLiquidationParams memory _preLiquidationParams, address morpho) { require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); @@ -62,13 +63,14 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { PRE_LLTV = _preLiquidationParams.preLltv; CLOSE_FACTOR = _preLiquidationParams.closeFactor; PRE_LIQUIDATION_INCENTIVE = _preLiquidationParams.preLiquidationIncentive; + PRE_LIQUIDATION_ORACLE = _preLiquidationParams.preLiquidationOracle; ERC20(marketParams.loanToken).safeApprove(morpho, type(uint256).max); } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); - uint256 collateralPrice = IOracle(ORACLE).price(); + uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); MORPHO.accrueInterest(marketParams); diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 4d4a8fd..db79e69 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -7,6 +7,7 @@ struct PreLiquidationParams { uint256 preLltv; uint256 closeFactor; uint256 preLiquidationIncentive; + address preLiquidationOracle; } interface IPreLiquidation { @@ -17,6 +18,7 @@ interface IPreLiquidation { function PRE_LLTV() external view returns (uint256); function CLOSE_FACTOR() external view returns (uint256); function PRE_LIQUIDATION_INCENTIVE() external view returns (uint256); + function PRE_LIQUIDATION_ORACLE() external view returns (address); function LOAN_TOKEN() external view returns (address); function COLLATERAL_TOKEN() external view returns (address); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index d5419a1..e5b9435 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -42,6 +42,8 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); + preLiquidationParams.preLiquidationOracle = marketParams.oracle; + collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); From ec174523344bc11fb732802028a19e8e88505802 Mon Sep 17 00:00:00 2001 From: peyha Date: Fri, 20 Sep 2024 17:23:06 +0200 Subject: [PATCH 132/182] doc: natspec --- src/interfaces/IPreLiquidation.sol | 12 ++++++++++++ src/interfaces/IPreLiquidationCallback.sol | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index db79e69..3c7267c 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -10,6 +10,10 @@ struct PreLiquidationParams { address preLiquidationOracle; } +/// @title IPreLiquidation +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Interface of PreLiquidation. interface IPreLiquidation { function MORPHO() external view returns (IMorpho); @@ -26,5 +30,13 @@ interface IPreLiquidation { function IRM() external view returns (address); function LLTV() external view returns (uint256); + /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the + /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` + /// function with the given `data`. + /// @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; } diff --git a/src/interfaces/IPreLiquidationCallback.sol b/src/interfaces/IPreLiquidationCallback.sol index 6982780..465961d 100644 --- a/src/interfaces/IPreLiquidationCallback.sol +++ b/src/interfaces/IPreLiquidationCallback.sol @@ -1,6 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; +/// @title IPreLiquidationCallback +/// @notice Interface that preliquidators willing to use `preliquidate's callback must implement. interface IPreLiquidationCallback { + /// @notice Callback called when a preLiquidation 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; } From c75e6089448f9cb8fd5729e9528a6a4520d00a59 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 10:13:39 +0200 Subject: [PATCH 133/182] refactor: naming --- src/PreLiquidation.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index bd1d27f..c70a763 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -45,10 +45,10 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { uint256 public immutable PRE_LIQUIDATION_INCENTIVE; address public immutable PRE_LIQUIDATION_ORACLE; - constructor(Id id, PreLiquidationParams memory _preLiquidationParams, address morpho) { + constructor(Id id, PreLiquidationParams memory preLiquidationParams, address morpho) { 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.preLltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh()); MORPHO = IMorpho(morpho); @@ -60,10 +60,10 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { IRM = marketParams.irm; LLTV = marketParams.lltv; - PRE_LLTV = _preLiquidationParams.preLltv; - CLOSE_FACTOR = _preLiquidationParams.closeFactor; - PRE_LIQUIDATION_INCENTIVE = _preLiquidationParams.preLiquidationIncentive; - PRE_LIQUIDATION_ORACLE = _preLiquidationParams.preLiquidationOracle; + PRE_LLTV = preLiquidationParams.preLltv; + CLOSE_FACTOR = preLiquidationParams.closeFactor; + PRE_LIQUIDATION_INCENTIVE = preLiquidationParams.preLiquidationIncentive; + PRE_LIQUIDATION_ORACLE = preLiquidationParams.preLiquidationOracle; ERC20(marketParams.loanToken).safeApprove(morpho, type(uint256).max); } From 4f08e8ee1fa846e2be77b5ec9f5a5fa46295b3a3 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 10:16:28 +0200 Subject: [PATCH 134/182] doc: wrong title --- src/PreLiquidation.sol | 2 +- src/PreLiquidationFactory.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index c70a763..af95d8e 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -16,7 +16,7 @@ import {IPreLiquidationCallback} from "./interfaces/IPreLiquidationCallback.sol" import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidation.sol"; import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; -/// @title Morpho +/// @title PreLiquidation /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Pre Liquidation Contract for Morpho diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 2ba9f7f..c6d9835 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -8,7 +8,7 @@ import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; -/// @title Morpho +/// @title PreLiquidationFactory /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Pre Liquidation Factory Contract for Morpho From 6de296dfde5384b860d6c5418ae58bf60ceae50a Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 10:30:50 +0200 Subject: [PATCH 135/182] doc: natspec --- src/interfaces/IPreLiquidation.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 3c7267c..acabc9b 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -15,19 +15,34 @@ struct PreLiquidationParams { /// @custom:contact security@morpho.org /// @notice Interface of PreLiquidation. interface IPreLiquidation { + /// @notice Morpho's address. function MORPHO() external view returns (IMorpho); + /// @notice The id of the Morpho Market specific to the PreLiquidation contract. function ID() external view returns (Id); + /// @notice The Pre LLTV of the PreLiquidation contract corresponding to + /// the maximum LTV of a position before allowing preliquidation. function PRE_LLTV() external view returns (uint256); + /// @notice The close factor of the PreLiquidation contract corresponding to + /// the maximum proportion of debt that can be pre-liquidated at once. function CLOSE_FACTOR() external view returns (uint256); + /// @notice The PreLiquidation incentive of the PreLiquidation contract corresponding to + /// the proportion of the liquidated debt value given to the liquidator in collateral token. function PRE_LIQUIDATION_INCENTIVE() external view returns (uint256); + /// @notice The PreLiquidation oracle of the PreLiquidation contract corresponding to + /// the oracle used to assess whether or not a position can be preliquidated. function PRE_LIQUIDATION_ORACLE() external view returns (address); + /// @notice The loan token of the Morpho Market specific to the PreLiquidation contract. function LOAN_TOKEN() external view returns (address); + /// @notice The collateral token of the Morpho Market specific to the PreLiquidation contract. function COLLATERAL_TOKEN() external view returns (address); + /// @notice The oracle address of the Morpho Market specific to the PreLiquidation contract. function ORACLE() external view returns (address); + /// @notice The IRM address of the Morpho Market specific to the PreLiquidation contract. function IRM() external view returns (address); + /// @notice The LLTV of the Morpho Market specific to the PreLiquidation contract. function LLTV() external view returns (uint256); /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the From d9b19363c23b8fb7486787c22f1da2240491f0f9 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 10:40:04 +0200 Subject: [PATCH 136/182] feat: remove MathLib for uint128 --- src/PreLiquidation.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index af95d8e..1a95414 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -25,7 +25,6 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using UtilsLib for uint256; using SharesMathLib for uint256; using MathLib for uint256; - using MathLib for uint128; using SafeTransferLib for ERC20; /* IMMUTABLE */ @@ -90,7 +89,8 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { } // Check if liquidation is ok with close factor - uint256 repayableShares = MORPHO.position(ID, borrower).borrowShares.wMulDown(CLOSE_FACTOR); + uint256 borrowerShares = MORPHO.position(ID, borrower).borrowShares; + uint256 repayableShares = borrowerShares.wMulDown(CLOSE_FACTOR); require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); bytes memory callbackData = abi.encode(seizedAssets, borrower, msg.sender, data); From 1ba3aaf71b1a39053cb3024287ec0e10b4a595ad Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 10:46:31 +0200 Subject: [PATCH 137/182] refactor: prevent redundant data call --- src/PreLiquidation.sol | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 1a95414..f392f53 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -72,10 +72,11 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); + Market memory market = MORPHO.market(ID); + Position memory position = MORPHO.position(ID, borrower); MORPHO.accrueInterest(marketParams); - require(_isPreLiquidatable(borrower, collateralPrice), ErrorsLib.NotPreLiquidatablePosition()); + require(_isPreLiquidatable(collateralPrice, position, market), ErrorsLib.NotPreLiquidatablePosition()); - Market memory market = MORPHO.market(ID); if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); @@ -89,7 +90,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { } // Check if liquidation is ok with close factor - uint256 borrowerShares = MORPHO.position(ID, borrower).borrowShares; + uint256 borrowerShares = position.borrowShares; uint256 repayableShares = borrowerShares.wMulDown(CLOSE_FACTOR); require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); @@ -114,14 +115,14 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ERC20(LOAN_TOKEN).safeTransferFrom(liquidator, address(this), repaidAssets); } - function _isPreLiquidatable(address borrower, uint256 collateralPrice) internal view returns (bool) { - Position memory borrowerPosition = MORPHO.position(ID, borrower); - Market memory market = MORPHO.market(ID); - - uint256 borrowed = - uint256(borrowerPosition.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); + function _isPreLiquidatable(uint256 collateralPrice, Position memory position, Market memory market) + internal + view + returns (bool) + { + uint256 borrowed = uint256(position.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); uint256 borrowThreshold = - uint256(borrowerPosition.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(PRE_LLTV); + uint256(position.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(PRE_LLTV); return borrowed > borrowThreshold; } From 4f8bf1067dbe84b22c55265beb01cd492e0154f8 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 12:18:23 +0200 Subject: [PATCH 138/182] refactor: errors --- src/libraries/ErrorsLib.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 975dade..f3b5b20 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,6 +8,8 @@ import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { + /* PreLiquidation errors */ + error PreLltvTooHigh(); error InconsistentInput(); @@ -18,9 +20,11 @@ library ErrorsLib { error NotMorpho(); + error NonexistentMarket(); + + /* PreLiquidation Factory errors */ + error ZeroAddress(); error PreLiquidationAlreadyExists(); - - error NonexistentMarket(); } From 81f3f78a7bdc0c0cb648a272798bb8e8e566864d Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 14:20:26 +0200 Subject: [PATCH 139/182] feat: create2 in factory --- src/PreLiquidationFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index c6d9835..ffb24ef 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -43,7 +43,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.PreLiquidationAlreadyExists()); IPreLiquidation preLiquidation = - IPreLiquidation(address(new PreLiquidation(id, preLiquidationParams, address(MORPHO)))); + IPreLiquidation(address(new PreLiquidation{salt: 0}(id, preLiquidationParams, address(MORPHO)))); preLiquidations[preLiquidationId] = preLiquidation; emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); From 732c1712db015f385799e2ffe7239470b3fccc5b Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 15:15:35 +0200 Subject: [PATCH 140/182] feat: implementing create2 and UtilsLib --- src/libraries/ConstantsLib.sol | 5 +++++ src/libraries/UtilsLib.sol | 27 +++++++++++++++++++++++++++ test/PreLiquidationFactoryTest.sol | 5 +++++ 3 files changed, 37 insertions(+) create mode 100644 src/libraries/ConstantsLib.sol create mode 100644 src/libraries/UtilsLib.sol diff --git a/src/libraries/ConstantsLib.sol b/src/libraries/ConstantsLib.sol new file mode 100644 index 0000000..14e6f43 --- /dev/null +++ b/src/libraries/ConstantsLib.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @dev The salt for preliquidation create2 address. +bytes32 constant SALT = 0; diff --git a/src/libraries/UtilsLib.sol b/src/libraries/UtilsLib.sol new file mode 100644 index 0000000..e91bb2b --- /dev/null +++ b/src/libraries/UtilsLib.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {SALT} from "../libraries/ConstantsLib.sol"; +import {PreLiquidation} from "../Preliquidation.sol"; +import {PreLiquidationParams} from "../interfaces/IPreliquidation.sol"; +import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + +/// @title UtilsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library exposing helpers. +/// @dev Inspired by https://github.com/morpho-org/morpho-utils. + +library UtilsLib { + function computePreLiquidationAddress( + Id id, + PreLiquidationParams memory preLiquidationParams, + address morpho, + address factory + ) internal returns (address preLiquidationAddress) { + bytes32 init_code_hash = + keccak256(abi.encode(type(PreLiquidation).creationCode, id, preLiquidationParams, morpho)); + preLiquidationAddress = + address(bytes20(keccak256(abi.encodePacked(uint8(0xff), factory, SALT, init_code_hash)))); + } +} diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 93368c4..5f4c732 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -5,6 +5,7 @@ import "./BaseTest.sol"; import {PreLiquidationParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; +import {UtilsLib} from "../src/libraries/UtilsLib.sol"; contract PreLiquidationFactoryTest is BaseTest { using MarketParamsLib for MarketParams; @@ -40,5 +41,9 @@ contract PreLiquidationFactoryTest is BaseTest { bytes32 preLiquidationId = factory.getPreLiquidationId(id, preLiquidationParams); assert(factory.preLiquidations(preLiquidationId) == preLiquidation); + + address preLiquidationAddress = + UtilsLib.computePreLiquidationAddress(id, preLiquidationParams, address(MORPHO), address(factory)); + assert(address(preLiquidation) == preLiquidationAddress); } } From bb869fc7a4dc2aa5496e59bd4c01ff824864ccc1 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 16:09:58 +0200 Subject: [PATCH 141/182] feat: implement create2 with test --- src/libraries/UtilsLib.sol | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libraries/UtilsLib.sol b/src/libraries/UtilsLib.sol index e91bb2b..d912eb6 100644 --- a/src/libraries/UtilsLib.sol +++ b/src/libraries/UtilsLib.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import {SALT} from "../libraries/ConstantsLib.sol"; -import {PreLiquidation} from "../Preliquidation.sol"; -import {PreLiquidationParams} from "../interfaces/IPreliquidation.sol"; +import {PreLiquidation} from "../PreLiquidation.sol"; +import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; /// @title UtilsLib @@ -18,10 +18,21 @@ library UtilsLib { PreLiquidationParams memory preLiquidationParams, address morpho, address factory - ) internal returns (address preLiquidationAddress) { - bytes32 init_code_hash = - keccak256(abi.encode(type(PreLiquidation).creationCode, id, preLiquidationParams, morpho)); + ) internal pure returns (address preLiquidationAddress) { + bytes32 init_code_hash = keccak256( + abi.encodePacked( + type(PreLiquidation).creationCode, + abi.encode( + id, + preLiquidationParams.preLltv, + preLiquidationParams.closeFactor, + preLiquidationParams.preLiquidationIncentive, + preLiquidationParams.preLiquidationOracle, + morpho + ) + ) + ); preLiquidationAddress = - address(bytes20(keccak256(abi.encodePacked(uint8(0xff), factory, SALT, init_code_hash)))); + address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, SALT, init_code_hash))))); } } From 9a1c56f9db6aa5071f6846e7684ef9f9d343b18a Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 16:30:52 +0200 Subject: [PATCH 142/182] feat: remove PreLiquidationAlreadyExist error --- src/PreLiquidationFactory.sol | 18 +----------------- src/interfaces/IPreLiquidationFactory.sol | 3 --- src/libraries/ErrorsLib.sol | 2 -- test/PreLiquidationFactoryTest.sol | 3 --- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index ffb24ef..ee41e2e 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -7,6 +7,7 @@ import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidatio import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; +import {UtilsLib} from "./libraries/UtilsLib.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs @@ -18,11 +19,6 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /// @inheritdoc IPreLiquidationFactory IMorpho public immutable MORPHO; - /* STORAGE */ - - /// @inheritdoc IPreLiquidationFactory - mapping(bytes32 => IPreLiquidation) public preLiquidations; - /* CONSTRUCTOR */ /// @param morpho The address of the Morpho contract. @@ -39,23 +35,11 @@ contract PreLiquidationFactory is IPreLiquidationFactory { external returns (IPreLiquidation) { - bytes32 preLiquidationId = getPreLiquidationId(id, preLiquidationParams); - require(address(preLiquidations[preLiquidationId]) == address(0), ErrorsLib.PreLiquidationAlreadyExists()); - IPreLiquidation preLiquidation = IPreLiquidation(address(new PreLiquidation{salt: 0}(id, preLiquidationParams, address(MORPHO)))); - preLiquidations[preLiquidationId] = preLiquidation; emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); return preLiquidation; } - - function getPreLiquidationId(Id id, PreLiquidationParams calldata preLiquidationParams) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(id, preLiquidationParams)); - } } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 97b29c5..52260b6 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -12,9 +12,6 @@ interface IPreLiquidationFactory { /// @notice The address of the Morpho contract. function MORPHO() external view returns (IMorpho); - /// @notice The contract address created for a specific preLiquidationId. - function preLiquidations(bytes32) external view returns (IPreLiquidation); - /// @notice Creates a PreLiquidation contract. /// @param id The Morpho market for PreLiquidations. /// @param preLiquidationParams The PreLiquidation params for the PreLiquidation contract. diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index f3b5b20..3c10aa4 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -25,6 +25,4 @@ library ErrorsLib { /* PreLiquidation Factory errors */ error ZeroAddress(); - - error PreLiquidationAlreadyExists(); } diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 5f4c732..fc885d7 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -39,9 +39,6 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.IRM() == marketParams.irm); assert(preLiquidation.ORACLE() == marketParams.oracle); - bytes32 preLiquidationId = factory.getPreLiquidationId(id, preLiquidationParams); - assert(factory.preLiquidations(preLiquidationId) == preLiquidation); - address preLiquidationAddress = UtilsLib.computePreLiquidationAddress(id, preLiquidationParams, address(MORPHO), address(factory)); assert(address(preLiquidation) == preLiquidationAddress); From 45ab92c8df8762cacc3c2bb0a39b3abc688b1eb8 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 16:31:31 +0200 Subject: [PATCH 143/182] feat: use salt in factory --- src/PreLiquidationFactory.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index ee41e2e..381ee95 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -8,6 +8,7 @@ import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; import {UtilsLib} from "./libraries/UtilsLib.sol"; +import {SALT} from "./libraries/ConstantsLib.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs @@ -36,7 +37,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { returns (IPreLiquidation) { IPreLiquidation preLiquidation = - IPreLiquidation(address(new PreLiquidation{salt: 0}(id, preLiquidationParams, address(MORPHO)))); + IPreLiquidation(address(new PreLiquidation{salt: SALT}(id, preLiquidationParams, address(MORPHO)))); emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); From c51b45c8f284ebfc586850601c19899b621c67d7 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 16:32:52 +0200 Subject: [PATCH 144/182] refactor: encode struct --- src/libraries/UtilsLib.sol | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/libraries/UtilsLib.sol b/src/libraries/UtilsLib.sol index d912eb6..5c0c167 100644 --- a/src/libraries/UtilsLib.sol +++ b/src/libraries/UtilsLib.sol @@ -19,19 +19,8 @@ library UtilsLib { address morpho, address factory ) internal pure returns (address preLiquidationAddress) { - bytes32 init_code_hash = keccak256( - abi.encodePacked( - type(PreLiquidation).creationCode, - abi.encode( - id, - preLiquidationParams.preLltv, - preLiquidationParams.closeFactor, - preLiquidationParams.preLiquidationIncentive, - preLiquidationParams.preLiquidationOracle, - morpho - ) - ) - ); + bytes32 init_code_hash = + keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(id, preLiquidationParams, morpho))); preLiquidationAddress = address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, SALT, init_code_hash))))); } From f7d98367102861dc69425e36c86d107daa71b4bd Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 17:31:30 +0200 Subject: [PATCH 145/182] test: redundant pre liquidation --- test/PreLiquidationFactoryTest.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index fc885d7..95d9eb0 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -43,4 +43,14 @@ contract PreLiquidationFactoryTest is BaseTest { UtilsLib.computePreLiquidationAddress(id, preLiquidationParams, address(MORPHO), address(factory)); assert(address(preLiquidation) == preLiquidationAddress); } + + function testRedundantPreLiquidation(PreLiquidationParams memory preLiquidationParams) public { + vm.assume(preLiquidationParams.preLltv < lltv); + factory = new PreLiquidationFactory(address(MORPHO)); + + factory.createPreLiquidation(id, preLiquidationParams); + + vm.expectRevert(); + factory.createPreLiquidation(id, preLiquidationParams); + } } From 2dedab63e214b75796b13e92ac32f6b71ee218d0 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 17:33:29 +0200 Subject: [PATCH 146/182] doc: remove natspec --- src/libraries/UtilsLib.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/libraries/UtilsLib.sol b/src/libraries/UtilsLib.sol index 5c0c167..554d992 100644 --- a/src/libraries/UtilsLib.sol +++ b/src/libraries/UtilsLib.sol @@ -6,12 +6,6 @@ import {PreLiquidation} from "../PreLiquidation.sol"; import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -/// @title UtilsLib -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Library exposing helpers. -/// @dev Inspired by https://github.com/morpho-org/morpho-utils. - library UtilsLib { function computePreLiquidationAddress( Id id, From dfc6011ca3833446cb155651116c25cf35dd906c Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 17:40:19 +0200 Subject: [PATCH 147/182] refactor: remove constantsLib and hardcode salt --- src/PreLiquidationFactory.sol | 3 +-- src/libraries/ConstantsLib.sol | 5 ----- src/libraries/UtilsLib.sol | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 src/libraries/ConstantsLib.sol diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 381ee95..ee41e2e 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -8,7 +8,6 @@ import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; import {UtilsLib} from "./libraries/UtilsLib.sol"; -import {SALT} from "./libraries/ConstantsLib.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs @@ -37,7 +36,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { returns (IPreLiquidation) { IPreLiquidation preLiquidation = - IPreLiquidation(address(new PreLiquidation{salt: SALT}(id, preLiquidationParams, address(MORPHO)))); + IPreLiquidation(address(new PreLiquidation{salt: 0}(id, preLiquidationParams, address(MORPHO)))); emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); diff --git a/src/libraries/ConstantsLib.sol b/src/libraries/ConstantsLib.sol deleted file mode 100644 index 14e6f43..0000000 --- a/src/libraries/ConstantsLib.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -/// @dev The salt for preliquidation create2 address. -bytes32 constant SALT = 0; diff --git a/src/libraries/UtilsLib.sol b/src/libraries/UtilsLib.sol index 554d992..cc1b222 100644 --- a/src/libraries/UtilsLib.sol +++ b/src/libraries/UtilsLib.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {SALT} from "../libraries/ConstantsLib.sol"; import {PreLiquidation} from "../PreLiquidation.sol"; import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; @@ -16,6 +15,6 @@ library UtilsLib { bytes32 init_code_hash = keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(id, preLiquidationParams, morpho))); preLiquidationAddress = - address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, SALT, init_code_hash))))); + address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, uint256(0), init_code_hash))))); } } From 69d3fc4f796ae0135242dce23c1a75dd13f97789 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 17:47:07 +0200 Subject: [PATCH 148/182] feat: error --- src/libraries/ErrorsLib.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 3c10aa4..1022a63 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -25,4 +25,6 @@ library ErrorsLib { /* PreLiquidation Factory errors */ error ZeroAddress(); + + // Creating a redundant PreLiquidation contract will lead to "EvmError: Revert" } From 90b18ad78c263d192eb79d005aa3f4790a782c2c Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 17:58:50 +0200 Subject: [PATCH 149/182] test: add revert data --- test/PreLiquidationFactoryTest.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 95d9eb0..a18f664 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -50,7 +50,7 @@ contract PreLiquidationFactoryTest is BaseTest { factory.createPreLiquidation(id, preLiquidationParams); - vm.expectRevert(); + vm.expectRevert(bytes("")); factory.createPreLiquidation(id, preLiquidationParams); } } From 329b07ced1dd176ff75251b2e64e37c0c8390102 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:01:40 +0200 Subject: [PATCH 150/182] refactor: move UtilsLib to periphery --- src/PreLiquidationFactory.sol | 2 +- src/libraries/{ => periphery}/UtilsLib.sol | 6 +++--- test/PreLiquidationFactoryTest.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/libraries/{ => periphery}/UtilsLib.sol (75%) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index ee41e2e..6a84129 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -7,7 +7,7 @@ import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidatio import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; -import {UtilsLib} from "./libraries/UtilsLib.sol"; +import {UtilsLib} from "./libraries/periphery/UtilsLib.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs diff --git a/src/libraries/UtilsLib.sol b/src/libraries/periphery/UtilsLib.sol similarity index 75% rename from src/libraries/UtilsLib.sol rename to src/libraries/periphery/UtilsLib.sol index cc1b222..99db18b 100644 --- a/src/libraries/UtilsLib.sol +++ b/src/libraries/periphery/UtilsLib.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {PreLiquidation} from "../PreLiquidation.sol"; -import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; -import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +import {PreLiquidation} from "../../PreLiquidation.sol"; +import {PreLiquidationParams} from "../../interfaces/IPreLiquidation.sol"; +import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; library UtilsLib { function computePreLiquidationAddress( diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index a18f664..d5133eb 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -5,7 +5,7 @@ import "./BaseTest.sol"; import {PreLiquidationParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; -import {UtilsLib} from "../src/libraries/UtilsLib.sol"; +import {UtilsLib} from "../src/libraries/periphery/UtilsLib.sol"; contract PreLiquidationFactoryTest is BaseTest { using MarketParamsLib for MarketParams; From 5827c739b9d7852498acbac7624d6406e492344e Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:05:28 +0200 Subject: [PATCH 151/182] doc: redundant preliquidation natspec --- src/interfaces/IPreLiquidationFactory.sol | 2 ++ src/libraries/ErrorsLib.sol | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 52260b6..c8619d5 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -15,6 +15,8 @@ interface IPreLiquidationFactory { /// @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 if a PreLiquidation with these exact same + /// parameters already exists. function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 1022a63..3c10aa4 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -25,6 +25,4 @@ library ErrorsLib { /* PreLiquidation Factory errors */ error ZeroAddress(); - - // Creating a redundant PreLiquidation contract will lead to "EvmError: Revert" } From c9bfa30a29144e85ae56c132a49a6b496b2def15 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:09:57 +0200 Subject: [PATCH 152/182] test: isolate create2 test --- test/PreLiquidationFactoryTest.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index d5133eb..7e535c9 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -38,6 +38,13 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.LOAN_TOKEN() == marketParams.loanToken); assert(preLiquidation.IRM() == marketParams.irm); assert(preLiquidation.ORACLE() == marketParams.oracle); + } + + function testCreate2Deployment(PreLiquidationParams memory preLiquidationParams) public { + vm.assume(preLiquidationParams.preLltv < lltv); + + factory = new PreLiquidationFactory(address(MORPHO)); + IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); address preLiquidationAddress = UtilsLib.computePreLiquidationAddress(id, preLiquidationParams, address(MORPHO), address(factory)); From 4a3a87a3469ddf949b021490ba6e4bff36cb8343 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:13:39 +0200 Subject: [PATCH 153/182] refactor: reorder constant arg --- src/PreLiquidation.sol | 2 +- src/PreLiquidationFactory.sol | 2 +- src/libraries/periphery/UtilsLib.sol | 8 ++++---- test/PreLiquidationFactoryTest.sol | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index f392f53..11d7a37 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -44,7 +44,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { uint256 public immutable PRE_LIQUIDATION_INCENTIVE; address public immutable PRE_LIQUIDATION_ORACLE; - constructor(Id id, PreLiquidationParams memory preLiquidationParams, address morpho) { + 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()); diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 6a84129..d185067 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -36,7 +36,7 @@ contract PreLiquidationFactory is IPreLiquidationFactory { returns (IPreLiquidation) { IPreLiquidation preLiquidation = - IPreLiquidation(address(new PreLiquidation{salt: 0}(id, preLiquidationParams, address(MORPHO)))); + IPreLiquidation(address(new PreLiquidation{salt: 0}(address(MORPHO), id, preLiquidationParams))); emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); diff --git a/src/libraries/periphery/UtilsLib.sol b/src/libraries/periphery/UtilsLib.sol index 99db18b..8688b97 100644 --- a/src/libraries/periphery/UtilsLib.sol +++ b/src/libraries/periphery/UtilsLib.sol @@ -7,13 +7,13 @@ import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; library UtilsLib { function computePreLiquidationAddress( - Id id, - PreLiquidationParams memory preLiquidationParams, address morpho, - address factory + address factory, + Id id, + PreLiquidationParams memory preLiquidationParams ) internal pure returns (address preLiquidationAddress) { bytes32 init_code_hash = - keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(id, preLiquidationParams, morpho))); + keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(morpho, id, preLiquidationParams))); preLiquidationAddress = address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, uint256(0), init_code_hash))))); } diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 7e535c9..8a6c5ae 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -47,7 +47,7 @@ contract PreLiquidationFactoryTest is BaseTest { IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); address preLiquidationAddress = - UtilsLib.computePreLiquidationAddress(id, preLiquidationParams, address(MORPHO), address(factory)); + UtilsLib.computePreLiquidationAddress(address(MORPHO), address(factory), id, preLiquidationParams); assert(address(preLiquidation) == preLiquidationAddress); } From 2ad38b7e5602aaba471038a4322f2677a8aa6cd4 Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:31:41 +0200 Subject: [PATCH 154/182] doc: natspec improvement --- src/interfaces/IPreLiquidationFactory.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index c8619d5..b399045 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -15,8 +15,7 @@ interface IPreLiquidationFactory { /// @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 if a PreLiquidation with these exact same - /// parameters already exists. + /// @dev Warning: This function will revert without data if a PreLiquidation with these exact same parameters already exists. function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); From c809d5b75e932e7105bd889e4228da3bb6d23c3b Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:33:56 +0200 Subject: [PATCH 155/182] refactor: remove return variable --- src/libraries/periphery/UtilsLib.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/periphery/UtilsLib.sol b/src/libraries/periphery/UtilsLib.sol index 8688b97..2c3f1ab 100644 --- a/src/libraries/periphery/UtilsLib.sol +++ b/src/libraries/periphery/UtilsLib.sol @@ -11,10 +11,9 @@ library UtilsLib { address factory, Id id, PreLiquidationParams memory preLiquidationParams - ) internal pure returns (address preLiquidationAddress) { + ) internal pure returns (address) { bytes32 init_code_hash = keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(morpho, id, preLiquidationParams))); - preLiquidationAddress = - address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, uint256(0), init_code_hash))))); + return address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, uint256(0), init_code_hash))))); } } From 66af3ccba25a025ee256b498e836c3054af54cab Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:41:40 +0200 Subject: [PATCH 156/182] refactor: rename UtilsLib --- src/PreLiquidationFactory.sol | 1 - .../{UtilsLib.sol => PreLiquidationAddressLib.sol} | 2 +- test/PreLiquidationFactoryTest.sol | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/libraries/periphery/{UtilsLib.sol => PreLiquidationAddressLib.sol} (95%) diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index d185067..dc4bb66 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -7,7 +7,6 @@ import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidatio import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; -import {UtilsLib} from "./libraries/periphery/UtilsLib.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs diff --git a/src/libraries/periphery/UtilsLib.sol b/src/libraries/periphery/PreLiquidationAddressLib.sol similarity index 95% rename from src/libraries/periphery/UtilsLib.sol rename to src/libraries/periphery/PreLiquidationAddressLib.sol index 2c3f1ab..068df48 100644 --- a/src/libraries/periphery/UtilsLib.sol +++ b/src/libraries/periphery/PreLiquidationAddressLib.sol @@ -5,7 +5,7 @@ import {PreLiquidation} from "../../PreLiquidation.sol"; import {PreLiquidationParams} from "../../interfaces/IPreLiquidation.sol"; import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; -library UtilsLib { +library PreLiquidationAddressLib { function computePreLiquidationAddress( address morpho, address factory, diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 8a6c5ae..e448f6d 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -5,7 +5,7 @@ import "./BaseTest.sol"; import {PreLiquidationParams, IPreLiquidation} from "../src/interfaces/IPreLiquidation.sol"; import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; -import {UtilsLib} from "../src/libraries/periphery/UtilsLib.sol"; +import {PreLiquidationAddressLib} from "../src/libraries/periphery/PreLiquidationAddressLib.sol"; contract PreLiquidationFactoryTest is BaseTest { using MarketParamsLib for MarketParams; @@ -46,8 +46,9 @@ contract PreLiquidationFactoryTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); - address preLiquidationAddress = - UtilsLib.computePreLiquidationAddress(address(MORPHO), address(factory), id, preLiquidationParams); + address preLiquidationAddress = PreLiquidationAddressLib.computePreLiquidationAddress( + address(MORPHO), address(factory), id, preLiquidationParams + ); assert(address(preLiquidation) == preLiquidationAddress); } From b4558b644d736b12c3b3fc10f2a4e8115f940c8a Mon Sep 17 00:00:00 2001 From: peyha Date: Mon, 23 Sep 2024 18:49:49 +0200 Subject: [PATCH 157/182] doc: lib natspec --- src/libraries/periphery/PreLiquidationAddressLib.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libraries/periphery/PreLiquidationAddressLib.sol b/src/libraries/periphery/PreLiquidationAddressLib.sol index 068df48..f415bc8 100644 --- a/src/libraries/periphery/PreLiquidationAddressLib.sol +++ b/src/libraries/periphery/PreLiquidationAddressLib.sol @@ -6,6 +6,13 @@ import {PreLiquidationParams} from "../../interfaces/IPreLiquidation.sol"; import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; library PreLiquidationAddressLib { + /// @notice Computes the create2 address of the PreLiquidation contract generated by the `factory` + /// for a specific `morpho` market (determined by `id`) with `preLiquidationParams` PreLiquidation parameters. + /// @param morpho Morpho contract address. + /// @param factory PreLiquidationFactory contract address. + /// @param id Morpho market id for the PreLiquidation contract. + /// @param preLiquidationParams PreLiquidation parameters. + /// @return preLiquidationAddress The address of this PreLiquidation contract. function computePreLiquidationAddress( address morpho, address factory, From e129848ad13592a4c9490ec9a22af135dc7fa576 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Mon, 23 Sep 2024 21:01:43 +0200 Subject: [PATCH 158/182] refactor: factorize immutable getters --- src/PreLiquidation.sol | 39 ++++++++++++++++++++++-------- src/interfaces/IPreLiquidation.sol | 33 ++++++++----------------- test/PreLiquidationFactoryTest.sol | 24 ++++++++++-------- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index f392f53..2643b8c 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -32,17 +32,36 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { Id public immutable ID; // Market parameters - address public immutable LOAN_TOKEN; - address public immutable COLLATERAL_TOKEN; - address public immutable ORACLE; - address public immutable IRM; - uint256 public immutable LLTV; + 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 public immutable PRE_LLTV; - uint256 public immutable CLOSE_FACTOR; - uint256 public immutable PRE_LIQUIDATION_INCENTIVE; - address public immutable PRE_LIQUIDATION_ORACLE; + uint256 internal immutable PRE_LLTV; + uint256 internal immutable CLOSE_FACTOR; + uint256 internal immutable PRE_LIQUIDATION_INCENTIVE; + address internal immutable PRE_LIQUIDATION_ORACLE; + + function getMarketParams() public view returns (MarketParams memory) { + return MarketParams({ + loanToken: LOAN_TOKEN, + collateralToken: COLLATERAL_TOKEN, + oracle: ORACLE, + irm: IRM, + lltv: LLTV + }); + } + + function getPreLiquidationParams() external view returns (PreLiquidationParams memory) { + return PreLiquidationParams({ + preLltv: PRE_LLTV, + closeFactor: CLOSE_FACTOR, + preLiquidationIncentive: PRE_LIQUIDATION_INCENTIVE, + preLiquidationOracle: PRE_LIQUIDATION_ORACLE + }); + } constructor(Id id, PreLiquidationParams memory preLiquidationParams, address morpho) { require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); @@ -71,7 +90,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); - MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); + MarketParams memory marketParams = getMarketParams(); Market memory market = MORPHO.market(ID); Position memory position = MORPHO.position(ID, borrower); MORPHO.accrueInterest(marketParams); diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index acabc9b..e5cd841 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >= 0.5.0; -import {Id, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; +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. +/// - preLiquidationIncentive, the proportion of the liquidated debt value given to the liquidator in collateral token. +/// - preLiquidationOracle, the oracle used to assess whether or not a position can be preliquidated. struct PreLiquidationParams { uint256 preLltv; uint256 closeFactor; @@ -21,29 +26,11 @@ interface IPreLiquidation { /// @notice The id of the Morpho Market specific to the PreLiquidation contract. function ID() external view returns (Id); - /// @notice The Pre LLTV of the PreLiquidation contract corresponding to - /// the maximum LTV of a position before allowing preliquidation. - function PRE_LLTV() external view returns (uint256); - /// @notice The close factor of the PreLiquidation contract corresponding to - /// the maximum proportion of debt that can be pre-liquidated at once. - function CLOSE_FACTOR() external view returns (uint256); - /// @notice The PreLiquidation incentive of the PreLiquidation contract corresponding to - /// the proportion of the liquidated debt value given to the liquidator in collateral token. - function PRE_LIQUIDATION_INCENTIVE() external view returns (uint256); - /// @notice The PreLiquidation oracle of the PreLiquidation contract corresponding to - /// the oracle used to assess whether or not a position can be preliquidated. - function PRE_LIQUIDATION_ORACLE() external view returns (address); + /// @notice The Morpho market parameters specific to the PreLiquidation contract. + function getMarketParams() external returns (MarketParams memory); - /// @notice The loan token of the Morpho Market specific to the PreLiquidation contract. - function LOAN_TOKEN() external view returns (address); - /// @notice The collateral token of the Morpho Market specific to the PreLiquidation contract. - function COLLATERAL_TOKEN() external view returns (address); - /// @notice The oracle address of the Morpho Market specific to the PreLiquidation contract. - function ORACLE() external view returns (address); - /// @notice The IRM address of the Morpho Market specific to the PreLiquidation contract. - function IRM() external view returns (address); - /// @notice The LLTV of the Morpho Market specific to the PreLiquidation contract. - function LLTV() external view returns (uint256); + /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. + function getPreLiquidationParams() external view returns (PreLiquidationParams memory); /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 93368c4..e6eaf73 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -27,16 +27,20 @@ contract PreLiquidationFactoryTest is BaseTest { IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); assert(preLiquidation.MORPHO() == MORPHO); - - assert(preLiquidation.PRE_LLTV() == preLiquidationParams.preLltv); - assert(preLiquidation.CLOSE_FACTOR() == preLiquidationParams.closeFactor); - assert(preLiquidation.PRE_LIQUIDATION_INCENTIVE() == preLiquidationParams.preLiquidationIncentive); - - assert(preLiquidation.LLTV() == marketParams.lltv); - assert(preLiquidation.COLLATERAL_TOKEN() == marketParams.collateralToken); - assert(preLiquidation.LOAN_TOKEN() == marketParams.loanToken); - assert(preLiquidation.IRM() == marketParams.irm); - assert(preLiquidation.ORACLE() == marketParams.oracle); + assert(Id.unwrap(preLiquidation.ID()) == Id.unwrap(id)); + + PreLiquidationParams memory preLiqParams = preLiquidation.getPreLiquidationParams(); + assert(preLiqParams.preLltv == preLiquidationParams.preLltv); + assert(preLiqParams.closeFactor == preLiquidationParams.closeFactor); + assert(preLiqParams.preLiquidationIncentive == preLiquidationParams.preLiquidationIncentive); + assert(preLiqParams.preLiquidationOracle == preLiquidationParams.preLiquidationOracle); + + MarketParams memory preLiqMarketParams = preLiquidation.getMarketParams(); + assert(preLiqMarketParams.loanToken == marketParams.loanToken); + assert(preLiqMarketParams.collateralToken == marketParams.collateralToken); + assert(preLiqMarketParams.oracle == marketParams.oracle); + assert(preLiqMarketParams.irm == marketParams.irm); + assert(preLiqMarketParams.lltv == marketParams.lltv); bytes32 preLiquidationId = factory.getPreLiquidationId(id, preLiquidationParams); assert(factory.preLiquidations(preLiquidationId) == preLiquidation); From 5352c32e9b291696f4ccbe59eaa78c46cfe02650 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 10:19:24 +0200 Subject: [PATCH 159/182] doc: improve natspec --- src/libraries/periphery/PreLiquidationAddressLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/periphery/PreLiquidationAddressLib.sol b/src/libraries/periphery/PreLiquidationAddressLib.sol index f415bc8..b2a716f 100644 --- a/src/libraries/periphery/PreLiquidationAddressLib.sol +++ b/src/libraries/periphery/PreLiquidationAddressLib.sol @@ -7,7 +7,7 @@ import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; library PreLiquidationAddressLib { /// @notice Computes the create2 address of the PreLiquidation contract generated by the `factory` - /// for a specific `morpho` market (determined by `id`) with `preLiquidationParams` PreLiquidation parameters. + /// for a specific Morpho market `id` with the pre-liquidation parameters `preLiquidationParams`. /// @param morpho Morpho contract address. /// @param factory PreLiquidationFactory contract address. /// @param id Morpho market id for the PreLiquidation contract. From a422573c1792e36b177debf3ec5c816e85989e36 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 10:51:41 +0200 Subject: [PATCH 160/182] doc: natspec --- src/interfaces/IPreLiquidationFactory.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index b399045..9ba7b6b 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -15,7 +15,8 @@ interface IPreLiquidationFactory { /// @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 a PreLiquidation with these exact same parameters already exists. + /// @dev Warning: This function will revert without data if a PreLiquidation with these exact same parameters already exists + /// because each PreLiquidation contract's address is deterministically computed with its parameters using create2. function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); From 7f009ee13886f0ab04ec658b7e1366d9b86c951b Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 11:31:52 +0200 Subject: [PATCH 161/182] doc: natspec --- src/interfaces/IPreLiquidationFactory.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 9ba7b6b..d831eaf 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -15,8 +15,7 @@ interface IPreLiquidationFactory { /// @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 a PreLiquidation with these exact same parameters already exists - /// because each PreLiquidation contract's address is deterministically computed with its parameters using create2. + /// @dev Warning: This function will revert without data if the pre-liquidation already exists. function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); From aa84ba1b7c2927a74575be95795a9aafcf99665b Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 24 Sep 2024 11:34:12 +0200 Subject: [PATCH 162/182] refactor: rename to pre liquidation incentive factor --- src/PreLiquidation.sol | 10 +++++----- src/interfaces/IPreLiquidation.sol | 4 ++-- test/PreLiquidationFactoryTest.sol | 2 +- test/PreLiquidationTest.sol | 7 ++++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 2643b8c..9448e3a 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -41,7 +41,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { // Pre-liquidation parameters uint256 internal immutable PRE_LLTV; uint256 internal immutable CLOSE_FACTOR; - uint256 internal immutable PRE_LIQUIDATION_INCENTIVE; + uint256 internal immutable PRE_LIQUIDATION_INCENTIVE_FACTOR; address internal immutable PRE_LIQUIDATION_ORACLE; function getMarketParams() public view returns (MarketParams memory) { @@ -58,7 +58,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { return PreLiquidationParams({ preLltv: PRE_LLTV, closeFactor: CLOSE_FACTOR, - preLiquidationIncentive: PRE_LIQUIDATION_INCENTIVE, + preLiquidationIncentiveFactor: PRE_LIQUIDATION_INCENTIVE_FACTOR, preLiquidationOracle: PRE_LIQUIDATION_ORACLE }); } @@ -80,7 +80,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { PRE_LLTV = preLiquidationParams.preLltv; CLOSE_FACTOR = preLiquidationParams.closeFactor; - PRE_LIQUIDATION_INCENTIVE = preLiquidationParams.preLiquidationIncentive; + PRE_LIQUIDATION_INCENTIVE_FACTOR = preLiquidationParams.preLiquidationIncentiveFactor; PRE_LIQUIDATION_ORACLE = preLiquidationParams.preLiquidationOracle; ERC20(marketParams.loanToken).safeApprove(morpho, type(uint256).max); @@ -99,12 +99,12 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { if (seizedAssets > 0) { uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE); - repaidShares = seizedAssetsQuoted.wDivUp(PRE_LIQUIDATION_INCENTIVE).toSharesUp( + repaidShares = seizedAssetsQuoted.wDivUp(PRE_LIQUIDATION_INCENTIVE_FACTOR).toSharesUp( market.totalBorrowAssets, market.totalBorrowShares ); } else { seizedAssets = repaidShares.toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares).wMulDown( - PRE_LIQUIDATION_INCENTIVE + PRE_LIQUIDATION_INCENTIVE_FACTOR ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index e5cd841..7ebccab 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -6,12 +6,12 @@ import {Id, IMorpho, MarketParams} from "../../lib/morpho-blue/src/interfaces/IM /// @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. -/// - preLiquidationIncentive, the proportion of the liquidated debt value given to the liquidator in collateral token. +/// - 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 preLiquidationIncentive; + uint256 preLiquidationIncentiveFactor; address preLiquidationOracle; } diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index e6eaf73..ffb6a8d 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -32,7 +32,7 @@ contract PreLiquidationFactoryTest is BaseTest { PreLiquidationParams memory preLiqParams = preLiquidation.getPreLiquidationParams(); assert(preLiqParams.preLltv == preLiquidationParams.preLltv); assert(preLiqParams.closeFactor == preLiquidationParams.closeFactor); - assert(preLiqParams.preLiquidationIncentive == preLiquidationParams.preLiquidationIncentive); + assert(preLiqParams.preLiquidationIncentiveFactor == preLiquidationParams.preLiquidationIncentiveFactor); assert(preLiqParams.preLiquidationOracle == preLiquidationParams.preLiquidationOracle); MarketParams memory preLiqMarketParams = preLiquidation.getMarketParams(); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index e5b9435..ba4cd3f 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -41,7 +41,8 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { ) public returns (uint256) { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); - preLiquidationParams.preLiquidationIncentive = bound(preLiquidationParams.preLiquidationIncentive, 1, WAD / 10); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -106,7 +107,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( - preLiquidationParams.preLiquidationIncentive + preLiquidationParams.preLiquidationIncentiveFactor ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); @@ -132,7 +133,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); uint256 seizedAssets = uint256(repayableShares).toAssetsDown(market.totalBorrowAssets, market.totalBorrowShares) - .wMulDown(preLiquidationParams.preLiquidationIncentive).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + .wMulDown(preLiquidationParams.preLiquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); vm.assume(seizedAssets > 0); bytes memory data = abi.encode(this.testPreLiquidationCallback.selector, hex""); From 148db67921ae8de43ef4c5b4c747b8d6ac104b45 Mon Sep 17 00:00:00 2001 From: Quentin Garchery Date: Tue, 24 Sep 2024 11:43:38 +0200 Subject: [PATCH 163/182] refactor: rename getter to remove get prefix --- src/PreLiquidation.sol | 38 ++++++++++++++---------------- src/interfaces/IPreLiquidation.sol | 4 ++-- test/PreLiquidationFactoryTest.sol | 4 ++-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 9448e3a..13f7b3b 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -44,7 +44,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { uint256 internal immutable PRE_LIQUIDATION_INCENTIVE_FACTOR; address internal immutable PRE_LIQUIDATION_ORACLE; - function getMarketParams() public view returns (MarketParams memory) { + function marketParams() public view returns (MarketParams memory) { return MarketParams({ loanToken: LOAN_TOKEN, collateralToken: COLLATERAL_TOKEN, @@ -54,7 +54,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { }); } - function getPreLiquidationParams() external view returns (PreLiquidationParams memory) { + function preLiquidationParams() external view returns (PreLiquidationParams memory) { return PreLiquidationParams({ preLltv: PRE_LLTV, closeFactor: CLOSE_FACTOR, @@ -63,37 +63,36 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { }); } - constructor(Id id, PreLiquidationParams memory preLiquidationParams, address morpho) { + constructor(Id id, PreLiquidationParams memory _preLiquidationParams, address morpho) { require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); - MarketParams memory marketParams = IMorpho(morpho).idToMarketParams(id); - require(preLiquidationParams.preLltv < marketParams.lltv, ErrorsLib.PreLltvTooHigh()); + MarketParams memory _marketParams = IMorpho(morpho).idToMarketParams(id); + require(_preLiquidationParams.preLltv < _marketParams.lltv, ErrorsLib.PreLltvTooHigh()); MORPHO = IMorpho(morpho); ID = id; - LOAN_TOKEN = marketParams.loanToken; - COLLATERAL_TOKEN = marketParams.collateralToken; - ORACLE = marketParams.oracle; - IRM = marketParams.irm; - LLTV = marketParams.lltv; + 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; + PRE_LLTV = _preLiquidationParams.preLltv; + CLOSE_FACTOR = _preLiquidationParams.closeFactor; + PRE_LIQUIDATION_INCENTIVE_FACTOR = _preLiquidationParams.preLiquidationIncentiveFactor; + PRE_LIQUIDATION_ORACLE = _preLiquidationParams.preLiquidationOracle; - ERC20(marketParams.loanToken).safeApprove(morpho, type(uint256).max); + ERC20(LOAN_TOKEN).safeApprove(morpho, type(uint256).max); } function preLiquidate(address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); - MarketParams memory marketParams = getMarketParams(); Market memory market = MORPHO.market(ID); Position memory position = MORPHO.position(ID, borrower); - MORPHO.accrueInterest(marketParams); + MORPHO.accrueInterest(marketParams()); require(_isPreLiquidatable(collateralPrice, position, market), ErrorsLib.NotPreLiquidatablePosition()); if (seizedAssets > 0) { @@ -114,7 +113,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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); + (uint256 repaidAssets,) = MORPHO.repay(marketParams(), 0, repaidShares, borrower, callbackData); emit EventsLib.PreLiquidate(ID, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets); } @@ -124,8 +123,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { (uint256 seizedAssets, address borrower, address liquidator, bytes memory data) = abi.decode(callbackData, (uint256, address, address, bytes)); - MarketParams memory marketParams = MarketParams(LOAN_TOKEN, COLLATERAL_TOKEN, ORACLE, IRM, LLTV); - MORPHO.withdrawCollateral(marketParams, seizedAssets, borrower, liquidator); + MORPHO.withdrawCollateral(marketParams(), seizedAssets, borrower, liquidator); if (data.length > 0) { IPreLiquidationCallback(liquidator).onPreLiquidate(repaidAssets, data); diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 7ebccab..dc74ea7 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -27,10 +27,10 @@ interface IPreLiquidation { function ID() external view returns (Id); /// @notice The Morpho market parameters specific to the PreLiquidation contract. - function getMarketParams() external returns (MarketParams memory); + function marketParams() external returns (MarketParams memory); /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. - function getPreLiquidationParams() external view returns (PreLiquidationParams memory); + function preLiquidationParams() external view returns (PreLiquidationParams memory); /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index ffb6a8d..c76345d 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -29,13 +29,13 @@ contract PreLiquidationFactoryTest is BaseTest { assert(preLiquidation.MORPHO() == MORPHO); assert(Id.unwrap(preLiquidation.ID()) == Id.unwrap(id)); - PreLiquidationParams memory preLiqParams = preLiquidation.getPreLiquidationParams(); + PreLiquidationParams memory preLiqParams = preLiquidation.preLiquidationParams(); assert(preLiqParams.preLltv == preLiquidationParams.preLltv); assert(preLiqParams.closeFactor == preLiquidationParams.closeFactor); assert(preLiqParams.preLiquidationIncentiveFactor == preLiquidationParams.preLiquidationIncentiveFactor); assert(preLiqParams.preLiquidationOracle == preLiquidationParams.preLiquidationOracle); - MarketParams memory preLiqMarketParams = preLiquidation.getMarketParams(); + MarketParams memory preLiqMarketParams = preLiquidation.marketParams(); assert(preLiqMarketParams.loanToken == marketParams.loanToken); assert(preLiqMarketParams.collateralToken == marketParams.collateralToken); assert(preLiqMarketParams.oracle == marketParams.oracle); From 92e975ea3a56fd0e82f733f3c764f9cf8fbaf0c5 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 11:52:30 +0200 Subject: [PATCH 164/182] feat: solidity version --- src/libraries/ErrorsLib.sol | 2 +- src/libraries/EventsLib.sol | 2 +- test/PreLiquidationTest.sol | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 3c10aa4..1e9b294 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.27; import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index f7a63df..284884f 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.0; import {Id, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index e5b9435..bcb5df0 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.27; +pragma solidity ^0.8.0; + import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; From 5f579a38d8478344eb86d89b207f3bb56c118afb Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 11:54:47 +0200 Subject: [PATCH 165/182] style: fmt --- test/PreLiquidationTest.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index bcb5df0..30ae59f 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; - import "../lib/forge-std/src/Test.sol"; import "../lib/forge-std/src/console.sol"; From 0fc011b7fa733b8868cfc1e3c730db116342db71 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 14:28:48 +0200 Subject: [PATCH 166/182] doc: preliquidation natspec --- src/PreLiquidation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 38a72b2..1128dac 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -19,7 +19,7 @@ import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCal /// @title PreLiquidation /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Pre Liquidation Contract for Morpho +/// @notice The Fixed LI, Fixed CF pre-liquidation contract for Morpho. contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using MarketParamsLib for MarketParams; using UtilsLib for uint256; From 1ca70cf88abea58992b9af067b5db5d8b855360e Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 14:32:40 +0200 Subject: [PATCH 167/182] refactor: event naming --- src/libraries/EventsLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 284884f..4d2ab6a 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -18,5 +18,5 @@ library EventsLib { uint256 seizedAssets ); - event CreatePreLiquidation(address indexed preLiquidation, Id id, PreLiquidationParams preLiquidationParams); + event CreatePreLiquidation(address indexed preLiquidation, Id marketId, PreLiquidationParams preLiquidationParams); } From 6c953e2b33d9dc42dacf9612853b39b26954f678 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 14:38:03 +0200 Subject: [PATCH 168/182] build: remove api key from ci and improve foundry config --- .github/workflows/foundry.yml | 2 -- foundry.toml | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index d40ea4c..38d2529 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -29,5 +29,3 @@ jobs: - name: Run forge tests run: forge test -vvv - env: - ALCHEMY_KEY: ${{ secrets.ALCHEMY_KEY }} diff --git a/foundry.toml b/foundry.toml index 94c3f0f..19eb2c7 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,8 +3,6 @@ src = "src" out = "out" libs = ["lib"] via_ir = true - -[profile.default.rpc_endpoints] -mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" +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 From ff50b6e55f27c6f9d16ddd12dcb0d286fef04391 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 17:31:14 +0200 Subject: [PATCH 169/182] doc: move natspec from interface to contract --- src/PreLiquidation.sol | 17 ++++++++++++++--- src/PreLiquidationFactory.sol | 7 +++++-- src/interfaces/IPreLiquidation.sol | 17 +++++------------ src/interfaces/IPreLiquidationFactory.sol | 9 +++------ 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 1128dac..3fe555d 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -28,7 +28,10 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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 @@ -44,6 +47,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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, @@ -53,7 +57,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { lltv: LLTV }); } - + /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. function preLiquidationParams() external view returns (PreLiquidationParams memory) { return PreLiquidationParams({ preLltv: PRE_LLTV, @@ -85,7 +89,14 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ERC20(LOAN_TOKEN).safeApprove(morpho, type(uint256).max); } - + /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the + /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` + /// function with the given `data`. + /// @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()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); @@ -107,7 +118,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } - // Check if liquidation is ok with close factor + // Check if preliquidation is ok with close factor uint256 borrowerShares = position.borrowShares; uint256 repayableShares = borrowerShares.wMulDown(CLOSE_FACTOR); require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index dc4bb66..349fb4b 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -15,7 +15,7 @@ import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ - /// @inheritdoc IPreLiquidationFactory + /// @notice The address of the Morpho contract. IMorpho public immutable MORPHO; /* CONSTRUCTOR */ @@ -29,7 +29,10 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* EXTERNAL */ - /// @inheritdoc IPreLiquidationFactory + /// @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) diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index dc74ea7..0d07638 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -20,25 +20,18 @@ struct PreLiquidationParams { /// @custom:contact security@morpho.org /// @notice Interface of PreLiquidation. interface IPreLiquidation { - /// @notice Morpho's address. + function MORPHO() external view returns (IMorpho); - /// @notice The id of the Morpho Market specific to the PreLiquidation contract. + function ID() external view returns (Id); - /// @notice The Morpho market parameters specific to the PreLiquidation contract. + function marketParams() external returns (MarketParams memory); - /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. + function preLiquidationParams() external view returns (PreLiquidationParams memory); - /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the - /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` - /// function with the given `data`. - /// @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; } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index d831eaf..d2ce5b9 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -3,19 +3,16 @@ pragma solidity >= 0.5.0; import {Id, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IPreLiquidation, PreLiquidationParams} from "./IPreLiquidation.sol"; - +import {PreLiquidationFactory} from "../PreLiquidationFactory.sol"; /// @title IPreLiquidationFactory /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Interface of PreLiquidation's factory. interface IPreLiquidationFactory { - /// @notice The address of the Morpho contract. + function MORPHO() external view returns (IMorpho); - /// @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 preLiquidation); From 66a15735d18f4d3acd39e4d22ad024e88b3b2bc3 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 17:33:13 +0200 Subject: [PATCH 170/182] style: renaming event --- src/libraries/EventsLib.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 4d2ab6a..5dfc298 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -10,7 +10,7 @@ import {PreLiquidationParams} from "../interfaces/IPreLiquidation.sol"; /// @notice Library exposing events. library EventsLib { event PreLiquidate( - Id indexed marketId, + Id indexed id, address indexed liquidator, address indexed borrower, uint256 repaidAssets, @@ -18,5 +18,5 @@ library EventsLib { uint256 seizedAssets ); - event CreatePreLiquidation(address indexed preLiquidation, Id marketId, PreLiquidationParams preLiquidationParams); + event CreatePreLiquidation(address indexed preLiquidation, Id id, PreLiquidationParams preLiquidationParams); } From 34eba761f0e0f1e57c593f4daef1918fe439d312 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 17:35:12 +0200 Subject: [PATCH 171/182] refactor: fmt --- src/PreLiquidation.sol | 2 ++ src/interfaces/IPreLiquidation.sol | 5 ----- src/interfaces/IPreLiquidationFactory.sol | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 3fe555d..f3e5a43 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -58,6 +58,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { }); } /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. + function preLiquidationParams() external view returns (PreLiquidationParams memory) { return PreLiquidationParams({ preLltv: PRE_LLTV, @@ -97,6 +98,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { /// @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()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index 0d07638..bae1618 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -20,18 +20,13 @@ struct PreLiquidationParams { /// @custom:contact security@morpho.org /// @notice Interface of PreLiquidation. 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; } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index d2ce5b9..9f9a863 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -8,11 +8,10 @@ import {PreLiquidationFactory} from "../PreLiquidationFactory.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Interface of PreLiquidation's factory. -interface IPreLiquidationFactory { +interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); - function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); From 9f718c5d187af3537eb0a6698e34258361d54ef9 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 18:20:09 +0200 Subject: [PATCH 172/182] refactor: move accrue interest before market --- src/PreLiquidation.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index f3e5a43..907ff5c 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -15,7 +15,6 @@ 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 security@morpho.org @@ -103,9 +102,10 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.InconsistentInput()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); + MORPHO.accrueInterest(marketParams()); Market memory market = MORPHO.market(ID); Position memory position = MORPHO.position(ID, borrower); - MORPHO.accrueInterest(marketParams()); + require(_isPreLiquidatable(collateralPrice, position, market), ErrorsLib.NotPreLiquidatablePosition()); if (seizedAssets > 0) { From 30d678683bbab0fe7d3627eec7f6cf7cd520d2d4 Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 18:20:28 +0200 Subject: [PATCH 173/182] test: implement interest test --- test/PreLiquidationTest.sol | 102 +++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 26 deletions(-) diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 7b154a4..fb0f37a 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -38,37 +38,21 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 collateralAmount, uint256 borrowAmount, address liquidator - ) public returns (uint256) { - preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); - preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); - preLiquidationParams.preLiquidationIncentiveFactor = - bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); - preLiquidationParams.preLiquidationOracle = marketParams.oracle; - - collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); + ) public{ preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); - uint256 collateralPrice = IOracle(marketParams.oracle).price(); - uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown( - IOracle(marketParams.oracle).price(), ORACLE_PRICE_SCALE - ).wMulDown(marketParams.lltv); - uint256 borrowPreLiquidationThreshold = - collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv); - borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); - - deal(address(loanToken), SUPPLIER, borrowAmount); + loanToken.mint(SUPPLIER, borrowAmount); vm.prank(SUPPLIER); - MORPHO.supply(marketParams, uint128(borrowAmount), 0, SUPPLIER, hex""); + MORPHO.supply(marketParams, borrowAmount, 0, SUPPLIER, hex""); - deal(address(collateralToken), BORROWER, collateralAmount); + collateralToken.mint(BORROWER, collateralAmount); vm.startPrank(BORROWER); MORPHO.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); vm.startPrank(liquidator); - deal(address(loanToken), liquidator, type(uint256).max); + loanToken.mint(liquidator, type(uint128).max); loanToken.approve(address(preLiquidation), type(uint256).max); - collateralToken.approve(address(preLiquidation), type(uint256).max); vm.expectRevert(ErrorsLib.NotPreLiquidatablePosition.selector); preLiquidation.preLiquidate(BORROWER, 0, 1, hex""); @@ -78,7 +62,6 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { MORPHO.setAuthorization(address(preLiquidation), true); vm.stopPrank(); - return collateralPrice; } function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { @@ -98,8 +81,22 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 collateralAmount, uint256 borrowAmount ) public virtual { - uint256 collateralPrice = - preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, LIQUIDATOR); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + preLiquidationParams.preLiquidationOracle = marketParams.oracle; + + collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown( + IOracle(marketParams.oracle).price(), ORACLE_PRICE_SCALE + ).wMulDown(marketParams.lltv); + uint256 borrowPreLiquidationThreshold = + collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv); + borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); + + preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, LIQUIDATOR); vm.startPrank(LIQUIDATOR); Position memory position = MORPHO.position(id, BORROWER); @@ -125,8 +122,24 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 collateralAmount, uint256 borrowAmount ) public virtual { - uint256 collateralPrice = - preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, address(this)); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + preLiquidationParams.preLiquidationOracle = marketParams.oracle; + + collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); + + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + uint256 borrowLiquidationThreshold = collateralAmount.mulDivDown( + IOracle(marketParams.oracle).price(), ORACLE_PRICE_SCALE + ).wMulDown(marketParams.lltv); + uint256 borrowPreLiquidationThreshold = + collateralAmount.mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv); + + borrowAmount = bound(borrowAmount, borrowPreLiquidationThreshold + 1, borrowLiquidationThreshold); + + preparePreLiquidation(preLiquidationParams, collateralAmount, borrowAmount, address(this)); Position memory position = MORPHO.position(marketParams.id(), BORROWER); Market memory market = MORPHO.market(marketParams.id()); @@ -146,4 +159,41 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { (selector,) = abi.decode(data, (bytes4, bytes)); require(selector == this.testPreLiquidationCallback.selector); } + + function testPreLiquidationWithInterest( + PreLiquidationParams memory preLiquidationParams, + uint256 collateralAmount + ) public { + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + preLiquidationParams.preLiquidationOracle = marketParams.oracle; + + collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); + + uint256 collateralPrice = IOracle(marketParams.oracle).price(); + uint256 borrowThreshold = + uint256(collateralAmount).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv)-1; + preparePreLiquidation(preLiquidationParams, collateralAmount, borrowThreshold, LIQUIDATOR); + + vm.startPrank(LIQUIDATOR); + Position memory position = MORPHO.position(id, BORROWER); + Market memory m = MORPHO.market(id); + + uint256 repayableShares = position.borrowShares.wMulDown(preLiquidationParams.closeFactor); + uint256 seizedAssets = uint256(repayableShares).toAssetsDown(m.totalBorrowAssets, m.totalBorrowShares).wMulDown( + preLiquidationParams.preLiquidationIncentiveFactor + ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + vm.assume(seizedAssets > 0); + + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NotPreLiquidatablePosition.selector)); + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); + + vm.warp(block.timestamp + 12); + vm.roll(block.number + 1); + + preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); + + } } From cf57d42b47b728e95707148c7fa37ec9388110fd Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 18:43:01 +0200 Subject: [PATCH 174/182] test: improvement --- src/PreLiquidation.sol | 2 +- test/PreLiquidationTest.sol | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 907ff5c..1abda70 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -56,8 +56,8 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { lltv: LLTV }); } - /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. + /// @notice The pre-liquidation parameters specific to the PreLiquidation contract. function preLiquidationParams() external view returns (PreLiquidationParams memory) { return PreLiquidationParams({ preLltv: PRE_LLTV, diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index fb0f37a..e83665f 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -27,6 +27,8 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { PreLiquidationFactory internal factory; IPreLiquidation internal preLiquidation; + event CallbackReached(); + function setUp() public override { super.setUp(); @@ -71,9 +73,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { factory.createPreLiquidation(id, preLiquidationParams); } - function testNonexistentMarket(Id _id, PreLiquidationParams memory preLiquidationParams) public virtual { + function testNonexistentMarket(PreLiquidationParams memory preLiquidationParams) public virtual { vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector)); - factory.createPreLiquidation(_id, preLiquidationParams); + factory.createPreLiquidation(Id.wrap(bytes32(0)), preLiquidationParams); } function testPreLiquidation( @@ -151,13 +153,20 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { bytes memory data = abi.encode(this.testPreLiquidationCallback.selector, hex""); + vm.recordLogs(); preLiquidation.preLiquidate(BORROWER, 0, repayableShares, data); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + assert(entries.length == 7); + assert(entries[3].topics[0] == keccak256("CallbackReached()")); } - function onPreLiquidate(uint256, bytes memory data) external pure { + function onPreLiquidate(uint256, bytes memory data) external{ bytes4 selector; (selector,) = abi.decode(data, (bytes4, bytes)); require(selector == this.testPreLiquidationCallback.selector); + + emit CallbackReached(); } function testPreLiquidationWithInterest( From 9d47d9582551e7524440125ca7b6fd0418d7afec Mon Sep 17 00:00:00 2001 From: peyha Date: Tue, 24 Sep 2024 19:17:15 +0200 Subject: [PATCH 175/182] doc: natspec improvements --- .gitignore | 2 -- src/PreLiquidation.sol | 27 +++++++++++++++---- src/PreLiquidationFactory.sol | 2 +- src/interfaces/IPreLiquidation.sol | 4 --- src/interfaces/IPreLiquidationCallback.sol | 4 +-- src/interfaces/IPreLiquidationFactory.sol | 4 --- src/libraries/ErrorsLib.sol | 4 +-- .../periphery/PreLiquidationAddressLib.sol | 10 +++---- test/PreLiquidationTest.sol | 19 ++++++------- 9 files changed, 40 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 021292d..85198aa 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,3 @@ docs/ # Dotenv file .env - -.prettierignore diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 1abda70..16a177f 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -19,6 +19,7 @@ import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCal /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Fixed LI, Fixed CF pre-liquidation contract for Morpho. + contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using MarketParamsLib for MarketParams; using UtilsLib for uint256; @@ -67,6 +68,12 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { }); } + /* 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); @@ -89,15 +96,15 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ERC20(LOAN_TOKEN).safeApprove(morpho, type(uint256).max); } - /// @notice Preliquidates the given `repaidShares of debt asset or seize the given `seizedAssets`of collateral on the - /// contract's Morpho market of the given `borrower`'s position, optionally calling back the caller's `onPreLiquidate` - /// function with the given `data`. + + /* 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()); uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); @@ -120,7 +127,6 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); } - // Check if preliquidation is ok with close factor uint256 borrowerShares = position.borrowShares; uint256 repayableShares = borrowerShares.wMulDown(CLOSE_FACTOR); require(repaidShares <= repayableShares, ErrorsLib.PreLiquidationTooLarge(repaidShares, repayableShares)); @@ -131,6 +137,11 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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) = @@ -145,6 +156,12 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ERC20(LOAN_TOKEN).safeTransferFrom(liquidator, address(this), repaidAssets); } + /// @notice Assess whether a `position` on some Morpho `market` is pre-liquidatable + /// for a specific `collateralPrice` (fetched by calling PRE_LIQUIDATION_ORACLE). + /// @param collateralPrice the price of the collateral quoted in loan. + /// @param position The position on Morpho. + /// @param market The asset totals on the Morpho market. + /// @return isPreLiquidatable A boolean which is true if the position is pre-liquidatable and otherwise false. function _isPreLiquidatable(uint256 collateralPrice, Position memory position, Market memory market) internal view diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 349fb4b..768b2b9 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -11,7 +11,7 @@ import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Pre Liquidation Factory Contract for Morpho +/// @notice The Fixed LI, Fixed CF pre-liquidation factory contract for Morpho. contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ diff --git a/src/interfaces/IPreLiquidation.sol b/src/interfaces/IPreLiquidation.sol index bae1618..2608b55 100644 --- a/src/interfaces/IPreLiquidation.sol +++ b/src/interfaces/IPreLiquidation.sol @@ -15,10 +15,6 @@ struct PreLiquidationParams { address preLiquidationOracle; } -/// @title IPreLiquidation -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Interface of PreLiquidation. interface IPreLiquidation { function MORPHO() external view returns (IMorpho); diff --git a/src/interfaces/IPreLiquidationCallback.sol b/src/interfaces/IPreLiquidationCallback.sol index 465961d..34a8f95 100644 --- a/src/interfaces/IPreLiquidationCallback.sol +++ b/src/interfaces/IPreLiquidationCallback.sol @@ -2,9 +2,9 @@ pragma solidity >= 0.5.0; /// @title IPreLiquidationCallback -/// @notice Interface that preliquidators willing to use `preliquidate's callback must implement. +/// @notice Interface that "pre-liquidators" willing to use the pre-liquidation callback must implement. interface IPreLiquidationCallback { - /// @notice Callback called when a preLiquidation occurs. + /// @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. diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index 9f9a863..47c0298 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -4,10 +4,6 @@ pragma solidity >= 0.5.0; import {Id, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IPreLiquidation, PreLiquidationParams} from "./IPreLiquidation.sol"; import {PreLiquidationFactory} from "../PreLiquidationFactory.sol"; -/// @title IPreLiquidationFactory -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Interface of PreLiquidation's factory. interface IPreLiquidationFactory { function MORPHO() external view returns (IMorpho); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 1e9b294..46632aa 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -8,7 +8,7 @@ import {Id} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; /// @custom:contact security@morpho.org /// @notice Library exposing errors. library ErrorsLib { - /* PreLiquidation errors */ + /* PRELIQUIDATION ERRORS */ error PreLltvTooHigh(); @@ -22,7 +22,7 @@ library ErrorsLib { error NonexistentMarket(); - /* PreLiquidation Factory errors */ + /* PRELIQUIDATION FACTORY ERRORS */ error ZeroAddress(); } diff --git a/src/libraries/periphery/PreLiquidationAddressLib.sol b/src/libraries/periphery/PreLiquidationAddressLib.sol index b2a716f..0811447 100644 --- a/src/libraries/periphery/PreLiquidationAddressLib.sol +++ b/src/libraries/periphery/PreLiquidationAddressLib.sol @@ -6,13 +6,13 @@ import {PreLiquidationParams} from "../../interfaces/IPreLiquidation.sol"; import {Id} from "../../../lib/morpho-blue/src/interfaces/IMorpho.sol"; library PreLiquidationAddressLib { - /// @notice Computes the create2 address of the PreLiquidation contract generated by the `factory` + /// @notice Computes the CREATE2 address of the pre-liquidation contract generated by the `factory` /// for a specific Morpho market `id` with the pre-liquidation parameters `preLiquidationParams`. - /// @param morpho Morpho contract address. + /// @param morpho Morpho's address. /// @param factory PreLiquidationFactory contract address. - /// @param id Morpho market id for the PreLiquidation contract. - /// @param preLiquidationParams PreLiquidation parameters. - /// @return preLiquidationAddress The address of this PreLiquidation contract. + /// @param id Morpho market id for the pre-liquidation contract. + /// @param preLiquidationParams Pre-liquidation parameters. + /// @return preLiquidationAddress The address of this pre-liquidation contract. function computePreLiquidationAddress( address morpho, address factory, diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index e83665f..0070b08 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -40,8 +40,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 collateralAmount, uint256 borrowAmount, address liquidator - ) public{ - + ) public { preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); loanToken.mint(SUPPLIER, borrowAmount); @@ -63,7 +62,6 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { MORPHO.borrow(marketParams, borrowAmount, 0, BORROWER, BORROWER); MORPHO.setAuthorization(address(preLiquidation), true); vm.stopPrank(); - } function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { @@ -161,7 +159,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { assert(entries[3].topics[0] == keccak256("CallbackReached()")); } - function onPreLiquidate(uint256, bytes memory data) external{ + function onPreLiquidate(uint256, bytes memory data) external { bytes4 selector; (selector,) = abi.decode(data, (bytes4, bytes)); require(selector == this.testPreLiquidationCallback.selector); @@ -169,10 +167,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { emit CallbackReached(); } - function testPreLiquidationWithInterest( - PreLiquidationParams memory preLiquidationParams, - uint256 collateralAmount - ) public { + function testPreLiquidationWithInterest(PreLiquidationParams memory preLiquidationParams, uint256 collateralAmount) + public + { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentiveFactor = @@ -182,8 +179,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); uint256 collateralPrice = IOracle(marketParams.oracle).price(); - uint256 borrowThreshold = - uint256(collateralAmount).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(preLiquidationParams.preLltv)-1; + uint256 borrowThreshold = uint256(collateralAmount).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown( + preLiquidationParams.preLltv + ) - 1; preparePreLiquidation(preLiquidationParams, collateralAmount, borrowThreshold, LIQUIDATOR); vm.startPrank(LIQUIDATOR); @@ -203,6 +201,5 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { vm.roll(block.number + 1); preLiquidation.preLiquidate(BORROWER, 0, repayableShares, hex""); - } } From 59eb52dd41f0cd3bdd21ae006eb6486b06900c4b Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 10:18:11 +0200 Subject: [PATCH 176/182] refactor: inline health check --- src/PreLiquidation.sol | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 16a177f..026a722 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -113,7 +113,11 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { Market memory market = MORPHO.market(ID); Position memory position = MORPHO.position(ID, borrower); - require(_isPreLiquidatable(collateralPrice, position, market), ErrorsLib.NotPreLiquidatablePosition()); + 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); @@ -155,22 +159,4 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { ERC20(LOAN_TOKEN).safeTransferFrom(liquidator, address(this), repaidAssets); } - - /// @notice Assess whether a `position` on some Morpho `market` is pre-liquidatable - /// for a specific `collateralPrice` (fetched by calling PRE_LIQUIDATION_ORACLE). - /// @param collateralPrice the price of the collateral quoted in loan. - /// @param position The position on Morpho. - /// @param market The asset totals on the Morpho market. - /// @return isPreLiquidatable A boolean which is true if the position is pre-liquidatable and otherwise false. - function _isPreLiquidatable(uint256 collateralPrice, Position memory position, Market memory market) - internal - view - returns (bool) - { - uint256 borrowed = uint256(position.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares); - uint256 borrowThreshold = - uint256(position.collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(PRE_LLTV); - - return borrowed > borrowThreshold; - } } From fbaf3ce6760c61ef58cd9a382551f6027c62dcd6 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 10:24:51 +0200 Subject: [PATCH 177/182] style: format --- src/PreLiquidation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 026a722..53df37f 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -15,11 +15,11 @@ 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 security@morpho.org /// @notice The Fixed LI, Fixed CF pre-liquidation contract for Morpho. - contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using MarketParamsLib for MarketParams; using UtilsLib for uint256; From ac111830d37bc7afa684e4ecdef49b6a72042b80 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 14:56:47 +0200 Subject: [PATCH 178/182] feat: pre-liquidation incentive factor require --- src/PreLiquidation.sol | 5 ++++- src/libraries/ErrorsLib.sol | 2 ++ test/PreLiquidationFactoryTest.sol | 14 +++++++++++--- test/PreLiquidationTest.sol | 17 ++++++++++++++--- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 53df37f..e3c69c5 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -6,7 +6,7 @@ import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {UtilsLib} from "../lib/morpho-blue/src/libraries/UtilsLib.sol"; import {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; import "../lib/morpho-blue/src/libraries/ConstantsLib.sol"; -import {MathLib} from "../lib/morpho-blue/src/libraries/MathLib.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"; @@ -78,6 +78,9 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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.preLiquidationIncentiveFactor >= WAD, ErrorsLib.PreLiquidationIncentiveFactorTooLow() + ); MORPHO = IMorpho(morpho); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 46632aa..94e1234 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -12,6 +12,8 @@ library ErrorsLib { error PreLltvTooHigh(); + error PreLiquidationIncentiveFactorTooLow(); + error InconsistentInput(); error NotPreLiquidatablePosition(); diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 3c52aef..8585358 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -6,6 +6,7 @@ import {PreLiquidationParams, IPreLiquidation} from "../src/interfaces/IPreLiqui import {PreLiquidationFactory} from "../src/PreLiquidationFactory.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; import {PreLiquidationAddressLib} from "../src/libraries/periphery/PreLiquidationAddressLib.sol"; +import {WAD} from "../lib/morpho-blue/src/libraries/MathLib.sol"; contract PreLiquidationFactoryTest is BaseTest { using MarketParamsLib for MarketParams; @@ -22,7 +23,9 @@ contract PreLiquidationFactoryTest is BaseTest { } function testCreatePreLiquidation(PreLiquidationParams memory preLiquidationParams) public { - vm.assume(preLiquidationParams.preLltv < lltv); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); factory = new PreLiquidationFactory(address(MORPHO)); IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); @@ -45,7 +48,9 @@ contract PreLiquidationFactoryTest is BaseTest { } function testCreate2Deployment(PreLiquidationParams memory preLiquidationParams) public { - vm.assume(preLiquidationParams.preLltv < lltv); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); factory = new PreLiquidationFactory(address(MORPHO)); IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); @@ -57,7 +62,10 @@ contract PreLiquidationFactoryTest is BaseTest { } function testRedundantPreLiquidation(PreLiquidationParams memory preLiquidationParams) public { - vm.assume(preLiquidationParams.preLltv < lltv); + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); + factory = new PreLiquidationFactory(address(MORPHO)); factory.createPreLiquidation(id, preLiquidationParams); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 0070b08..1fee7d0 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -66,11 +66,22 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, marketParams.lltv, type(uint256).max); + preLiquidationParams.preLiquidationIncentiveFactor = + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector)); factory.createPreLiquidation(id, preLiquidationParams); } + function testLowLiquidationIncentiveFactor(PreLiquidationParams memory preLiquidationParams) public virtual { + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD - 1); + + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLiquidationIncentiveFactorTooLow.selector)); + factory.createPreLiquidation(id, preLiquidationParams); + } + function testNonexistentMarket(PreLiquidationParams memory preLiquidationParams) public virtual { vm.expectRevert(abi.encodeWithSelector(ErrorsLib.NonexistentMarket.selector)); factory.createPreLiquidation(Id.wrap(bytes32(0)), preLiquidationParams); @@ -84,7 +95,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentiveFactor = - bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -125,7 +136,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentiveFactor = - bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -173,7 +184,7 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); preLiquidationParams.preLiquidationIncentiveFactor = - bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); From 698ea9ca6d24ee3005aa5d0a3c82ba464f456044 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 15:15:09 +0200 Subject: [PATCH 179/182] feat: close factor require --- src/PreLiquidation.sol | 1 + src/libraries/ErrorsLib.sol | 2 ++ test/PreLiquidationFactoryTest.sol | 3 +++ test/PreLiquidationTest.sol | 26 +++++++++++++++++++------- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index e3c69c5..dac6567 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -78,6 +78,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { 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() ); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 94e1234..887119a 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -12,6 +12,8 @@ library ErrorsLib { error PreLltvTooHigh(); + error CloseFactorTooHigh(); + error PreLiquidationIncentiveFactorTooLow(); error InconsistentInput(); diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 8585358..c41aaf7 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -24,6 +24,7 @@ contract PreLiquidationFactoryTest is BaseTest { function testCreatePreLiquidation(PreLiquidationParams memory preLiquidationParams) public { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); @@ -49,6 +50,7 @@ contract PreLiquidationFactoryTest is BaseTest { function testCreate2Deployment(PreLiquidationParams memory preLiquidationParams) public { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); @@ -63,6 +65,7 @@ contract PreLiquidationFactoryTest is BaseTest { function testRedundantPreLiquidation(PreLiquidationParams memory preLiquidationParams) public { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); diff --git a/test/PreLiquidationTest.sol b/test/PreLiquidationTest.sol index 1fee7d0..5a4b709 100644 --- a/test/PreLiquidationTest.sol +++ b/test/PreLiquidationTest.sol @@ -66,15 +66,27 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { function testHighLltv(PreLiquidationParams memory preLiquidationParams) public virtual { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, marketParams.lltv, type(uint256).max); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = - WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); vm.expectRevert(abi.encodeWithSelector(ErrorsLib.PreLltvTooHigh.selector)); factory.createPreLiquidation(id, preLiquidationParams); } + function testHighCloseFactor(PreLiquidationParams memory preLiquidationParams) public virtual { + preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD + 1, type(uint256).max); + preLiquidationParams.preLiquidationIncentiveFactor = + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD - 1); + + vm.expectRevert(abi.encodeWithSelector(ErrorsLib.CloseFactorTooHigh.selector)); + factory.createPreLiquidation(id, preLiquidationParams); + } + function testLowLiquidationIncentiveFactor(PreLiquidationParams memory preLiquidationParams) public virtual { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD - 1); @@ -93,9 +105,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 borrowAmount ) public virtual { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); - preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = - WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -134,9 +146,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { uint256 borrowAmount ) public virtual { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); - preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = - WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); @@ -182,9 +194,9 @@ contract PreLiquidationTest is BaseTest, IPreLiquidationCallback { public { preLiquidationParams.preLltv = bound(preLiquidationParams.preLltv, WAD / 100, marketParams.lltv - 1); - preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD - 1); + preLiquidationParams.closeFactor = bound(preLiquidationParams.closeFactor, WAD / 100, WAD); preLiquidationParams.preLiquidationIncentiveFactor = - WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 1, WAD / 10); + WAD + bound(preLiquidationParams.preLiquidationIncentiveFactor, 0, WAD / 10); preLiquidationParams.preLiquidationOracle = marketParams.oracle; collateralAmount = bound(collateralAmount, 10 ** 18, 10 ** 24); From 2178f35581476f6184fb9290d1c7a783724c7113 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 15:46:44 +0200 Subject: [PATCH 180/182] refactor: fetch price before accrue interest --- src/PreLiquidation.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index dac6567..abc057e 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -111,12 +111,13 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { /// @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()); - uint256 collateralPrice = IOracle(PRE_LIQUIDATION_ORACLE).price(); 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); From ccfd8f16504f2bebe96a85442d250b319b415499 Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 15:58:22 +0200 Subject: [PATCH 181/182] refactor: import --- src/PreLiquidation.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index abc057e..8038b4c 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -4,8 +4,7 @@ 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 {MarketParamsLib} from "../lib/morpho-blue/src/libraries/MarketParamsLib.sol"; -import "../lib/morpho-blue/src/libraries/ConstantsLib.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"; @@ -21,8 +20,6 @@ import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCal /// @custom:contact security@morpho.org /// @notice The Fixed LI, Fixed CF pre-liquidation contract for Morpho. contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { - using MarketParamsLib for MarketParams; - using UtilsLib for uint256; using SharesMathLib for uint256; using MathLib for uint256; using SafeTransferLib for ERC20; @@ -104,7 +101,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { /* 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. + /// @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. From fc5728d4db627c1c166effc30cfc3bf1def0f4ac Mon Sep 17 00:00:00 2001 From: peyha Date: Wed, 25 Sep 2024 16:38:24 +0200 Subject: [PATCH 182/182] doc: improvements --- src/PreLiquidation.sol | 2 +- src/PreLiquidationFactory.sol | 2 +- src/mocks/MorphoImport.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index 8038b4c..11e74f7 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -18,7 +18,7 @@ import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCal /// @title PreLiquidation /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Fixed LI, Fixed CF pre-liquidation contract for Morpho. +/// @notice The Fixed LIF, Fixed CF pre-liquidation contract for Morpho. contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { using SharesMathLib for uint256; using MathLib for uint256; diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 768b2b9..308a0c5 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -11,7 +11,7 @@ import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; /// @title PreLiquidationFactory /// @author Morpho Labs /// @custom:contact security@morpho.org -/// @notice The Fixed LI, Fixed CF pre-liquidation factory contract for Morpho. +/// @notice The Fixed LIF, Fixed CF pre-liquidation factory contract for Morpho. contract PreLiquidationFactory is IPreLiquidationFactory { /* IMMUTABLE */ diff --git a/src/mocks/MorphoImport.sol b/src/mocks/MorphoImport.sol index bf778e1..2110eee 100644 --- a/src/mocks/MorphoImport.sol +++ b/src/mocks/MorphoImport.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity 0.8.19; -// Force foundry to compile Morpho Blue even though it's not imported by Metamorpho or by the tests. +// Force foundry to compile Morpho Blue even though it's not imported by PreLiquidation or by the tests. // Morpho Blue will be compiled with its own solidity version. // The resulting bytecode is then loaded by BaseTest.sol.