-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: liquidation cap and grace period timelock in recovery mode #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5b04b1c
dd38e11
04e70f3
2970eb4
99b51dd
137e0f3
df88d0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // 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); | ||
| address payable constant TM_BEACON_ADDRESS = payable(0x00); | ||
|
|
||
| interface IBeacon { | ||
| function upgradeTo(address newImplementation) external; | ||
| function implementation() external view returns (address); | ||
| } | ||
|
|
||
| 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: newBorrowerOperationsImpl, | ||
| action: IERC2535DiamondCutInternal.FacetCutAction.REPLACE, | ||
| selectors: getBOSelectors() | ||
| }); | ||
| facetCuts[1] = IERC2535DiamondCutInternal.FacetCut({ | ||
| target: newBorrowerOperationsImpl, | ||
| action: IERC2535DiamondCutInternal.FacetCutAction.ADD, | ||
| selectors: getBONewSelectors() | ||
| }); | ||
|
|
||
| // 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() | ||
| }); | ||
|
|
||
| // 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; | ||
| 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; | ||
| return selectors; | ||
| } | ||
|
|
||
| function getBONewSelectors() internal pure returns (bytes4[] memory) { | ||
| bytes4[] memory newSelectors = new bytes4[](1); | ||
| newSelectors[0] = IBorrowerOperationsFacet.syncGracePeriod.selector; | ||
| return newSelectors; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| function getLiquidationNewSelectors() internal pure returns (bytes4[] memory) { | ||
| bytes4[] memory newSelectors = new bytes4[](1); | ||
| newSelectors[0] = ILiquidationFacet.setGracePeriod.selector; | ||
| return newSelectors; | ||
| } | ||
|
|
||
| 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; | ||
| 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(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,5 +108,20 @@ contract Initializer is Initializable, AccessControlInternal, OwnableInternal { | |
| */ | ||
| // debtToken | ||
| // rewardManager | ||
|
|
||
| /** | ||
| * LiquidationFacet | ||
| */ | ||
| initV2(); | ||
| } | ||
|
|
||
| function initV2() public reinitializer(2) { | ||
| AppStorage.Layout storage s = AppStorage.layout(); | ||
|
|
||
| /** | ||
| * LiquidationFacet | ||
| */ | ||
| s.lastGracePeriodStartTimestamp = Config.UNSET_TIMESTAMP; | ||
| s.recoveryModeGracePeriodDuration = Config.MINIMUM_GRACE_PERIOD; | ||
|
Comment on lines
+124
to
+125
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides this, I suggest adding another initialization function dedicated to these two variables. Otherwise, the state could be problematic after upgrading, e.g., unable to start a grace period countdown if already in recovery mode. I'm thinking about adding an |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also a
syncGracePeriodin the LiquidationFacet, it causes selector collision.