From 3fee998e273a13f439cec4292b0c52ecb3860e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Sun, 28 Apr 2024 18:08:57 +0100 Subject: [PATCH] feat: Reduce liquidation penalty --- contracts/src/BorrowerOperations.sol | 24 +- contracts/src/CollateralRegistry.sol | 11 +- contracts/src/Dependencies/LiquityBase.sol | 3 - contracts/src/Dependencies/LiquityMath.sol | 4 + .../src/Interfaces/IBorrowerOperations.sol | 1 - contracts/src/Interfaces/ILiquityBase.sol | 1 - contracts/src/Interfaces/ITroveManager.sol | 2 + contracts/src/TroveManager.sol | 139 ++++++--- contracts/src/deployment.sol | 5 +- contracts/src/test/TestContracts/BaseTest.sol | 2 +- .../BorrowerOperationsTester.sol | 2 +- .../CollateralRegistryTester.sol | 4 +- .../src/test/TestContracts/DevTestSetup.sol | 2 + .../test/TestContracts/TroveManagerTester.sol | 4 + contracts/src/test/liquidations.t.sol | 284 ++++++++++++++++++ contracts/src/test/liquidationsLST.t.sol | 250 +++++++++++++++ contracts/src/test/redemptions.t.sol | 24 +- 17 files changed, 691 insertions(+), 71 deletions(-) create mode 100644 contracts/src/test/liquidations.t.sol create mode 100644 contracts/src/test/liquidationsLST.t.sol diff --git a/contracts/src/BorrowerOperations.sol b/contracts/src/BorrowerOperations.sol index 54c9afdd..ba5f462d 100644 --- a/contracts/src/BorrowerOperations.sol +++ b/contracts/src/BorrowerOperations.sol @@ -23,7 +23,7 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe // --- Connected contract declarations --- IERC20 public immutable ETH; - ITroveManager public troveManager; + ITroveManager public immutable troveManager; address stabilityPoolAddress; address gasPoolAddress; ICollSurplusPool collSurplusPool; @@ -31,6 +31,9 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe // A doubly linked list of Troves, sorted by their collateral ratios ISortedTroves public sortedTroves; + // Minimum collateral ratio for individual troves + uint256 public immutable MCR; + /* --- Variable container structs --- Used to hold, return and assign variables inside a function, in order to avoid the error: @@ -101,15 +104,21 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe ); event BoldBorrowingFeePaid(uint256 indexed _troveId, uint256 _boldFee); - constructor(address _ETHAddress) { - checkContract(_ETHAddress); - ETH = IERC20(_ETHAddress); + constructor(IERC20 _ETH, ITroveManager _troveManager) { + checkContract(address(_ETH)); + checkContract(address(_troveManager)); + + ETH = _ETH; + troveManager = _troveManager; + + MCR = _troveManager.MCR(); + + emit TroveManagerAddressChanged(address(_troveManager)); } // --- Dependency setters --- function setAddresses( - address _troveManagerAddress, address _activePoolAddress, address _defaultPoolAddress, address _stabilityPoolAddress, @@ -122,7 +131,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe // This makes impossible to open a trove with zero withdrawn Bold assert(MIN_NET_DEBT > 0); - checkContract(_troveManagerAddress); checkContract(_activePoolAddress); checkContract(_defaultPoolAddress); checkContract(_stabilityPoolAddress); @@ -132,7 +140,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe checkContract(_sortedTrovesAddress); checkContract(_boldTokenAddress); - troveManager = ITroveManager(_troveManagerAddress); activePool = IActivePool(_activePoolAddress); defaultPool = IDefaultPool(_defaultPoolAddress); stabilityPoolAddress = _stabilityPoolAddress; @@ -142,7 +149,6 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe sortedTroves = ISortedTroves(_sortedTrovesAddress); boldToken = IBoldToken(_boldTokenAddress); - emit TroveManagerAddressChanged(_troveManagerAddress); emit ActivePoolAddressChanged(_activePoolAddress); emit DefaultPoolAddressChanged(_defaultPoolAddress); emit StabilityPoolAddressChanged(_stabilityPoolAddress); @@ -770,7 +776,7 @@ contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOpe } } - function _requireICRisAboveMCR(uint256 _newICR) internal pure { + function _requireICRisAboveMCR(uint256 _newICR) internal view { require(_newICR >= MCR, "BorrowerOps: An operation that would result in ICR < MCR is not permitted"); } diff --git a/contracts/src/CollateralRegistry.sol b/contracts/src/CollateralRegistry.sol index 13504e18..6dcc49aa 100644 --- a/contracts/src/CollateralRegistry.sol +++ b/contracts/src/CollateralRegistry.sol @@ -118,7 +118,9 @@ contract CollateralRegistry is LiquityBase, ICollateralRegistry { uint256 redeemedAmount; } - function redeemCollateral(uint256 _boldAmount, uint256 _maxIterationsPerCollateral, uint256 _maxFeePercentage) external { + function redeemCollateral(uint256 _boldAmount, uint256 _maxIterationsPerCollateral, uint256 _maxFeePercentage) + external + { _requireValidMaxFeePercentage(_maxFeePercentage); _requireAmountGreaterThanZero(_boldAmount); _requireBoldBalanceCoversRedemption(boldToken, msg.sender, _boldAmount); @@ -135,7 +137,8 @@ contract CollateralRegistry is LiquityBase, ICollateralRegistry { // We only compute it here, and update it at the end, // because the final redeemed amount may be less than the requested amount // Redeemers should take this into account in order to request the optimal amount to not overpay - uint256 redemptionRate = _calcRedemptionRate(_getUpdatedBaseRateFromRedemption(_boldAmount, totals.boldSupplyAtStart)); + uint256 redemptionRate = + _calcRedemptionRate(_getUpdatedBaseRateFromRedemption(_boldAmount, totals.boldSupplyAtStart)); require(redemptionRate <= _maxFeePercentage, "CR: Fee exceeded provided maximum"); // Implicit by the above and the _requireValidMaxFeePercentage checks //require(newBaseRate < DECIMAL_PRECISION, "CR: Fee would eat up all collateral"); @@ -195,9 +198,7 @@ contract CollateralRegistry is LiquityBase, ICollateralRegistry { } // Updates the `baseRate` state with math from `_getUpdatedBaseRateFromRedemption` - function _updateBaseRateAndGetRedemptionRate(uint256 _boldAmount, uint256 _totalBoldSupplyAtStart) - internal - { + function _updateBaseRateAndGetRedemptionRate(uint256 _boldAmount, uint256 _totalBoldSupplyAtStart) internal { uint256 newBaseRate = _getUpdatedBaseRateFromRedemption(_boldAmount, _totalBoldSupplyAtStart); //assert(newBaseRate <= DECIMAL_PRECISION); // This is already enforced in `_getUpdatedBaseRateFromRedemption` diff --git a/contracts/src/Dependencies/LiquityBase.sol b/contracts/src/Dependencies/LiquityBase.sol index 83c64f2e..0c7cba76 100644 --- a/contracts/src/Dependencies/LiquityBase.sol +++ b/contracts/src/Dependencies/LiquityBase.sol @@ -20,9 +20,6 @@ contract LiquityBase is BaseMath, ILiquityBase { uint256 public constant _100pct = 1000000000000000000; // 1e18 == 100% - // Minimum collateral ratio for individual troves - uint256 public constant MCR = 1100000000000000000; // 110% - // Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered. uint256 public constant CCR = 1500000000000000000; // 150% diff --git a/contracts/src/Dependencies/LiquityMath.sol b/contracts/src/Dependencies/LiquityMath.sol index a7c0718f..d96782eb 100644 --- a/contracts/src/Dependencies/LiquityMath.sol +++ b/contracts/src/Dependencies/LiquityMath.sol @@ -13,6 +13,10 @@ library LiquityMath { return (_a >= _b) ? _a : _b; } + function _sub_min_0(uint256 _a, uint256 _b) internal pure returns (uint256) { + return (_a > _b) ? _a - _b : 0; + } + /* * Multiply two decimal numbers and use normal rounding rules: * -round product up if 19'th mantissa digit >= 5 diff --git a/contracts/src/Interfaces/IBorrowerOperations.sol b/contracts/src/Interfaces/IBorrowerOperations.sol index 7609d7ae..e1929725 100644 --- a/contracts/src/Interfaces/IBorrowerOperations.sol +++ b/contracts/src/Interfaces/IBorrowerOperations.sol @@ -13,7 +13,6 @@ interface IBorrowerOperations is ILiquityBase { function sortedTroves() external view returns (ISortedTroves); function setAddresses( - address _troveManagerAddress, address _activePoolAddress, address _defaultPoolAddress, address _stabilityPoolAddress, diff --git a/contracts/src/Interfaces/ILiquityBase.sol b/contracts/src/Interfaces/ILiquityBase.sol index 81504c2d..086a1f73 100644 --- a/contracts/src/Interfaces/ILiquityBase.sol +++ b/contracts/src/Interfaces/ILiquityBase.sol @@ -12,6 +12,5 @@ interface ILiquityBase { function priceFeed() external view returns (IPriceFeed); function BOLD_GAS_COMPENSATION() external view returns (uint256); function MIN_NET_DEBT() external view returns (uint256); - function MCR() external view returns (uint256); function getEntireSystemDebt() external view returns (uint256); } diff --git a/contracts/src/Interfaces/ITroveManager.sol b/contracts/src/Interfaces/ITroveManager.sol index 22f777cc..bdfa989c 100644 --- a/contracts/src/Interfaces/ITroveManager.sol +++ b/contracts/src/Interfaces/ITroveManager.sol @@ -11,6 +11,8 @@ import "./ISortedTroves.sol"; // Common interface for the Trove Manager. interface ITroveManager is IERC721, ILiquityBase { + function MCR() external view returns (uint256); + function setAddresses( address _borrowerOperationsAddress, address _activePoolAddress, diff --git a/contracts/src/TroveManager.sol b/contracts/src/TroveManager.sol index fd62d3ee..d277f67a 100644 --- a/contracts/src/TroveManager.sol +++ b/contracts/src/TroveManager.sol @@ -31,6 +31,13 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { // --- Data structures --- + // Minimum collateral ratio for individual troves + uint256 public immutable MCR; + // Liquidation penalty for troves offset to the SP + uint256 public immutable LIQUIDATION_PENALTY_SP; + // Liquidation penalty for troves redistributed + uint256 public immutable LIQUIDATION_PENALTY_REDISTRIBUTION; + uint256 public constant SECONDS_IN_ONE_YEAR = 31536000; // 60 * 60 * 24 * 365, uint256 public constant STALE_TROVE_DURATION = 7776000; // 90 days: 60*60*24*90 = 7776000 @@ -242,7 +249,18 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { redeemCollateral } - constructor() ERC721(NAME, SYMBOL) {} + constructor(uint256 _mcr, uint256 _liquidationPenaltySP, uint256 _liquidationPenaltyRedistribution) + ERC721(NAME, SYMBOL) + { + require(_mcr > 1e18 && _mcr < 2e18, "Invalid MCR"); + require(_liquidationPenaltySP >= 5e16, "SP penalty too low"); + require(_liquidationPenaltySP <= _liquidationPenaltyRedistribution, "SP penalty cannot be > redist"); + require(_liquidationPenaltyRedistribution <= 10e16, "Redistribution penalty too high"); + + MCR = _mcr; + LIQUIDATION_PENALTY_SP = _liquidationPenaltySP; + LIQUIDATION_PENALTY_REDISTRIBUTION = _liquidationPenaltyRedistribution; + } // --- Dependency setter --- @@ -313,7 +331,8 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { IActivePool _activePool, IDefaultPool _defaultPool, uint256 _troveId, - uint256 _boldInStabPool + uint256 _boldInStabPool, + uint256 _price ) internal returns (LiquidationValues memory singleLiquidation) { LocalVariables_InnerSingleLiquidateFunction memory vars; ( @@ -342,10 +361,17 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { singleLiquidation.debtToOffset, singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, - singleLiquidation.collToRedistribute - ) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, collToLiquidate, _boldInStabPool); + singleLiquidation.collToRedistribute, + singleLiquidation.collSurplus + ) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, collToLiquidate, _boldInStabPool, _price); _closeTrove(_troveId, Status.closedByLiquidation); + + // Differencen between liquidation penalty and liquidation threshold + if (singleLiquidation.collSurplus > 0) { + collSurplusPool.accountSurplus(_troveId, singleLiquidation.collSurplus); + } + emit TroveLiquidated( _troveId, singleLiquidation.entireTroveDebt, @@ -416,12 +442,19 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { singleLiquidation.debtToOffset, singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, - singleLiquidation.collToRedistribute + singleLiquidation.collToRedistribute, + singleLiquidation.collSurplus ) = _getOffsetAndRedistributionVals( - singleLiquidation.entireTroveDebt, vars.collToLiquidate, _boldInStabPool + singleLiquidation.entireTroveDebt, vars.collToLiquidate, _boldInStabPool, _price ); _closeTrove(_troveId, Status.closedByLiquidation); + + // Differencen between liquidation penalty and liquidation threshold + if (singleLiquidation.collSurplus > 0) { + collSurplusPool.accountSurplus(_troveId, singleLiquidation.collSurplus); + } + emit TroveLiquidated( _troveId, singleLiquidation.entireTroveDebt, @@ -475,33 +508,67 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { */ function _getOffsetAndRedistributionVals( uint256 _entireTroveDebt, - uint256 _collToLiquidate, - uint256 _boldInStabPool + uint256 _collToLiquidate, // gas compensation is already subtracted + uint256 _boldInStabPool, + uint256 _price ) internal - pure - returns (uint256 debtToOffset, uint256 collToSendToSP, uint256 debtToRedistribute, uint256 collToRedistribute) + view + returns ( + uint256 debtToOffset, + uint256 collToSendToSP, + uint256 debtToRedistribute, + uint256 collToRedistribute, + uint256 collSurplus + ) { + uint256 collSPPortion; + /* + * Offset as much debt & collateral as possible against the Stability Pool, and redistribute the remainder + * between all active troves. + * + * If the trove's debt is larger than the deposited Bold in the Stability Pool: + * + * - Offset an amount of the trove's debt equal to the Bold in the Stability Pool + * - Send a fraction of the trove's collateral to the Stability Pool, equal to the fraction of its offset debt + * + */ if (_boldInStabPool > 0) { - /* - * Offset as much debt & collateral as possible against the Stability Pool, and redistribute the remainder - * between all active troves. - * - * If the trove's debt is larger than the deposited Bold in the Stability Pool: - * - * - Offset an amount of the trove's debt equal to the Bold in the Stability Pool - * - Send a fraction of the trove's collateral to the Stability Pool, equal to the fraction of its offset debt - * - */ debtToOffset = LiquityMath._min(_entireTroveDebt, _boldInStabPool); - collToSendToSP = _collToLiquidate * debtToOffset / _entireTroveDebt; - debtToRedistribute = _entireTroveDebt - debtToOffset; - collToRedistribute = _collToLiquidate - collToSendToSP; + collSPPortion = _collToLiquidate * debtToOffset / _entireTroveDebt; + (collToSendToSP, collSurplus) = + _getCollPenaltyAndSurplus(collSPPortion, debtToOffset, LIQUIDATION_PENALTY_SP, _price); + } + // TODO: this fails if debt in gwei is less than price (rounding coll to zero) + //assert(debtToOffset == 0 || collToSendToSP > 0); + + // Redistribution + debtToRedistribute = _entireTroveDebt - debtToOffset; + if (debtToRedistribute > 0) { + uint256 collRedistributionPortion = _collToLiquidate - collSPPortion; + if (collRedistributionPortion > 0) { + (collToRedistribute, collSurplus) = _getCollPenaltyAndSurplus( + collRedistributionPortion + collSurplus, // Coll surplus from offset can be eaten up by red. penalty + debtToRedistribute, LIQUIDATION_PENALTY_REDISTRIBUTION, _price + ); + } + } + assert(_collToLiquidate == collToSendToSP + collToRedistribute + collSurplus); + } + + function _getCollPenaltyAndSurplus( + uint256 _collToLiquidate, + uint256 _debtToLiquidate, + uint256 _penaltyRatio, + uint256 _price + ) internal pure returns (uint256 collPenalty, uint256 collSurplus) { + uint256 maxCollWithPenalty = _debtToLiquidate * (DECIMAL_PRECISION + _penaltyRatio) / _price; + if (_collToLiquidate > maxCollWithPenalty) { + collPenalty = maxCollWithPenalty; + collSurplus = _collToLiquidate - maxCollWithPenalty; } else { - debtToOffset = 0; - collToSendToSP = 0; - debtToRedistribute = _entireTroveDebt; - collToRedistribute = _collToLiquidate; + collPenalty = _collToLiquidate; + collSurplus = 0; } } @@ -514,11 +581,12 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { uint256 _recordedTroveDebt, uint256 _weightedRecordedTroveDebt, uint256 _price - ) internal pure returns (LiquidationValues memory singleLiquidation) { + ) internal view returns (LiquidationValues memory singleLiquidation) { singleLiquidation.entireTroveDebt = _entireTroveDebt; singleLiquidation.entireTroveColl = _entireTroveColl; singleLiquidation.recordedTroveDebt = _recordedTroveDebt; singleLiquidation.weightedRecordedTroveDebt = _weightedRecordedTroveDebt; + // TODO: We don’t bother updating this because we are removing RM uint256 cappedCollPortion = _entireTroveDebt * MCR / _price; singleLiquidation.collGasCompensation = _getCollGasCompensation(cappedCollPortion); @@ -575,7 +643,10 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { ); // Move liquidated ETH and Bold to the appropriate pools - stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP); + if (totals.totalDebtToOffset > 0 || totals.totalCollToSendToSP > 0) { + stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP); + } + // we check amount is not zero inside _redistributeDebtAndColl( activePoolCached, defaultPoolCached, totals.totalDebtToRedistribute, totals.totalCollToRedistribute ); @@ -646,7 +717,7 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { !_checkPotentialRecoveryMode(vars.entireSystemColl, vars.entireSystemDebt, _price); } else if (vars.backToNormalMode && vars.ICR < MCR) { singleLiquidation = - _liquidateNormalMode(_activePool, _defaultPool, vars.troveId, vars.remainingBoldInStabPool); + _liquidateNormalMode(_activePool, _defaultPool, vars.troveId, vars.remainingBoldInStabPool, _price); vars.remainingBoldInStabPool = vars.remainingBoldInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals @@ -675,7 +746,7 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { if (vars.ICR < MCR) { singleLiquidation = - _liquidateNormalMode(_activePool, _defaultPool, vars.troveId, vars.remainingBoldInStabPool); + _liquidateNormalMode(_activePool, _defaultPool, vars.troveId, vars.remainingBoldInStabPool, _price); vars.remainingBoldInStabPool = vars.remainingBoldInStabPool - singleLiquidation.debtToOffset; // Add liquidation values to their respective running totals @@ -772,8 +843,8 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { if (_getNetDebt(singleRedemption.newRecordedTroveDebt) < MIN_NET_DEBT) { Troves[_troveId].status = Status.unredeemable; sortedTroves.remove(_troveId); - // TODO: should we also remove from the Troves array? Seems unneccessary as it's only used for off-chain hints. - // We save borrowers gas by not removing + // TODO: should we also remove from the Troves array? Seems unneccessary as it's only used for off-chain hints. + // We save borrowers gas by not removing } Troves[_troveId].debt = singleRedemption.newRecordedTroveDebt; Troves[_troveId].coll = newColl; @@ -1233,7 +1304,7 @@ contract TroveManager is ERC721, LiquityBase, Ownable, ITroveManager { return TCR < CCR; } - + function checkTroveIsOpen(uint256 _troveId) public view returns (bool) { Status status = Troves[_troveId].status; return status == Status.active || status == Status.unredeemable; diff --git a/contracts/src/deployment.sol b/contracts/src/deployment.sol index df7a6515..354c2230 100644 --- a/contracts/src/deployment.sol +++ b/contracts/src/deployment.sol @@ -94,14 +94,14 @@ function _deployAndConnectCollateralContracts(IERC20 _collateralToken, IBoldToke // Deploy all contracts contracts.activePool = new ActivePool(address(_collateralToken)); - contracts.borrowerOperations = new BorrowerOperations(address(_collateralToken)); + contracts.troveManager = new TroveManager(110e16, 5e16, 10e16); + contracts.borrowerOperations = new BorrowerOperations(_collateralToken, contracts.troveManager); contracts.collSurplusPool = new CollSurplusPool(address(_collateralToken)); contracts.defaultPool = new DefaultPool(address(_collateralToken)); contracts.gasPool = new GasPool(); contracts.priceFeed = new PriceFeedTestnet(); contracts.sortedTroves = new SortedTroves(); contracts.stabilityPool = new StabilityPool(address(_collateralToken)); - contracts.troveManager = new TroveManager(); contracts.interestRouter = new MockInterestRouter(); _boldToken.setBranchAddresses( @@ -129,7 +129,6 @@ function _deployAndConnectCollateralContracts(IERC20 _collateralToken, IBoldToke // set contracts in BorrowerOperations contracts.borrowerOperations.setAddresses( - address(contracts.troveManager), address(contracts.activePool), address(contracts.defaultPool), address(contracts.stabilityPool), diff --git a/contracts/src/test/TestContracts/BaseTest.sol b/contracts/src/test/TestContracts/BaseTest.sol index 26ba8a76..55cf27b7 100644 --- a/contracts/src/test/TestContracts/BaseTest.sol +++ b/contracts/src/test/TestContracts/BaseTest.sol @@ -35,7 +35,7 @@ contract BaseTest is Test { uint256 public constant MAX_UINT256 = type(uint256).max; uint256 public constant SECONDS_IN_1_YEAR = 31536000; // 60*60*24*365 uint256 _100pct = 100e16; - uint256 MCR = 110e16; + uint256 MCR; uint256 CCR = 150e16; address public constant ZERO_ADDRESS = address(0); diff --git a/contracts/src/test/TestContracts/BorrowerOperationsTester.sol b/contracts/src/test/TestContracts/BorrowerOperationsTester.sol index b3e02581..a9b8b01e 100644 --- a/contracts/src/test/TestContracts/BorrowerOperationsTester.sol +++ b/contracts/src/test/TestContracts/BorrowerOperationsTester.sol @@ -7,7 +7,7 @@ import "../../BorrowerOperations.sol"; /* Tester contract inherits from BorrowerOperations, and provides external functions for testing the parent's internal functions. */ contract BorrowerOperationsTester is BorrowerOperations { - constructor(address _ETHAddress) BorrowerOperations(_ETHAddress) {} + constructor(IERC20 _ETH, ITroveManager _troveManager) BorrowerOperations(_ETH, _troveManager) {} function getNewICRFromTroveChange( uint256 _coll, diff --git a/contracts/src/test/TestContracts/CollateralRegistryTester.sol b/contracts/src/test/TestContracts/CollateralRegistryTester.sol index a69242e5..9568b7c6 100644 --- a/contracts/src/test/TestContracts/CollateralRegistryTester.sol +++ b/contracts/src/test/TestContracts/CollateralRegistryTester.sol @@ -8,7 +8,9 @@ import "../../CollateralRegistry.sol"; for testing the parent's internal functions. */ contract CollateralRegistryTester is CollateralRegistry { - constructor(IBoldToken _boldToken, IERC20[] memory _tokens, ITroveManager[] memory _troveManagers) CollateralRegistry(_boldToken, _tokens, _troveManagers) {} + constructor(IBoldToken _boldToken, IERC20[] memory _tokens, ITroveManager[] memory _troveManagers) + CollateralRegistry(_boldToken, _tokens, _troveManagers) + {} function unprotectedDecayBaseRateFromBorrowing() external returns (uint256) { baseRate = _calcDecayedBaseRate(); diff --git a/contracts/src/test/TestContracts/DevTestSetup.sol b/contracts/src/test/TestContracts/DevTestSetup.sol index 29b3522f..e73a2481 100644 --- a/contracts/src/test/TestContracts/DevTestSetup.sol +++ b/contracts/src/test/TestContracts/DevTestSetup.sol @@ -66,6 +66,8 @@ contract DevTestSetup is BaseTest { troveManager = contracts.troveManager; mockInterestRouter = contracts.interestRouter; + MCR = troveManager.MCR(); + // Give some ETH to test accounts, and approve it to BorrowerOperations uint256 initialETHAmount = 1000_000e18; for (uint256 i = 0; i < 6; i++) { diff --git a/contracts/src/test/TestContracts/TroveManagerTester.sol b/contracts/src/test/TestContracts/TroveManagerTester.sol index ec4388d0..a12ebc28 100644 --- a/contracts/src/test/TestContracts/TroveManagerTester.sol +++ b/contracts/src/test/TestContracts/TroveManagerTester.sol @@ -8,6 +8,10 @@ import "../../TroveManager.sol"; for testing the parent's internal functions. */ contract TroveManagerTester is TroveManager { + constructor(uint256 _mcr, uint256 _liquidationPenaltySP, uint256 _liquidationPenaltyRedistribution) + TroveManager(_mcr, _liquidationPenaltySP, _liquidationPenaltyRedistribution) + {} + function computeICR(uint256 _coll, uint256 _debt, uint256 _price) external pure returns (uint256) { return LiquityMath._computeCR(_coll, _debt, _price); } diff --git a/contracts/src/test/liquidations.t.sol b/contracts/src/test/liquidations.t.sol new file mode 100644 index 00000000..9d607dec --- /dev/null +++ b/contracts/src/test/liquidations.t.sol @@ -0,0 +1,284 @@ +pragma solidity ^0.8.18; + +import "./TestContracts/DevTestSetup.sol"; + +contract LiquidationsTest is DevTestSetup { + function testLiquidationOffsetWithSurplus() public { + uint256 liquidationAmount = 2000e18; + uint256 collAmount = 2e18; + + priceFeed.setPrice(2000e18); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + borrowerOperations.openTrove(B, 0, 1e18, 2 * collAmount, liquidationAmount, 0, 0, 0); + // B deposits to SP + stabilityPool.provideToSP(liquidationAmount); + + // Price drops + priceFeed.setPrice(1100e18 - 1); + uint256 price = priceFeed.fetchPrice(); + + uint256 initialSPBoldBalance = stabilityPool.getTotalBoldDeposits(); + uint256 initialSPETHBalance = stabilityPool.getETHBalance(); + uint256 AInitialETHBalance = WETH.balanceOf(A); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(price), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, price), MCR); + assertGt(troveManager.getTCR(price), CCR); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Check SP Bold has decreased + uint256 finalSPBoldBalance = stabilityPool.getTotalBoldDeposits(); + assertEq(initialSPBoldBalance - finalSPBoldBalance, liquidationAmount, "SP Bold balance mismatch"); + // Check SP ETH has increased + uint256 finalSPETHBalance = stabilityPool.getETHBalance(); + // liquidationAmount to ETH + 5% + assertApproxEqAbs( + finalSPETHBalance - initialSPETHBalance, + liquidationAmount * DECIMAL_PRECISION / price * 105 / 100, + 10, + "SP ETH balance mismatch" + ); + + // Check A retains ~4.5% of the collateral (after claiming from CollSurplus) + // collAmount - 0.5% - (liquidationAmount to ETH + 5%) + uint256 collSurplusAmount = collAmount * 995 / 1000 - liquidationAmount * DECIMAL_PRECISION / price * 105 / 100; + assertEq( + WETH.balanceOf(address(collSurplusPool)), + collSurplusAmount, + "CollSurplusPoll should have received collateral" + ); + vm.startPrank(A); + borrowerOperations.claimCollateral(ATroveId); + vm.stopPrank(); + assertEq(WETH.balanceOf(A) - AInitialETHBalance, collSurplusAmount, "A collateral balance mismatch"); + } + + function testLiquidationOffsetNoSurplus() public { + uint256 liquidationAmount = 10000e18; + uint256 collAmount = 10e18; + + priceFeed.setPrice(2000e18); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + borrowerOperations.openTrove(B, 0, 1e18, 3 * collAmount, liquidationAmount, 0, 0, 0); + // B deposits to SP + stabilityPool.provideToSP(liquidationAmount); + + // Price drops + priceFeed.setPrice(1030e18); + uint256 price = priceFeed.fetchPrice(); + + uint256 initialSPBoldBalance = stabilityPool.getTotalBoldDeposits(); + uint256 initialSPETHBalance = stabilityPool.getETHBalance(); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(price), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, price), MCR, "ICR too high"); + assertGe(troveManager.getTCR(price), CCR, "TCR too low"); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Check SP Bold has decreased + uint256 finalSPBoldBalance = stabilityPool.getTotalBoldDeposits(); + assertEq(initialSPBoldBalance - finalSPBoldBalance, liquidationAmount, "SP Bold balance mismatch"); + // Check SP ETH has increased by coll minus coll gas comp + uint256 finalSPETHBalance = stabilityPool.getETHBalance(); + // liquidationAmount to ETH + 5% + assertApproxEqAbs( + finalSPETHBalance - initialSPETHBalance, collAmount * 995 / 1000, 10, "SP ETH balance mismatch" + ); + + // Check there’s no surplus + assertEq(WETH.balanceOf(address(collSurplusPool)), 0, "CollSurplusPoll should be empty"); + + vm.startPrank(A); + vm.expectRevert("CollSurplusPool: No collateral available to claim"); + borrowerOperations.claimCollateral(); + vm.stopPrank(); + } + + function testLiquidationRedistributionNoSurplus() public { + uint256 liquidationAmount = 2000e18; + uint256 collAmount = 2e18; + + priceFeed.setPrice(2000e18); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + uint256 BTroveId = borrowerOperations.openTrove(B, 0, 1e18, 2 * collAmount, liquidationAmount, 0, 0, 0); + + // Price drops + priceFeed.setPrice(1100e18 - 1); + uint256 price = priceFeed.fetchPrice(); + + uint256 BInitialDebt = troveManager.getTroveEntireDebt(BTroveId); + uint256 BInitialColl = troveManager.getTroveEntireColl(BTroveId); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(price), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, price), MCR); + assertGt(troveManager.getTCR(price), CCR); + + // Check empty SP + assertEq(stabilityPool.getTotalBoldDeposits(), 0, "SP should be empty"); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Check SP stays the same + assertEq(stabilityPool.getTotalBoldDeposits(), 0, "SP should be empty"); + assertEq(stabilityPool.getETHBalance(), 0, "SP should not have ETH rewards"); + + // Check B has received debt + assertEq(troveManager.getTroveEntireDebt(BTroveId) - BInitialDebt, liquidationAmount, "B debt mismatch"); + // Check B has received all coll minus coll gas comp + assertApproxEqAbs( + troveManager.getTroveEntireColl(BTroveId) - BInitialColl, + collAmount * 995 / 1000, // Collateral - coll gas comp + 10, + "B trove coll mismatch" + ); + + assertEq(WETH.balanceOf(address(collSurplusPool)), 0, "CollSurplusPoll should be empty"); + } + + struct InitialValues { + uint256 spBoldBalance; + uint256 spETHBalance; + uint256 AETHBalance; + uint256 BDebt; + uint256 BColl; + } + + // Offset and Redistribution + function testLiquidationMix() public { + uint256 liquidationAmount = 2000e18; + uint256 collAmount = 2e18; + + priceFeed.setPrice(2000e18); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + uint256 BTroveId = borrowerOperations.openTrove(B, 0, 1e18, 2 * collAmount, liquidationAmount, 0, 0, 0); + // B deposits to SP + stabilityPool.provideToSP(liquidationAmount / 2); + + // Price drops + priceFeed.setPrice(1100e18 - 1); + uint256 price = priceFeed.fetchPrice(); + + InitialValues memory initialValues; + initialValues.spBoldBalance = stabilityPool.getTotalBoldDeposits(); + initialValues.spETHBalance = stabilityPool.getETHBalance(); + initialValues.AETHBalance = WETH.balanceOf(A); + initialValues.BDebt = troveManager.getTroveEntireDebt(BTroveId); + initialValues.BColl = troveManager.getTroveEntireColl(BTroveId); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(price), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, price), MCR); + assertGt(troveManager.getTCR(price), CCR); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Check SP Bold has decreased + uint256 finalSPBoldBalance = stabilityPool.getTotalBoldDeposits(); + assertEq(initialValues.spBoldBalance - finalSPBoldBalance, liquidationAmount / 2, "SP Bold balance mismatch"); + // Check SP ETH has increased + uint256 finalSPETHBalance = stabilityPool.getETHBalance(); + // liquidationAmount to ETH + 5% + assertApproxEqAbs( + finalSPETHBalance - initialValues.spETHBalance, + liquidationAmount / 2 * DECIMAL_PRECISION / price * 105 / 100, + 10, + "SP ETH balance mismatch" + ); + + // Check B has received debt + assertEq( + troveManager.getTroveEntireDebt(BTroveId) - initialValues.BDebt, liquidationAmount / 2, "B debt mismatch" + ); + // Check B has received coll + assertApproxEqAbs( + troveManager.getTroveEntireColl(BTroveId) - initialValues.BColl, + //collAmount * 995 / 1000 - liquidationAmount / 2 * DECIMAL_PRECISION / price * 105 / 100, + liquidationAmount / 2 * DECIMAL_PRECISION / price * 110 / 100, + 10, + "B trove coll mismatch" + ); + + // Check A retains ~4.5% of the collateral (after claiming from CollSurplus) + // collAmount - 0.5% - (liquidationAmount to ETH + 5%) + uint256 collSurplusAmount = collAmount * 995 / 1000 + - liquidationAmount / 2 * DECIMAL_PRECISION / price * 105 / 100 + - liquidationAmount / 2 * DECIMAL_PRECISION / price * 110 / 100; + assertApproxEqAbs( + WETH.balanceOf(address(collSurplusPool)), + collSurplusAmount, + 10, + "CollSurplusPoll should have received collateral" + ); + vm.startPrank(A); + borrowerOperations.claimCollateral(); + vm.stopPrank(); + assertApproxEqAbs( + WETH.balanceOf(A) - initialValues.AETHBalance, collSurplusAmount, 10, "A collateral balance mismatch" + ); + } +} diff --git a/contracts/src/test/liquidationsLST.t.sol b/contracts/src/test/liquidationsLST.t.sol new file mode 100644 index 00000000..5e95d0a9 --- /dev/null +++ b/contracts/src/test/liquidationsLST.t.sol @@ -0,0 +1,250 @@ +pragma solidity ^0.8.18; + +import "./TestContracts/DevTestSetup.sol"; + +contract LiquidationsLSTTest is DevTestSetup { + function setUp() public override { + // Start tests at a non-zero timestamp + vm.warp(block.timestamp + 600); + + accounts = new Accounts(); + createAccounts(); + + (A, B, C, D, E, F, G) = ( + accountsList[0], + accountsList[1], + accountsList[2], + accountsList[3], + accountsList[4], + accountsList[5], + accountsList[6] + ); + + LiquityContracts memory contracts; + (contracts, collateralRegistry, boldToken) = _deployAndConnectContracts(TroveManagerParams(120e16, 5e16, 10e16)); + WETH = contracts.WETH; + activePool = contracts.activePool; + borrowerOperations = contracts.borrowerOperations; + collSurplusPool = contracts.collSurplusPool; + defaultPool = contracts.defaultPool; + gasPool = contracts.gasPool; + priceFeed = contracts.priceFeed; + sortedTroves = contracts.sortedTroves; + stabilityPool = contracts.stabilityPool; + troveManager = contracts.troveManager; + mockInterestRouter = contracts.interestRouter; + + MCR = troveManager.MCR(); + + // Give some ETH to test accounts, and approve it to BorrowerOperations + uint256 initialETHAmount = 10_000e18; + for (uint256 i = 0; i < 6; i++) { + // A to F + giveAndApproveETH(accountsList[i], initialETHAmount); + } + } + + function testLiquidationRedistributionWithSurplus() public { + uint256 liquidationAmount = 2000e18; + uint256 collAmount = 2e18; + + priceFeed.setPrice(2000e18); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + uint256 BTroveId = borrowerOperations.openTrove(B, 0, 1e18, 2 * collAmount, liquidationAmount, 0, 0, 0); + + // Price drops + priceFeed.setPrice(1200e18 - 1); + uint256 price = priceFeed.fetchPrice(); + + uint256 BInitialDebt = troveManager.getTroveEntireDebt(BTroveId); + uint256 BInitialColl = troveManager.getTroveEntireColl(BTroveId); + uint256 AInitialETHBalance = WETH.balanceOf(A); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(price), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, price), MCR); + assertGt(troveManager.getTCR(price), CCR); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Check SP stays the same + assertEq(stabilityPool.getTotalBoldDeposits(), 0, "SP should be empty"); + assertEq(stabilityPool.getETHBalance(), 0, "SP should not have ETH rewards"); + + // Check B has received debt + assertEq(troveManager.getTroveEntireDebt(BTroveId) - BInitialDebt, liquidationAmount, "B debt mismatch"); + // Check B has received all coll minus coll gas comp + assertApproxEqAbs( + troveManager.getTroveEntireColl(BTroveId) - BInitialColl, + LiquityMath._min( + collAmount * 995 / 1000, // Collateral - coll gas comp + liquidationAmount * DECIMAL_PRECISION / price * 110 / 100 // debt with penalty + ), + 10, + "B trove coll mismatch" + ); + + // Check A retains ~9.5% of the collateral (after claiming from CollSurplus) + // collAmount - 0.5% - (liquidationAmount to ETH + 10%) + uint256 collSurplusAmount = collAmount * 995 / 1000 - liquidationAmount * DECIMAL_PRECISION / price * 110 / 100; + assertApproxEqAbs( + WETH.balanceOf(address(collSurplusPool)), + collSurplusAmount, + 10, + "CollSurplusPoll should have received collateral" + ); + vm.startPrank(A); + borrowerOperations.claimCollateral(); + vm.stopPrank(); + assertApproxEqAbs( + WETH.balanceOf(A) - AInitialETHBalance, collSurplusAmount, 10, "A collateral balance mismatch" + ); + } + + struct InitialValues { + uint256 spBoldBalance; + uint256 spETHBalance; + uint256 AETHBalance; + uint256 BDebt; + uint256 BColl; + } + + struct FinalValues { + uint256 spBoldBalance; + uint256 spETHBalance; + uint256 collToLiquidate; + uint256 collSPPortion; + uint256 collPenaltySP; + uint256 collToSendToSP; + uint256 collRedistributionPortion; + uint256 collPenaltyRedistribution; + } + + function testLiquidationFuzz(uint256 _finalPrice, uint256 _spAmount) public { + uint256 liquidationAmount = 2000e18; + uint256 collAmount = 2e18; + uint256 initialPrice = 2000e18; + // A initial CR: 200% + + _finalPrice = bound(_finalPrice, 1000e18, 1200e18 - 1); // A final CR in [100%, 120%[ + _spAmount = bound(_spAmount, 0, liquidationAmount); + + priceFeed.setPrice(initialPrice); + vm.startPrank(A); + uint256 ATroveId = borrowerOperations.openTrove( + A, 0, 1e18, collAmount, liquidationAmount - troveManager.BOLD_GAS_COMPENSATION(), 0, 0, 0 + ); + vm.stopPrank(); + + vm.startPrank(B); + uint256 BTroveId = borrowerOperations.openTrove(B, 0, 1e18, 3 * collAmount, liquidationAmount, 0, 0, 0); + // B deposits to SP + if (_spAmount > 0) { + stabilityPool.provideToSP(_spAmount); + } + + // Price drops + priceFeed.setPrice(_finalPrice); + console2.log(_finalPrice, "_finalPrice"); + + InitialValues memory initialValues; + initialValues.spBoldBalance = stabilityPool.getTotalBoldDeposits(); + initialValues.spETHBalance = stabilityPool.getETHBalance(); + initialValues.AETHBalance = WETH.balanceOf(A); + initialValues.BDebt = troveManager.getTroveEntireDebt(BTroveId); + initialValues.BColl = troveManager.getTroveEntireColl(BTroveId); + + // Check not RM + assertEq(troveManager.checkRecoveryMode(_finalPrice), false, "System should not be in Recovery Mode"); + + // Check CR_A < MCR and TCR > CCR + assertLt(troveManager.getCurrentICR(ATroveId, _finalPrice), MCR); + assertGt(troveManager.getTCR(_finalPrice), CCR); + + uint256 trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 2); + + troveManager.liquidate(ATroveId); + + // Check Troves count reduced by 1 + trovesCount = troveManager.getTroveIdsCount(); + assertEq(trovesCount, 1); + + // Offset part + FinalValues memory finalValues; + finalValues.collToLiquidate = collAmount * 995 / 1000; + // Check SP Bold has decreased + finalValues.spBoldBalance = stabilityPool.getTotalBoldDeposits(); + assertEq(initialValues.spBoldBalance - finalValues.spBoldBalance, _spAmount, "SP Bold balance mismatch"); + // Check SP ETH has increased + finalValues.spETHBalance = stabilityPool.getETHBalance(); + finalValues.collSPPortion = finalValues.collToLiquidate * _spAmount / liquidationAmount; + finalValues.collPenaltySP = _spAmount * DECIMAL_PRECISION / _finalPrice * 105 / 100; + finalValues.collToSendToSP = LiquityMath._min(finalValues.collPenaltySP, finalValues.collSPPortion); + // liquidationAmount to ETH + 5% + assertApproxEqAbs( + finalValues.spETHBalance - initialValues.spETHBalance, + finalValues.collToSendToSP, + 10, + "SP ETH balance mismatch" + ); + + // Redistribution part + finalValues.collRedistributionPortion = finalValues.collToLiquidate - finalValues.collSPPortion; + finalValues.collPenaltyRedistribution = + (liquidationAmount - _spAmount) * DECIMAL_PRECISION / _finalPrice * 110 / 100; + // Check B has received debt + assertApproxEqAbs( + troveManager.getTroveEntireDebt(BTroveId) - initialValues.BDebt, + liquidationAmount - _spAmount, + 10, + "B debt mismatch" + ); + // Check B has received coll + assertApproxEqAbs( + troveManager.getTroveEntireColl(BTroveId) - initialValues.BColl, + LiquityMath._min( + finalValues.collPenaltyRedistribution, + finalValues.collRedistributionPortion + finalValues.collSPPortion - finalValues.collToSendToSP + ), + 10, + "B trove coll mismatch" + ); + + // Surplus + // Check A retains part of the collateral (after claiming from CollSurplus) + // collAmount - 0.5% - (liquidationAmount to ETH + penalty) + uint256 collPenalty = finalValues.collPenaltySP + finalValues.collPenaltyRedistribution; + console2.log(finalValues.collPenaltySP, "finalValues.collPenaltySP"); + console2.log(finalValues.collPenaltyRedistribution, "finalValues.collPenaltyRedistribution"); + console2.log(collPenalty, "collPenalty"); + uint256 collSurplusAmount; + if (collPenalty < finalValues.collToLiquidate) { + collSurplusAmount = finalValues.collToLiquidate - collPenalty; + } + assertApproxEqAbs(WETH.balanceOf(address(collSurplusPool)), collSurplusAmount, 1e9, "CollSurplusPoll mismatch"); + if (collSurplusAmount > 0) { + vm.startPrank(A); + borrowerOperations.claimCollateral(); + vm.stopPrank(); + assertApproxEqAbs( + WETH.balanceOf(A) - initialValues.AETHBalance, collSurplusAmount, 1e9, "A collateral balance mismatch" + ); + } + } +} diff --git a/contracts/src/test/redemptions.t.sol b/contracts/src/test/redemptions.t.sol index bc8e5732..6acdb972 100644 --- a/contracts/src/test/redemptions.t.sol +++ b/contracts/src/test/redemptions.t.sol @@ -189,13 +189,13 @@ contract Redemptions is DevTestSetup { redeem(E, redeemAmount); // Check B's debt unchanged from redeemAmount < debt_B; - assertEq(debt_B, troveManager.getTroveEntireDebt(troveIDs.B)); + assertEq(debt_B, troveManager.getTroveEntireDebt(troveIDs.B)); redeemAmount = debt_B + 1; redeem(E, redeemAmount); // Check B's debt unchanged from redeemAmount > debt_B; - assertEq(debt_B, troveManager.getTroveEntireDebt(troveIDs.B)); + assertEq(debt_B, troveManager.getTroveEntireDebt(troveIDs.B)); } function testZombieTrovesCanReceiveRedistGains() public { @@ -268,13 +268,13 @@ contract Redemptions is DevTestSetup { _redeemAndCreateZombieTrovesAAndB(troveIDs); - assertLt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); + assertLt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); assertLt(troveManager.getTroveEntireDebt(troveIDs.B), troveManager.MIN_NET_DEBT()); // 100 years passes vm.warp(block.timestamp + 36500 days); - assertGt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); + assertGt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); assertGt(troveManager.getTroveEntireDebt(troveIDs.B), troveManager.MIN_NET_DEBT()); } @@ -301,7 +301,7 @@ contract Redemptions is DevTestSetup { // assertFalse(troveManager.checkRecoveryMode(price)); assertLt(troveManager.getCurrentICR(troveID_E, price), troveManager.MCR()); - assertLt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); + assertLt(troveManager.getTroveEntireDebt(troveIDs.A), troveManager.MIN_NET_DEBT()); assertLt(troveManager.getTroveEntireDebt(troveIDs.B), troveManager.MIN_NET_DEBT()); // A liquidates E @@ -329,8 +329,8 @@ contract Redemptions is DevTestSetup { assertEq(troveManager.getTroveStatus(troveIDs.A), 5); // Status 5 - 'unredeemable' assertEq(troveManager.getTroveStatus(troveIDs.B), 5); // Status 5 - 'unredeemable' - closeTrove(A, troveIDs.A); - closeTrove(B, troveIDs.B); + closeTrove(A, troveIDs.A); + closeTrove(B, troveIDs.B); assertEq(troveManager.getTroveStatus(troveIDs.A), 2); // Status 2 - 'closed by owner' assertEq(troveManager.getTroveStatus(troveIDs.B), 2); // Status 2 - 'closed by owner' @@ -702,7 +702,7 @@ contract Redemptions is DevTestSetup { vm.expectRevert("BorrowerOps: Trove does not have active status"); borrowerOperations.adjustTroveInterestRate(troveIDs.B, newInterestRate, troveIDs.B, troveIDs.B); vm.stopPrank(); - } + } function testZombieTroveAccruedInterestCanBePermissionlesslyApplied() public { ( , , TroveIDs memory troveIDs) = _setupForRedemptionAscendingInterest(); @@ -723,7 +723,7 @@ contract Redemptions is DevTestSetup { assertEq(troveManager.calcTroveAccruedInterest(troveIDs.A), 0); assertEq(troveManager.calcTroveAccruedInterest(troveIDs.B), 0); - } + } function testZombieTroveCanBeLiquidated() public { ( , , TroveIDs memory troveIDs) = _setupForRedemptionAscendingInterest(); @@ -745,13 +745,13 @@ contract Redemptions is DevTestSetup { // assertFalse(troveManager.checkRecoveryMode(price)); assertLt(troveManager.getCurrentICR(troveIDs.B, price), troveManager.MCR()); - assertEq(troveManager.getTroveStatus(troveIDs.B), 5); + assertEq(troveManager.getTroveStatus(troveIDs.B), 5); // E liquidates B liquidate(E, troveIDs.B); assertEq(troveManager.getTroveStatus(troveIDs.B), 3); // Status 3 - closed by liquidation - } + } // TODO: tests borrower for combined adjustments - debt changes and coll add/withdrawals. - // Borrower should only be able to close OR leave Trove at >= min net debt. + // Borrower should only be able to close OR leave Trove at >= min net debt. }