From 5b04b1c0d6915922685cb5bbf8d3d636dbe47fbb Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Wed, 12 Mar 2025 16:31:04 +0800 Subject: [PATCH 1/7] add(LiquidationFacet): liquidate with 110% cap when MCR > ICR > 110% --- src/core/Config.sol | 2 ++ src/core/facets/LiquidationFacet.sol | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/core/Config.sol b/src/core/Config.sol index 9477441..3829ccc 100644 --- a/src/core/Config.sol +++ b/src/core/Config.sol @@ -29,6 +29,8 @@ library Config { /* Liquidation */ uint256 internal constant _100_PCT = 1_000_000_000_000_000_000; // 1e18 == 100% + uint256 internal constant _110_PCT = 1_100_000_000_000_000_000; // 110% + /* PriceFeedAggregator */ uint256 public constant PRICE_TARGET_DIGITS = 18; diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index a0c0cf2..99632e1 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -78,11 +78,17 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn if (ICR <= Config._100_PCT && _inRecoveryMode()) { singleLiquidation = _liquidateWithoutSP(s, troveManager, account); _applyLiquidationValuesToTotals(totals, singleLiquidation); - } else if (ICR < troveManagerValues.MCR) { + } else if (ICR < Config._110_PCT) { singleLiquidation = _liquidateNormalMode(s, troveManager, account, debtInStabPool, troveManagerValues.sunsetting); debtInStabPool -= singleLiquidation.debtToOffset; _applyLiquidationValuesToTotals(totals, singleLiquidation); + } else if (ICR < troveManagerValues.MCR) { + singleLiquidation = _tryLiquidateWithCap( + troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + ); + debtInStabPool -= singleLiquidation.debtToOffset; + _applyLiquidationValuesToTotals(totals, singleLiquidation); } else { break; } // break if the loop reaches a Trove with ICR >= MCR @@ -184,10 +190,15 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn uint256 ICR = troveManager.getCurrentICR(account, troveManagerValues.price); if (ICR <= Config._100_PCT && _inRecoveryMode()) { singleLiquidation = _liquidateWithoutSP(s, troveManager, account); - } else if (ICR < troveManagerValues.MCR) { + } else if (ICR < Config._110_PCT) { singleLiquidation = _liquidateNormalMode(s, troveManager, account, debtInStabPool, troveManagerValues.sunsetting); debtInStabPool -= singleLiquidation.debtToOffset; + } else if (ICR < troveManagerValues.MCR) { + singleLiquidation = _tryLiquidateWithCap( + troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + ); + debtInStabPool -= singleLiquidation.debtToOffset; } else { // As soon as we find a trove with ICR >= MCR we need to start tracking the global TCR with the next loop break; @@ -212,9 +223,13 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn } if (ICR <= Config._100_PCT && _inRecoveryMode()) { singleLiquidation = _liquidateWithoutSP(s, troveManager, account); - } else if (ICR < troveManagerValues.MCR) { + } else if (ICR < Config._110_PCT) { singleLiquidation = _liquidateNormalMode(s, troveManager, account, debtInStabPool, troveManagerValues.sunsetting); + } else if (ICR < troveManagerValues.MCR) { + singleLiquidation = _tryLiquidateWithCap( + troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + ); } else { if (troveManagerValues.sunsetting) continue; uint256 TCR = SatoshiMath._computeCR(entireSystemColl, entireSystemDebt); From dd38e11413c54214a4d08fd5e79d25cc11735b66 Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Fri, 14 Mar 2025 13:22:09 +0800 Subject: [PATCH 2/7] feat(LiquidationFacet): recovery mode grace period --- src/core/AppStorage.sol | 3 + src/core/Config.sol | 4 ++ src/core/Initializer.sol | 6 ++ src/core/facets/BorrowerOperationsFacet.sol | 58 +++++++++++++------- src/core/facets/LiquidationFacet.sol | 31 +++++++++++ src/core/interfaces/ILiquidationFacet.sol | 13 +++++ src/core/libs/RecoveryModeGracePeriodLib.sol | 35 ++++++++++++ test/Liquidate.t.sol | 6 ++ test/utils/DeployBase.t.sol | 4 +- 9 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 src/core/libs/RecoveryModeGracePeriodLib.sol diff --git a/src/core/AppStorage.sol b/src/core/AppStorage.sol index adece3f..e94cb5f 100644 --- a/src/core/AppStorage.sol +++ b/src/core/AppStorage.sol @@ -102,6 +102,9 @@ library AppStorage { mapping(address => AssetConfig) assetConfigs; mapping(address => bool) isAssetSupported; mapping(address => uint256) dailyMintCount; + // Recovery mode liquidation grace period + uint128 lastGracePeriodStartTimestamp; + uint128 recoveryModeGracePeriodDuration; } function layout() internal pure returns (Layout storage s) { diff --git a/src/core/Config.sol b/src/core/Config.sol index 3829ccc..83e8fae 100644 --- a/src/core/Config.sol +++ b/src/core/Config.sol @@ -47,4 +47,8 @@ library Config { * Farming */ uint256 constant FARMING_PRECISION = 1e4; + + /* Recovery mode grace period */ + uint128 constant UNSET_TIMESTAMP = type(uint128).max; + uint128 constant MINIMUM_GRACE_PERIOD = 15 minutes; } diff --git a/src/core/Initializer.sol b/src/core/Initializer.sol index 47dc462..f5d8063 100644 --- a/src/core/Initializer.sol +++ b/src/core/Initializer.sol @@ -108,5 +108,11 @@ contract Initializer is Initializable, AccessControlInternal, OwnableInternal { */ // debtToken // rewardManager + + /** + * LiquidationFacet + */ + s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; + s.recoveryModeGracePeriodDuration = Config.MINIMUM_GRACE_PERIOD; } } diff --git a/src/core/facets/BorrowerOperationsFacet.sol b/src/core/facets/BorrowerOperationsFacet.sol index 2a70060..a013ea2 100644 --- a/src/core/facets/BorrowerOperationsFacet.sol +++ b/src/core/facets/BorrowerOperationsFacet.sol @@ -16,6 +16,7 @@ import { IDebtToken } from "../interfaces/IDebtToken.sol"; import { ITroveManager } from "../interfaces/ITroveManager.sol"; import { BorrowerOperationsLib } from "../libs/BorrowerOperationsLib.sol"; +import { RecoveryModeGracePeriodLib } from "../libs/RecoveryModeGracePeriodLib.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -26,6 +27,7 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter using SafeERC20 for IERC20; using SafeERC20 for IDebtToken; using BorrowerOperationsLib for *; + using RecoveryModeGracePeriodLib for AppStorage.Layout; struct LocalVariables_adjustTrove { uint256 price; @@ -177,20 +179,28 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter vars.ICR = SatoshiMath._computeCR(scaledCollateralAmount, vars.compositeDebt, vars.price); vars.NICR = SatoshiMath._computeNominalCR(_collateralAmount, vars.compositeDebt); + uint256 newTCR = BorrowerOperationsLib._getNewTCRFromTroveChange( + vars.totalPricedCollateral, + vars.totalDebt, + _collateralAmount * vars.price, + true, + vars.compositeDebt, + true, + decimals + ); // bools: coll increase, debt increase + if (isRecoveryMode) { BorrowerOperationsLib._requireICRisAboveCCR(vars.ICR); + if (newTCR < Config.CCR) { + s._startGracePeriod(); + } else { + // Deposit coll could exit Recovery Mode + s._endGracePeriod(); + } } else { BorrowerOperationsLib._requireICRisAboveMCR(vars.ICR, troveManager.MCR()); - uint256 newTCR = BorrowerOperationsLib._getNewTCRFromTroveChange( - vars.totalPricedCollateral, - vars.totalDebt, - _collateralAmount * vars.price, - true, - vars.compositeDebt, - true, - decimals - ); // bools: coll increase, debt increase BorrowerOperationsLib._requireNewTCRisAboveCCR(newTCR); + s._endGracePeriod(); } // Create the trove @@ -479,7 +489,6 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter uint8 decimals ) internal - pure { /* *In Recovery Mode, only allow: @@ -494,6 +503,7 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter * - The new ICR is above MCR * - The adjustment won't pull the TCR below CCR */ + AppStorage.Layout storage s = AppStorage.layout(); // Get the trove's old ICR before the adjustment uint256 scaledCollAmount = SatoshiMath._getScaledCollateralAmount(_vars.coll, decimals); @@ -511,25 +521,35 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter decimals ); + uint256 newTCR = BorrowerOperationsLib._getNewTCRFromTroveChange( + totalPricedCollateral, + totalDebt, + _vars.collChange * _vars.price, + _vars.isCollIncrease, + _vars.netDebtChange, + _isDebtIncrease, + decimals + ); + if (_isRecoveryMode) { require(_collWithdrawal == 0, "BorrowerOps: Collateral withdrawal not permitted Recovery Mode"); if (_isDebtIncrease) { BorrowerOperationsLib._requireICRisAboveCCR(newICR); BorrowerOperationsLib._requireNewICRisAboveOldICR(newICR, oldICR); } + + if (newTCR < Config.CCR) { + s._startGracePeriod(); + } else { + // Deposit coll could exit Recovery Mode + s._endGracePeriod(); + } } else { // if Normal Mode BorrowerOperationsLib._requireICRisAboveMCR(newICR, _vars.MCR); - uint256 newTCR = BorrowerOperationsLib._getNewTCRFromTroveChange( - totalPricedCollateral, - totalDebt, - _vars.collChange * _vars.price, - _vars.isCollIncrease, - _vars.netDebtChange, - _isDebtIncrease, - decimals - ); BorrowerOperationsLib._requireNewTCRisAboveCCR(newTCR); + + s._endGracePeriod(); } } diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index 99632e1..3e1ed74 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -16,6 +16,7 @@ import { IStabilityPoolFacet } from "../interfaces/IStabilityPoolFacet.sol"; import { ITroveManager } from "../interfaces/ITroveManager.sol"; import { BorrowerOperationsLib } from "../libs/BorrowerOperationsLib.sol"; +import { RecoveryModeGracePeriodLib } from "../libs/RecoveryModeGracePeriodLib.sol"; import { StabilityPoolLib } from "../libs/StabilityPoolLib.sol"; import { AccessControlInternal } from "@solidstate/contracts/access/access_control/AccessControlInternal.sol"; import { OwnableInternal } from "@solidstate/contracts/access/ownable/OwnableInternal.sol"; @@ -25,6 +26,7 @@ import { IERC20Metadata } from "@solidstate/contracts/token/ERC20/metadata/IERC2 contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableInternal { using StabilityPoolLib for AppStorage.Layout; using BorrowerOperationsLib for AppStorage.Layout; + using RecoveryModeGracePeriodLib for AppStorage.Layout; // --- Trove Liquidation functions --- @@ -114,6 +116,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn uint256 TCR = SatoshiMath._computeCR(entireSystemColl, entireSystemDebt); if (TCR >= Config.CCR || ICR >= TCR) break; + _checkRecoveryModeGracePeriod(); singleLiquidation = _tryLiquidateWithCap( _troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price @@ -234,6 +237,8 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn if (troveManagerValues.sunsetting) continue; uint256 TCR = SatoshiMath._computeCR(entireSystemColl, entireSystemDebt); if (TCR >= Config.CCR || ICR >= TCR) continue; + // check the recovery mode grace period + _checkRecoveryModeGracePeriod(); singleLiquidation = _tryLiquidateWithCap( troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price ); @@ -472,4 +477,30 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn uint256 TCR = SatoshiMath._computeCR(_entireSystemColl, _entireSystemDebt); return BorrowerOperationsLib._checkRecoveryMode(TCR); } + + function _checkRecoveryModeGracePeriod() internal { + AppStorage.Layout storage s = AppStorage.layout(); + uint128 cachedLastGracePeriodStartTimestamp = s.lastGracePeriodStartTimestamp; + if (cachedLastGracePeriodStartTimestamp == Config.UNSET_TIMESTAMP) { + revert NotInGracePeriod(); + } + if (uint128(block.timestamp) < cachedLastGracePeriodStartTimestamp + s.recoveryModeGracePeriodDuration) { + revert InGracePeriod(); + } + } + + // --- Grace Period --- + function setGracePeriod(uint128 _gracePeriod) external onlyRole(Config.OWNER_ROLE) { + AppStorage.Layout storage s = AppStorage.layout(); + if (_gracePeriod < Config.MINIMUM_GRACE_PERIOD) revert GracePeriodTooShort(_gracePeriod); + + s._syncGracePeriod(_inRecoveryMode()); + s.recoveryModeGracePeriodDuration = _gracePeriod; + emit GracePeriodDurationSet(_gracePeriod); + } + + function syncGracePeriod() external { + AppStorage.Layout storage s = AppStorage.layout(); + s._syncGracePeriod(_inRecoveryMode()); + } } diff --git a/src/core/interfaces/ILiquidationFacet.sol b/src/core/interfaces/ILiquidationFacet.sol index 6207d20..6b32329 100644 --- a/src/core/interfaces/ILiquidationFacet.sol +++ b/src/core/interfaces/ILiquidationFacet.sol @@ -91,6 +91,12 @@ interface ILiquidationFacet { /// @param _operation The operation type event TroveLiquidated(address indexed _borrower, uint256 _debt, uint256 _coll, uint8 _operation); + event GracePeriodDurationSet(uint128 _gracePeriod); + + error GracePeriodTooShort(uint128 gracePeriod); + error NotInGracePeriod(); + error InGracePeriod(); + /// @notice Batch liquidates a list of troves /// @param troveManager The Trove Manager handling the liquidation /// @param _troveArray The array of trove addresses to be liquidated @@ -106,4 +112,11 @@ interface ILiquidationFacet { /// @param maxTrovesToLiquidate The maximum number of troves to liquidate /// @param maxICR The maximum individual collateral ratio function liquidateTroves(ITroveManager troveManager, uint256 maxTrovesToLiquidate, uint256 maxICR) external; + + /// @notice Set the grace period for recovery mode + /// @param _gracePeriod The new grace period + function setGracePeriod(uint128 _gracePeriod) external; + + /// @notice Sync the grace period + function syncGracePeriod() external; } diff --git a/src/core/libs/RecoveryModeGracePeriodLib.sol b/src/core/libs/RecoveryModeGracePeriodLib.sol new file mode 100644 index 0000000..d0c7a55 --- /dev/null +++ b/src/core/libs/RecoveryModeGracePeriodLib.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { AppStorage } from "../AppStorage.sol"; +import { Config } from "../Config.sol"; + +library RecoveryModeGracePeriodLib { + event GracePeriodStart(); + event GracePeriodEnd(); + event GracePeriodDurationSet(uint128 _gracePeriod); + + function _startGracePeriod(AppStorage.Layout storage s) internal { + if (s.lastGracePeriodStartTimestamp == Config.UNSET_TIMESTAMP) { + s.lastGracePeriodStartTimestamp = uint128(block.timestamp); + + emit GracePeriodStart(); + } + } + + function _endGracePeriod(AppStorage.Layout storage s) internal { + if (s.lastGracePeriodStartTimestamp != Config.UNSET_TIMESTAMP) { + s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; + + emit GracePeriodEnd(); + } + } + + function _syncGracePeriod(AppStorage.Layout storage s, bool isRecoveryMode) internal { + if (isRecoveryMode) { + _startGracePeriod(s); + } else { + _endGracePeriod(s); + } + } +} diff --git a/test/Liquidate.t.sol b/test/Liquidate.t.sol index 7bb0d0e..661a94b 100644 --- a/test/Liquidate.t.sol +++ b/test/Liquidate.t.sol @@ -247,6 +247,10 @@ contract LiquidateTest is DeployBase, TroveBase { uint256 collUser1Remaining = coll1 - vars.collToSendToSP; vm.startPrank(user4); + liquidationManagerProxy().syncGracePeriod(); + vm.expectRevert(ILiquidationFacet.InGracePeriod.selector); + liquidationManagerProxy().liquidate(troveManagerBeaconProxy, user1); + vm.warp(block.timestamp + 20 minutes); // the user1 coll will capped at 1.1 * debt, no redistribution liquidationManagerProxy().liquidate(troveManagerBeaconProxy, user1); @@ -451,6 +455,8 @@ contract LiquidateTest is DeployBase, TroveBase { uint256 collUser1Remaining = coll1 - vars.collToSendToSP; vm.startPrank(user4); + liquidationManagerProxy().syncGracePeriod(); + vm.warp(block.timestamp + 20 minutes); // the user1 coll will capped at 1.1 * debt, no redistribution liquidationManagerProxy().liquidateTroves(troveManagerBeaconProxy, 10, CCR); diff --git a/test/utils/DeployBase.t.sol b/test/utils/DeployBase.t.sol index 360a45f..ddd8084 100644 --- a/test/utils/DeployBase.t.sol +++ b/test/utils/DeployBase.t.sol @@ -273,10 +273,12 @@ abstract contract DeployBase is Test { vm.startPrank(deployer); assert(address(liquidationFacet) == address(0)); // check if contract is not deployed liquidationFacet = ILiquidationFacet(address(new LiquidationFacet())); - bytes4[] memory selectors = new bytes4[](3); + bytes4[] memory selectors = new bytes4[](5); selectors[0] = ILiquidationFacet.batchLiquidateTroves.selector; selectors[1] = ILiquidationFacet.liquidate.selector; selectors[2] = ILiquidationFacet.liquidateTroves.selector; + selectors[3] = ILiquidationFacet.setGracePeriod.selector; + selectors[4] = ILiquidationFacet.syncGracePeriod.selector; vm.stopPrank(); return (address(liquidationFacet), selectors); } From 04e70f3b7e11ba02c1443d20eeb22a6656d5fdc0 Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Sun, 13 Apr 2025 03:14:33 +0800 Subject: [PATCH 3/7] fix & check grace period after liquidation, redeemption, close --- src/core/Initializer.sol | 10 ++++++++++ src/core/TroveManager.sol | 5 +++++ src/core/facets/BorrowerOperationsFacet.sol | 7 +++++++ src/core/facets/LiquidationFacet.sol | 15 +++++++++------ src/core/interfaces/IBorrowerOperationsFacet.sol | 2 ++ src/core/libs/BorrowerOperationsLib.sol | 6 ++++++ 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/core/Initializer.sol b/src/core/Initializer.sol index f5d8063..9fec557 100644 --- a/src/core/Initializer.sol +++ b/src/core/Initializer.sol @@ -115,4 +115,14 @@ contract Initializer is Initializable, AccessControlInternal, OwnableInternal { s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; s.recoveryModeGracePeriodDuration = Config.MINIMUM_GRACE_PERIOD; } + + function initV2() public reinitializer(2) { + AppStorage.Layout storage s = AppStorage.layout(); + + /** + * LiquidationFacet + */ + s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; + s.recoveryModeGracePeriodDuration = Config.MINIMUM_GRACE_PERIOD; + } } diff --git a/src/core/TroveManager.sol b/src/core/TroveManager.sol index 8441f50..9113cf8 100644 --- a/src/core/TroveManager.sol +++ b/src/core/TroveManager.sol @@ -667,6 +667,8 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { totals.collateralToSendToRedeemer = totals.totalCollateralDrawn - totals.collateralFee; + IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); + emit Redemption( msg.sender, _debtAmount, totals.totalDebtToRedeem, totals.totalCollateralDrawn, totals.collateralFee ); @@ -1109,6 +1111,9 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { external { _requireCallerIsSatoshiXapp(); + + IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); + // redistribute debt and collateral _redistributeDebtAndColl(_debt, _coll); diff --git a/src/core/facets/BorrowerOperationsFacet.sol b/src/core/facets/BorrowerOperationsFacet.sol index a013ea2..73fe0d2 100644 --- a/src/core/facets/BorrowerOperationsFacet.sol +++ b/src/core/facets/BorrowerOperationsFacet.sol @@ -437,6 +437,8 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter // Burn the repaid Debt from the user's balance and the gas compensation from the Gas Pool s.debtToken.burnWithGasCompensation(msg.sender, debt - s.gasCompensation); + syncGracePeriod(); + // collect interest payable to rewardManager if (troveManager.interestPayable() != 0) { troveManager.collectInterests(); @@ -558,4 +560,9 @@ contract BorrowerOperationsFacet is IBorrowerOperationsFacet, AccessControlInter Balances memory balances = s._fetchBalances(); (, totalPricedCollateral, totalDebt) = BorrowerOperationsLib._getTCRData(balances); } + + function syncGracePeriod() public { + AppStorage.Layout storage s = AppStorage.layout(); + s._syncGracePeriod(s._inRecoveryMode()); + } } diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index 3e1ed74..cd4ad21 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -87,7 +87,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn _applyLiquidationValuesToTotals(totals, singleLiquidation); } else if (ICR < troveManagerValues.MCR) { singleLiquidation = _tryLiquidateWithCap( - troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); debtInStabPool -= singleLiquidation.debtToOffset; _applyLiquidationValuesToTotals(totals, singleLiquidation); @@ -119,7 +119,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn _checkRecoveryModeGracePeriod(); singleLiquidation = _tryLiquidateWithCap( - _troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + _troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); if (singleLiquidation.debtToOffset == 0) continue; debtInStabPool -= singleLiquidation.debtToOffset; @@ -141,6 +141,9 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn address(this), totals.totalDebtToOffset, totals.totalCollToSendToSP ); } + + syncGracePeriod(); + troveManager.finalizeLiquidation( msg.sender, totals.totalDebtToRedistribute, @@ -199,7 +202,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn debtInStabPool -= singleLiquidation.debtToOffset; } else if (ICR < troveManagerValues.MCR) { singleLiquidation = _tryLiquidateWithCap( - troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); debtInStabPool -= singleLiquidation.debtToOffset; } else { @@ -231,7 +234,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn _liquidateNormalMode(s, troveManager, account, debtInStabPool, troveManagerValues.sunsetting); } else if (ICR < troveManagerValues.MCR) { singleLiquidation = _tryLiquidateWithCap( - troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); } else { if (troveManagerValues.sunsetting) continue; @@ -240,7 +243,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn // check the recovery mode grace period _checkRecoveryModeGracePeriod(); singleLiquidation = _tryLiquidateWithCap( - troveManager, account, debtInStabPool, troveManagerValues.MCR, troveManagerValues.price + troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); if (singleLiquidation.debtToOffset == 0) continue; } @@ -499,7 +502,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn emit GracePeriodDurationSet(_gracePeriod); } - function syncGracePeriod() external { + function syncGracePeriod() public { AppStorage.Layout storage s = AppStorage.layout(); s._syncGracePeriod(_inRecoveryMode()); } diff --git a/src/core/interfaces/IBorrowerOperationsFacet.sol b/src/core/interfaces/IBorrowerOperationsFacet.sol index e9fe9ab..dacf832 100644 --- a/src/core/interfaces/IBorrowerOperationsFacet.sol +++ b/src/core/interfaces/IBorrowerOperationsFacet.sol @@ -118,4 +118,6 @@ interface IBorrowerOperationsFacet { returns (IERC20 collateralToken, uint16 index); function forceResetTM(ITroveManager[] calldata _troveManagers) external; + + function syncGracePeriod() external; } diff --git a/src/core/libs/BorrowerOperationsLib.sol b/src/core/libs/BorrowerOperationsLib.sol index b9bd3c6..6d9fca8 100644 --- a/src/core/libs/BorrowerOperationsLib.sol +++ b/src/core/libs/BorrowerOperationsLib.sol @@ -209,4 +209,10 @@ library BorrowerOperationsLib { _maxFeePercentage <= SatoshiMath.DECIMAL_PRECISION, "Max fee percentage must less than or equal to 100%" ); } + + function _inRecoveryMode(AppStorage.Layout storage s) internal returns (bool) { + (uint256 _entireSystemColl, uint256 _entireSystemDebt) = _getGlobalSystemBalances(s); + uint256 TCR = SatoshiMath._computeCR(_entireSystemColl, _entireSystemDebt); + return _checkRecoveryMode(TCR); + } } From 2970eb44aed73b2166c949da32222da0f81829e3 Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Mon, 21 Apr 2025 14:19:38 +0800 Subject: [PATCH 4/7] patch --- src/core/Initializer.sol | 3 +-- src/core/facets/LiquidationFacet.sol | 14 +++++++++----- test/Liquidate.t.sol | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/core/Initializer.sol b/src/core/Initializer.sol index 9fec557..f078988 100644 --- a/src/core/Initializer.sol +++ b/src/core/Initializer.sol @@ -112,8 +112,7 @@ contract Initializer is Initializable, AccessControlInternal, OwnableInternal { /** * LiquidationFacet */ - s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; - s.recoveryModeGracePeriodDuration = Config.MINIMUM_GRACE_PERIOD; + initV2(); } function initV2() public reinitializer(2) { diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index cd4ad21..252b18c 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -116,7 +116,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn uint256 TCR = SatoshiMath._computeCR(entireSystemColl, entireSystemDebt); if (TCR >= Config.CCR || ICR >= TCR) break; - _checkRecoveryModeGracePeriod(); + if (_checkRecoveryModeGracePeriod()) break; singleLiquidation = _tryLiquidateWithCap( _troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price @@ -241,7 +241,7 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn uint256 TCR = SatoshiMath._computeCR(entireSystemColl, entireSystemDebt); if (TCR >= Config.CCR || ICR >= TCR) continue; // check the recovery mode grace period - _checkRecoveryModeGracePeriod(); + if (_checkRecoveryModeGracePeriod()) break; singleLiquidation = _tryLiquidateWithCap( troveManager, account, debtInStabPool, Config._110_PCT, troveManagerValues.price ); @@ -481,15 +481,19 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn return BorrowerOperationsLib._checkRecoveryMode(TCR); } - function _checkRecoveryModeGracePeriod() internal { + function _checkRecoveryModeGracePeriod() internal view returns (bool) { AppStorage.Layout storage s = AppStorage.layout(); uint128 cachedLastGracePeriodStartTimestamp = s.lastGracePeriodStartTimestamp; if (cachedLastGracePeriodStartTimestamp == Config.UNSET_TIMESTAMP) { - revert NotInGracePeriod(); + // grace period has never been triggered + return true; } if (uint128(block.timestamp) < cachedLastGracePeriodStartTimestamp + s.recoveryModeGracePeriodDuration) { - revert InGracePeriod(); + // it is still in grace period + return true; } + + return false; } // --- Grace Period --- diff --git a/test/Liquidate.t.sol b/test/Liquidate.t.sol index 661a94b..bc3febc 100644 --- a/test/Liquidate.t.sol +++ b/test/Liquidate.t.sol @@ -248,7 +248,7 @@ contract LiquidateTest is DeployBase, TroveBase { vm.startPrank(user4); liquidationManagerProxy().syncGracePeriod(); - vm.expectRevert(ILiquidationFacet.InGracePeriod.selector); + vm.expectRevert("TroveManager: nothing to liquidate"); liquidationManagerProxy().liquidate(troveManagerBeaconProxy, user1); vm.warp(block.timestamp + 20 minutes); // the user1 coll will capped at 1.1 * debt, no redistribution From 99b51dd53cca2f87499b4e6894a89265d7ca8c3e Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Tue, 29 Apr 2025 14:03:31 +0400 Subject: [PATCH 5/7] patch --- src/core/TroveManager.sol | 8 ++++---- src/core/facets/LiquidationFacet.sol | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/TroveManager.sol b/src/core/TroveManager.sol index 9113cf8..969d203 100644 --- a/src/core/TroveManager.sol +++ b/src/core/TroveManager.sol @@ -667,8 +667,6 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { totals.collateralToSendToRedeemer = totals.totalCollateralDrawn - totals.collateralFee; - IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); - emit Redemption( msg.sender, _debtAmount, totals.totalDebtToRedeem, totals.totalCollateralDrawn, totals.collateralFee ); @@ -680,6 +678,8 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { _sendCollateral(msg.sender, totals.collateralToSendToRedeemer); _resetState(); + IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); + if (interestPayable > 0) { collectInterests(); } @@ -1112,8 +1112,6 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { { _requireCallerIsSatoshiXapp(); - IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); - // redistribute debt and collateral _redistributeDebtAndColl(_debt, _coll); @@ -1139,6 +1137,8 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable { collateralToken.safeIncreaseAllowance(rewardManager, collGasCompToFeeReceiver); } IRewardManager(rewardManager).increaseCollPerUintStaked(collGasCompToFeeReceiver); + + IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod(); } function _redistributeDebtAndColl(uint256 _debt, uint256 _coll) internal { diff --git a/src/core/facets/LiquidationFacet.sol b/src/core/facets/LiquidationFacet.sol index 252b18c..3ff8d52 100644 --- a/src/core/facets/LiquidationFacet.sol +++ b/src/core/facets/LiquidationFacet.sol @@ -142,8 +142,6 @@ contract LiquidationFacet is ILiquidationFacet, AccessControlInternal, OwnableIn ); } - syncGracePeriod(); - troveManager.finalizeLiquidation( msg.sender, totals.totalDebtToRedistribute, From 137e0f36bfd614341edf3ee9ffa03c8aacfcd97d Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Tue, 10 Jun 2025 02:49:17 +0800 Subject: [PATCH 6/7] add upgrade script --- script/upgrade/UpgradeGracePeriod.s.sol | 124 ++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 script/upgrade/UpgradeGracePeriod.s.sol diff --git a/script/upgrade/UpgradeGracePeriod.s.sol b/script/upgrade/UpgradeGracePeriod.s.sol new file mode 100644 index 0000000..8925ced --- /dev/null +++ b/script/upgrade/UpgradeGracePeriod.s.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { Initializer } from "../../src/core/Initializer.sol"; +import { TroveManager } from "../../src/core/TroveManager.sol"; +import { BorrowerOperationsFacet } from "../../src/core/facets/BorrowerOperationsFacet.sol"; +import { LiquidationFacet } from "../../src/core/facets/LiquidationFacet.sol"; +import { IBorrowerOperationsFacet } from "../../src/core/interfaces/IBorrowerOperationsFacet.sol"; +import { IFactoryFacet } from "../../src/core/interfaces/IFactoryFacet.sol"; +import { ILiquidationFacet } from "../../src/core/interfaces/ILiquidationFacet.sol"; + +import { ISatoshiXApp } from "../../src/core/interfaces/ISatoshiXApp.sol"; +import { ITroveManager } from "../../src/core/interfaces/ITroveManager.sol"; +import { IERC2535DiamondCutInternal } from "@solidstate/contracts/interfaces/IERC2535DiamondCutInternal.sol"; +import { Script, console } from "forge-std/Script.sol"; + +address payable constant SATOSHI_X_APP_ADDRESS = payable(0x07BbC5A83B83a5C440D1CAedBF1081426d0AA4Ec); + +interface IBeacon { + function upgradeTo(address newImplementation) external; + function implementation() external view returns (address); +} + +contract UpgradeGracePeriodScript is Script { + uint256 internal OWNER_PRIVATE_KEY; + + function setUp() public { + OWNER_PRIVATE_KEY = uint256(vm.envBytes32("OWNER_PRIVATE_KEY")); + } + + function run() public { + vm.startBroadcast(OWNER_PRIVATE_KEY); + + _upgradeBOFaucet(); + _upgradeLiquidationFacet(); + _upgradeInitializer(); + + // initV2 + Initializer initializer = Initializer(SATOSHI_X_APP_ADDRESS); + initializer.initV2(); + + vm.stopBroadcast(); + } + + function _diamondCut( + address target, + bytes4[] memory selectors, + bytes4[] memory newSelectors, + bytes memory data + ) + internal + { + IERC2535DiamondCutInternal.FacetCut[] memory facetCuts = new IERC2535DiamondCutInternal.FacetCut[](2); + + facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({ + target: target, + action: IERC2535DiamondCutInternal.FacetCutAction.REPLACE, + selectors: selectors + }); + + facetCuts[1] = IERC2535DiamondCutInternal.FacetCut({ + target: target, + action: IERC2535DiamondCutInternal.FacetCutAction.ADD, + selectors: newSelectors + }); + + ISatoshiXApp XAPP = ISatoshiXApp(SATOSHI_X_APP_ADDRESS); + XAPP.diamondCut(facetCuts, address(0), data); + } + + function _upgradeBOFaucet() internal { + address newBorrowerOperationsImpl = address(new BorrowerOperationsFacet()); + + bytes4[] memory selectors = new bytes4[](19); + selectors[0] = IBorrowerOperationsFacet.addColl.selector; + selectors[1] = IBorrowerOperationsFacet.adjustTrove.selector; + selectors[2] = IBorrowerOperationsFacet.checkRecoveryMode.selector; + selectors[3] = IBorrowerOperationsFacet.closeTrove.selector; + selectors[4] = IBorrowerOperationsFacet.fetchBalances.selector; + selectors[5] = IBorrowerOperationsFacet.getCompositeDebt.selector; + selectors[6] = IBorrowerOperationsFacet.getGlobalSystemBalances.selector; + selectors[7] = IBorrowerOperationsFacet.getTCR.selector; + selectors[8] = IBorrowerOperationsFacet.isApprovedDelegate.selector; + selectors[9] = IBorrowerOperationsFacet.minNetDebt.selector; + selectors[10] = IBorrowerOperationsFacet.openTrove.selector; + selectors[11] = IBorrowerOperationsFacet.removeTroveManager.selector; + selectors[12] = IBorrowerOperationsFacet.repayDebt.selector; + selectors[13] = IBorrowerOperationsFacet.setDelegateApproval.selector; + selectors[14] = IBorrowerOperationsFacet.setMinNetDebt.selector; + selectors[15] = IBorrowerOperationsFacet.troveManagersData.selector; + selectors[16] = IBorrowerOperationsFacet.withdrawColl.selector; + selectors[17] = IBorrowerOperationsFacet.withdrawDebt.selector; + selectors[18] = IBorrowerOperationsFacet.forceResetTM.selector; + + bytes4[] memory newSelectors = new bytes4[](1); + newSelectors[0] = IBorrowerOperationsFacet.syncGracePeriod.selector; + + _diamondCut(newBorrowerOperationsImpl, selectors, newSelectors, ""); + } + + function _upgradeLiquidationFacet() internal { + address newLiquidationFacetImpl = address(new LiquidationFacet()); + bytes4[] memory selectors = new bytes4[](3); + selectors[0] = ILiquidationFacet.batchLiquidateTroves.selector; + selectors[1] = ILiquidationFacet.liquidate.selector; + selectors[2] = ILiquidationFacet.liquidateTroves.selector; + + bytes4[] memory newSelectors = new bytes4[](2); + newSelectors[0] = ILiquidationFacet.setGracePeriod.selector; + newSelectors[1] = ILiquidationFacet.syncGracePeriod.selector; + + _diamondCut(newLiquidationFacetImpl, selectors, newSelectors, ""); + } + + function _upgradeInitializer() internal { + Initializer initializer = new Initializer(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Initializer.init.selector; + bytes4[] memory newSelectors = new bytes4[](1); + newSelectors[0] = Initializer.initV2.selector; + bytes memory data = abi.encodeWithSelector(Initializer.initV2.selector); + _diamondCut(address(initializer), selectors, newSelectors, data); + } +} From df88d0b6791dd55a77ce481477a9cac68bb93d16 Mon Sep 17 00:00:00 2001 From: ashirleyshe Date: Tue, 17 Jun 2025 00:33:50 +0800 Subject: [PATCH 7/7] update script --- script/upgrade/GracePeriodUpgradeTest.t.sol | 83 +++++++++++++ script/upgrade/UpgradeGracePeriod.s.sol | 128 ++++++++++++-------- 2 files changed, 159 insertions(+), 52 deletions(-) create mode 100644 script/upgrade/GracePeriodUpgradeTest.t.sol diff --git a/script/upgrade/GracePeriodUpgradeTest.t.sol b/script/upgrade/GracePeriodUpgradeTest.t.sol new file mode 100644 index 0000000..c7a2bcb --- /dev/null +++ b/script/upgrade/GracePeriodUpgradeTest.t.sol @@ -0,0 +1,83 @@ +// test/upgrade/GracePeriodUpgradeTest.t.sol + +import { UpgradeGracePeriodLib } from "../../script/upgrade/UpgradeGracePeriod.s.sol"; +import { TroveManager } from "../../src/core/TroveManager.sol"; +import { ISatoshiPeriphery, LzSendParam } from "../../src/core/helpers/interfaces/ISatoshiPeriphery.sol"; +import { IBorrowerOperationsFacet } from "../../src/core/interfaces/IBorrowerOperationsFacet.sol"; +import { ILiquidationFacet } from "../../src/core/interfaces/ILiquidationFacet.sol"; +import { IPriceFeedAggregatorFacet } from "../../src/core/interfaces/IPriceFeedAggregatorFacet.sol"; +import { ITroveManager } from "../../src/core/interfaces/ITroveManager.sol"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +interface IBeacon { + function upgradeTo(address newImplementation) external; + function implementation() external view returns (address); +} + +contract GracePeriodUpgradeTest is Test { + // Base network + address payable constant SATOSHI_X_APP_ADDRESS = payable(0x9a3c724ee9603A7550499bE73DC743B371811dd3); + address payable constant OWNER_ADDRESS = payable(0xd3e87B4B76E6F8bFf454AAFc2AD3271C5b317d47); + address payable constant TM_BEACON_ADDRESS = payable(0xefAa8B485355066fA0993A605466eEf0ec026860); + IERC20 constant WETH = IERC20(0x4200000000000000000000000000000000000006); + ISatoshiPeriphery constant SATOSHI_PERIPHERY = ISatoshiPeriphery(0x9d9f0D9a13d3bA201003DD2e8950059d2c08D782); + ITroveManager constant TROVE_MANAGER = ITroveManager(0xddac7d4e228c205197FE9961865FFE20173dE56B); + + function setUp() public { + vm.createSelectFork(vm.envString("RPC_URL_BASE"), 31_529_675); + } + + function beforeTestSetup(bytes4 testSelector) public returns (bytes[] memory beforeTestCalldata) { + if ( + testSelector == this.testFork_NormalLiquidationAfterUpgrade.selector + || testSelector == this.testFork_SetGracePeriod.selector + || testSelector == this.testFork_SyncGracePeriod.selector + ) { + beforeTestCalldata = new bytes[](1); + beforeTestCalldata[0] = abi.encodeWithSelector(this.testFork_UpgradeGracePeriod.selector); + } + } + + function testFork_UpgradeGracePeriod() public { + vm.startPrank(OWNER_ADDRESS); + + UpgradeGracePeriodLib.upgradeGracePeriod(SATOSHI_X_APP_ADDRESS); + IBeacon troveManagerBeacon = IBeacon(TM_BEACON_ADDRESS); + address newTroveManagerImpl = address(new TroveManager()); + troveManagerBeacon.upgradeTo(address(newTroveManagerImpl)); + } + + function testFork_NormalLiquidationAfterUpgrade() public { + address user = makeAddr("user"); + deal(address(WETH), user, 1000 ether); + + vm.startPrank(user); + + LzSendParam memory sendParam; + IBorrowerOperationsFacet(SATOSHI_X_APP_ADDRESS).setDelegateApproval(address(SATOSHI_PERIPHERY), true); + WETH.approve(address(SATOSHI_PERIPHERY), 1000 ether); + SATOSHI_PERIPHERY.openTrove(TROVE_MANAGER, 1e18, 1 ether, 2000e18, address(0), address(0), sendParam); + + // ICR < MCR + vm.mockCall( + address(SATOSHI_X_APP_ADDRESS), + abi.encodeWithSelector(IPriceFeedAggregatorFacet.fetchPrice.selector, address(WETH)), + abi.encode(2200e18) + ); + + ILiquidationFacet(SATOSHI_X_APP_ADDRESS).liquidate(TROVE_MANAGER, user); + } + + function testFork_SetGracePeriod() public { + vm.startPrank(OWNER_ADDRESS); + + ILiquidationFacet(SATOSHI_X_APP_ADDRESS).setGracePeriod(15 minutes); + } + + function testFork_SyncGracePeriod() public { + IBorrowerOperationsFacet(SATOSHI_X_APP_ADDRESS).syncGracePeriod(); + } +} diff --git a/script/upgrade/UpgradeGracePeriod.s.sol b/script/upgrade/UpgradeGracePeriod.s.sol index 8925ced..d18cd66 100644 --- a/script/upgrade/UpgradeGracePeriod.s.sol +++ b/script/upgrade/UpgradeGracePeriod.s.sol @@ -15,62 +15,63 @@ import { IERC2535DiamondCutInternal } from "@solidstate/contracts/interfaces/IER import { Script, console } from "forge-std/Script.sol"; address payable constant SATOSHI_X_APP_ADDRESS = payable(0x07BbC5A83B83a5C440D1CAedBF1081426d0AA4Ec); +address payable constant TM_BEACON_ADDRESS = payable(0x00); interface IBeacon { function upgradeTo(address newImplementation) external; function implementation() external view returns (address); } -contract UpgradeGracePeriodScript is Script { - uint256 internal OWNER_PRIVATE_KEY; - - function setUp() public { - OWNER_PRIVATE_KEY = uint256(vm.envBytes32("OWNER_PRIVATE_KEY")); - } - - function run() public { - vm.startBroadcast(OWNER_PRIVATE_KEY); - - _upgradeBOFaucet(); - _upgradeLiquidationFacet(); - _upgradeInitializer(); - - // initV2 - Initializer initializer = Initializer(SATOSHI_X_APP_ADDRESS); - initializer.initV2(); - - vm.stopBroadcast(); - } - - function _diamondCut( - address target, - bytes4[] memory selectors, - bytes4[] memory newSelectors, - bytes memory data - ) - internal - { - IERC2535DiamondCutInternal.FacetCut[] memory facetCuts = new IERC2535DiamondCutInternal.FacetCut[](2); +library UpgradeGracePeriodLib { + function upgradeGracePeriod(address payable satoshiXApp) internal { + IERC2535DiamondCutInternal.FacetCut[] memory facetCuts = new IERC2535DiamondCutInternal.FacetCut[](6); + // BorrowerOperationsFacet + address newBorrowerOperationsImpl = address(new BorrowerOperationsFacet()); facetCuts[0] = IERC2535DiamondCutInternal.FacetCut({ - target: target, + target: newBorrowerOperationsImpl, action: IERC2535DiamondCutInternal.FacetCutAction.REPLACE, - selectors: selectors + selectors: getBOSelectors() }); - facetCuts[1] = IERC2535DiamondCutInternal.FacetCut({ - target: target, + target: newBorrowerOperationsImpl, action: IERC2535DiamondCutInternal.FacetCutAction.ADD, - selectors: newSelectors + selectors: getBONewSelectors() }); - ISatoshiXApp XAPP = ISatoshiXApp(SATOSHI_X_APP_ADDRESS); - XAPP.diamondCut(facetCuts, address(0), data); - } + // LiquidationFacet + address newLiquidationFacetImpl = address(new LiquidationFacet()); + facetCuts[2] = IERC2535DiamondCutInternal.FacetCut({ + target: newLiquidationFacetImpl, + action: IERC2535DiamondCutInternal.FacetCutAction.REPLACE, + selectors: getLiquidationSelectors() + }); + facetCuts[3] = IERC2535DiamondCutInternal.FacetCut({ + target: newLiquidationFacetImpl, + action: IERC2535DiamondCutInternal.FacetCutAction.ADD, + selectors: getLiquidationNewSelectors() + }); - function _upgradeBOFaucet() internal { - address newBorrowerOperationsImpl = address(new BorrowerOperationsFacet()); + // Initializer + address newInitializerImpl = address(new Initializer()); + facetCuts[4] = IERC2535DiamondCutInternal.FacetCut({ + target: newInitializerImpl, + action: IERC2535DiamondCutInternal.FacetCutAction.REPLACE, + selectors: getInitializerSelectors() + }); + facetCuts[5] = IERC2535DiamondCutInternal.FacetCut({ + target: newInitializerImpl, + action: IERC2535DiamondCutInternal.FacetCutAction.ADD, + selectors: getInitializerNewSelectors() + }); + // Upgrade and run initV2 + ISatoshiXApp XAPP = ISatoshiXApp(satoshiXApp); + bytes memory data = abi.encodeWithSelector(Initializer.initV2.selector); + XAPP.diamondCut(facetCuts, satoshiXApp, data); + } + + function getBOSelectors() internal pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](19); selectors[0] = IBorrowerOperationsFacet.addColl.selector; selectors[1] = IBorrowerOperationsFacet.adjustTrove.selector; @@ -91,34 +92,57 @@ contract UpgradeGracePeriodScript is Script { selectors[16] = IBorrowerOperationsFacet.withdrawColl.selector; selectors[17] = IBorrowerOperationsFacet.withdrawDebt.selector; selectors[18] = IBorrowerOperationsFacet.forceResetTM.selector; + return selectors; + } + function getBONewSelectors() internal pure returns (bytes4[] memory) { bytes4[] memory newSelectors = new bytes4[](1); newSelectors[0] = IBorrowerOperationsFacet.syncGracePeriod.selector; - - _diamondCut(newBorrowerOperationsImpl, selectors, newSelectors, ""); + return newSelectors; } - function _upgradeLiquidationFacet() internal { - address newLiquidationFacetImpl = address(new LiquidationFacet()); + function getLiquidationSelectors() internal pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](3); selectors[0] = ILiquidationFacet.batchLiquidateTroves.selector; selectors[1] = ILiquidationFacet.liquidate.selector; selectors[2] = ILiquidationFacet.liquidateTroves.selector; + return selectors; + } - bytes4[] memory newSelectors = new bytes4[](2); + function getLiquidationNewSelectors() internal pure returns (bytes4[] memory) { + bytes4[] memory newSelectors = new bytes4[](1); newSelectors[0] = ILiquidationFacet.setGracePeriod.selector; - newSelectors[1] = ILiquidationFacet.syncGracePeriod.selector; - - _diamondCut(newLiquidationFacetImpl, selectors, newSelectors, ""); + return newSelectors; } - function _upgradeInitializer() internal { - Initializer initializer = new Initializer(); + function getInitializerSelectors() internal pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](1); selectors[0] = Initializer.init.selector; + return selectors; + } + + function getInitializerNewSelectors() internal pure returns (bytes4[] memory) { bytes4[] memory newSelectors = new bytes4[](1); newSelectors[0] = Initializer.initV2.selector; - bytes memory data = abi.encodeWithSelector(Initializer.initV2.selector); - _diamondCut(address(initializer), selectors, newSelectors, data); + return newSelectors; + } +} + +contract UpgradeGracePeriodScript is Script { + uint256 internal OWNER_PRIVATE_KEY; + + function setUp() public { + OWNER_PRIVATE_KEY = uint256(vm.envBytes32("OWNER_PRIVATE_KEY")); + } + + function run() public { + vm.startBroadcast(OWNER_PRIVATE_KEY); + + UpgradeGracePeriodLib.upgradeGracePeriod(SATOSHI_X_APP_ADDRESS); + IBeacon troveManagerBeacon = IBeacon(TM_BEACON_ADDRESS); + address newTroveManagerImpl = address(new TroveManager()); + troveManagerBeacon.upgradeTo(address(newTroveManagerImpl)); + + vm.stopBroadcast(); } }