diff --git a/src/PreLiquidation.sol b/src/PreLiquidation.sol index d4e91b2..3f58929 100644 --- a/src/PreLiquidation.sol +++ b/src/PreLiquidation.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.27; import {Id, MarketParams, IMorpho, Position, Market} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {IMorphoRepayCallback} from "../lib/morpho-blue/src/interfaces/IMorphoCallbacks.sol"; import {IPreLiquidation, PreLiquidationParams} from "./interfaces/IPreLiquidation.sol"; +import {IPreLiquidationFactory} from "./interfaces/IPreLiquidationFactory.sol"; import {IPreLiquidationCallback} from "./interfaces/IPreLiquidationCallback.sol"; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; @@ -73,17 +74,20 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { /* CONSTRUCTOR */ /// @dev Initializes the PreLiquidation contract. - /// @param morpho The address of the Morpho contract. - /// @param id The id of the Morpho market on which pre-liquidations will occur. - /// @param _preLiquidationParams The pre-liquidation parameters. + /// @dev Meant to be called by the factory only. /// @dev The following requirements should be met: /// - preLltv < LLTV; /// - preLCF1 <= preLCF2; /// - preLCF1 <= 1; /// - 1 <= preLIF1 <= preLIF2 <= 1 / LLTV. - constructor(address morpho, Id id, PreLiquidationParams memory _preLiquidationParams) { - require(IMorpho(morpho).market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); - MarketParams memory _marketParams = IMorpho(morpho).idToMarketParams(id); + constructor() { + // Not optimized yet: no need to make 3 calls. + IMorpho morpho = IPreLiquidationFactory(msg.sender).MORPHO(); + Id id = IPreLiquidationFactory(msg.sender).id(); + PreLiquidationParams memory _preLiquidationParams = IPreLiquidationFactory(msg.sender).preLiquidationParams(); + + require(morpho.market(id).lastUpdate != 0, ErrorsLib.NonexistentMarket()); + MarketParams memory _marketParams = morpho.idToMarketParams(id); require(_preLiquidationParams.preLltv < _marketParams.lltv, ErrorsLib.PreLltvTooHigh()); require(_preLiquidationParams.preLCF1 <= _preLiquidationParams.preLCF2, ErrorsLib.PreLCFDecreasing()); require(_preLiquidationParams.preLCF1 <= WAD, ErrorsLib.PreLCFTooHigh()); @@ -91,7 +95,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { require(_preLiquidationParams.preLIF1 <= _preLiquidationParams.preLIF2, ErrorsLib.PreLIFDecreasing()); require(_preLiquidationParams.preLIF2 <= WAD.wDivDown(_marketParams.lltv), ErrorsLib.PreLIFTooHigh()); - MORPHO = IMorpho(morpho); + MORPHO = morpho; ID = id; @@ -108,7 +112,7 @@ contract PreLiquidation is IPreLiquidation, IMorphoRepayCallback { PRE_LIF_2 = _preLiquidationParams.preLIF2; PRE_LIQUIDATION_ORACLE = _preLiquidationParams.preLiquidationOracle; - ERC20(_marketParams.loanToken).safeApprove(morpho, type(uint256).max); + ERC20(_marketParams.loanToken).safeApprove(address(morpho), type(uint256).max); } /* PRE-LIQUIDATION */ diff --git a/src/PreLiquidationFactory.sol b/src/PreLiquidationFactory.sol index 44245dd..594d451 100644 --- a/src/PreLiquidationFactory.sol +++ b/src/PreLiquidationFactory.sol @@ -7,6 +7,7 @@ import {IMorpho, Id} from "../lib/morpho-blue/src/interfaces/IMorpho.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; import {EventsLib} from "./libraries/EventsLib.sol"; +import {PreLiquidationAddressLib} from "./libraries/PreLiquidationAddressLib.sol"; import {PreLiquidation} from "./PreLiquidation.sol"; @@ -22,8 +23,27 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* STORAGE */ - /// @notice Mapping which returns true if the address is a PreLiquidation contract created by this factory. - mapping(address => bool) public isPreLiquidation; + // Temporary contract creation variables. + // Not optimized yet: make those transient. + Id public id; + uint256 internal preLltv; + uint256 internal preLCF1; + uint256 internal preLCF2; + uint256 internal preLIF1; + uint256 internal preLIF2; + address internal preLiquidationOracle; + + /// @notice The pre-liquidation parameters specific currently set. + function preLiquidationParams() external view returns (PreLiquidationParams memory) { + return PreLiquidationParams({ + preLltv: preLltv, + preLCF1: preLCF1, + preLCF2: preLCF2, + preLIF1: preLIF1, + preLIF2: preLIF2, + preLiquidationOracle: preLiquidationOracle + }); + } /* CONSTRUCTOR */ @@ -37,20 +57,39 @@ contract PreLiquidationFactory is IPreLiquidationFactory { /* EXTERNAL */ /// @notice Creates a PreLiquidation contract. - /// @param id The Morpho market for PreLiquidations. - /// @param preLiquidationParams The PreLiquidation params for the 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) + function createPreLiquidation(Id _id, PreLiquidationParams calldata _preLiquidationParams) external returns (IPreLiquidation) { - IPreLiquidation preLiquidation = - IPreLiquidation(address(new PreLiquidation{salt: 0}(address(MORPHO), id, preLiquidationParams))); + id = _id; + preLltv = _preLiquidationParams.preLltv; + preLCF1 = _preLiquidationParams.preLCF1; + preLCF2 = _preLiquidationParams.preLCF2; + preLIF1 = _preLiquidationParams.preLIF1; + preLIF2 = _preLiquidationParams.preLIF2; + preLiquidationOracle = _preLiquidationParams.preLiquidationOracle; + + bytes32 salt = PreLiquidationAddressLib.hashPreLiquidationConstructorParams(MORPHO, id, _preLiquidationParams); + + address preLiquidation = PreLiquidationAddressLib.computePreLiquidationAddressFromSalt(address(this), salt); - emit EventsLib.CreatePreLiquidation(address(preLiquidation), id, preLiquidationParams); + require(preLiquidation.code.length == 0, ErrorsLib.AlreadyDeployedPreLiquidation(preLiquidation)); - isPreLiquidation[address(preLiquidation)] = true; + new PreLiquidation{salt: salt}(); + + emit EventsLib.CreatePreLiquidation(preLiquidation, id, _preLiquidationParams); + + return IPreLiquidation(preLiquidation); + } - return preLiquidation; + function isPreLiquidation(address preLiq) external view returns (bool) { + // Not optimized yet: no need to make 2 calls. + PreLiquidationParams memory _preLiquidationParams = IPreLiquidation(preLiq).preLiquidationParams(); + Id _id = IPreLiquidation(preLiq).ID(); + return preLiq + == PreLiquidationAddressLib.computePreLiquidationAddress(MORPHO, address(this), _id, _preLiquidationParams); } } diff --git a/src/interfaces/IPreLiquidationFactory.sol b/src/interfaces/IPreLiquidationFactory.sol index d39161c..52063b7 100644 --- a/src/interfaces/IPreLiquidationFactory.sol +++ b/src/interfaces/IPreLiquidationFactory.sol @@ -12,4 +12,8 @@ interface IPreLiquidationFactory { function createPreLiquidation(Id id, PreLiquidationParams calldata preLiquidationParams) external returns (IPreLiquidation preLiquidation); + + function id() external view returns (Id); + + function preLiquidationParams() external view returns (PreLiquidationParams memory); } diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 5c3393d..9d3b491 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -35,4 +35,6 @@ library ErrorsLib { /* PRELIQUIDATION FACTORY ERRORS */ error ZeroAddress(); + + error AlreadyDeployedPreLiquidation(address preLiquidation); } diff --git a/src/libraries/PreLiquidationAddressLib.sol b/src/libraries/PreLiquidationAddressLib.sol new file mode 100644 index 0000000..4d096ca --- /dev/null +++ b/src/libraries/PreLiquidationAddressLib.sol @@ -0,0 +1,40 @@ +// 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, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol"; + +library PreLiquidationAddressLib { + /// @dev Should be equal to keccak256(abi.encodePacked(type(PreLiquidation).creationCode)) + bytes32 internal constant INIT_CODE_HASH = 0xfd272715eba47f05c57fe589ac9906da3e899bc943693d89f067ee7ef51d01ff; + + /// @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's address. + /// @param factory PreLiquidationFactory contract address. + /// @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( + IMorpho morpho, + address factory, + Id id, + PreLiquidationParams memory preLiquidationParams + ) internal pure returns (address) { + bytes32 salt = hashPreLiquidationConstructorParams(morpho, id, preLiquidationParams); + return computePreLiquidationAddressFromSalt(factory, salt); + } + + function computePreLiquidationAddressFromSalt(address factory, bytes32 salt) internal pure returns (address) { + return address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, salt, INIT_CODE_HASH))))); + } + + function hashPreLiquidationConstructorParams( + IMorpho morpho, + Id id, + PreLiquidationParams memory preLiquidationParams + ) internal pure returns (bytes32) { + return keccak256(abi.encode(morpho, id, preLiquidationParams)); + } +} diff --git a/src/libraries/periphery/PreLiquidationAddressLib.sol b/src/libraries/periphery/PreLiquidationAddressLib.sol deleted file mode 100644 index eba40e0..0000000 --- a/src/libraries/periphery/PreLiquidationAddressLib.sol +++ /dev/null @@ -1,26 +0,0 @@ -// 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"; - -library PreLiquidationAddressLib { - /// @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's address. - /// @param factory PreLiquidationFactory contract address. - /// @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, - Id id, - PreLiquidationParams memory preLiquidationParams - ) internal pure returns (address) { - bytes32 initCodeHash = - keccak256(abi.encodePacked(type(PreLiquidation).creationCode, abi.encode(morpho, id, preLiquidationParams))); - return address(uint160(uint256(keccak256(abi.encodePacked(uint8(0xff), factory, uint256(0), initCodeHash))))); - } -} diff --git a/test/PreLiquidationFactoryTest.sol b/test/PreLiquidationFactoryTest.sol index 62c52aa..82c8d23 100644 --- a/test/PreLiquidationFactoryTest.sol +++ b/test/PreLiquidationFactoryTest.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.0; import "./BaseTest.sol"; +import {PreLiquidation} from "../src/PreLiquidation.sol"; import {ErrorsLib} from "../src/libraries/ErrorsLib.sol"; -import {PreLiquidationAddressLib} from "../src/libraries/periphery/PreLiquidationAddressLib.sol"; +import {PreLiquidationAddressLib} from "../src/libraries/PreLiquidationAddressLib.sol"; contract PreLiquidationFactoryTest is BaseTest { using MarketParamsLib for MarketParams; @@ -57,6 +58,11 @@ contract PreLiquidationFactoryTest is BaseTest { assert(factory.isPreLiquidation(address(preLiquidation))); } + function testInitCodeHash() public pure { + bytes32 expectedInitCodeHash = keccak256(abi.encodePacked(type(PreLiquidation).creationCode)); + assertEq(PreLiquidationAddressLib.INIT_CODE_HASH, expectedInitCodeHash, "Unexpected init code hash"); + } + function testCreate2Deployment(PreLiquidationParams memory preLiquidationParams) public { preLiquidationParams = boundPreLiquidationParameters( preLiquidationParams, @@ -74,9 +80,8 @@ contract PreLiquidationFactoryTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); - address preLiquidationAddress = PreLiquidationAddressLib.computePreLiquidationAddress( - address(MORPHO), address(factory), id, preLiquidationParams - ); + address preLiquidationAddress = + PreLiquidationAddressLib.computePreLiquidationAddress(MORPHO, address(factory), id, preLiquidationParams); assert(address(preLiquidation) == preLiquidationAddress); } @@ -96,9 +101,11 @@ contract PreLiquidationFactoryTest is BaseTest { factory = new PreLiquidationFactory(address(MORPHO)); - factory.createPreLiquidation(id, preLiquidationParams); + IPreLiquidation preLiquidation = factory.createPreLiquidation(id, preLiquidationParams); - vm.expectRevert(bytes("")); + vm.expectRevert( + abi.encodeWithSelector(ErrorsLib.AlreadyDeployedPreLiquidation.selector, address(preLiquidation)) + ); factory.createPreLiquidation(id, preLiquidationParams); } }