Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions script/upgrade/GracePeriodUpgradeTest.t.sol
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();
}
}
148 changes: 148 additions & 0 deletions script/upgrade/UpgradeGracePeriod.s.sol
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a syncGracePeriod in the LiquidationFacet, it causes selector collision.

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();
}
}
3 changes: 3 additions & 0 deletions src/core/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/core/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -45,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;
}
15 changes: 15 additions & 0 deletions src/core/Initializer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 initv2 with a reinitializer modifier. Also have to make sure to to call initv2 inside the original init as well so that it becomes compatible for new chain deployment.

}
}
5 changes: 5 additions & 0 deletions src/core/TroveManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,8 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable {
_sendCollateral(msg.sender, totals.collateralToSendToRedeemer);
_resetState();

IBorrowerOperationsFacet(satoshiXApp).syncGracePeriod();

if (interestPayable > 0) {
collectInterests();
}
Expand Down Expand Up @@ -1109,6 +1111,7 @@ contract TroveManager is ITroveManager, Initializable, OwnableUpgradeable {
external
{
_requireCallerIsSatoshiXapp();

// redistribute debt and collateral
_redistributeDebtAndColl(_debt, _coll);

Expand All @@ -1134,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 {
Expand Down
Loading