diff --git a/README.md b/README.md index e9e83eb8..1155848a 100644 --- a/README.md +++ b/README.md @@ -506,9 +506,27 @@ The premature adjustment fee works as so: - When a Trove is opened, its `lastInterestRateAdjTime` property is set equal to the current time - When a borrower adjusts their interest rate via `adjustTroveInterestRate` the system checks that the cooldown period has passed since their last interest rate adjustment - - If the adjustment is sooner it incurs an upfront fee (equal to 7 days of average interest of the respective branch) which is added to their debt. +#### Batches and upfront fee + +##### Joining a batch +When a trove joins a batch, it pays upfront fee if the last trove adjustment was done more than the cool period ago. It does’t matter if trove and batch have the same interest rate, or when was the last adjustment by the batch. + +The last interest rate timestamp will be updated to the time of joining. + +Batch interest rate changes only take into account global batch timestamps, so when the new batch manager changes the interest rate less than the cooldown period after the borrower moved to the new batch, but more than the cooldown period after its last adjustment, the newly joined borrower wouldn't pay the upfront fee despite the fact that his last interest rate change happened less than the cooldown period ago. + +That’s why troves pay upfront fee when joining even if the interest is the same. Otherwise a trove may game it by having a batch created in advance (with no recent changens), joining it and the changing the rate of the batch. + +##### Leaving a batch +When a trove leaves a batch, the user's timestamp is again reset to the current time. +No upfront fee is charged, unless the interest rate is changed in the same transaction and the batch changed the interest rate less than the cooldown period ago. + +##### Switching batches +As the function to switch batches is just a wrapper that calls the functions for leaving and joining a batch, this means that switching batches always incurs in upfront fee now (unless user doesn’t use the wrapper and waits for 1 week between leaving and joining). + + ## BOLD Redemptions Any BOLD holder (whether or not they have an active Trove) may redeem their BOLD directly with the system. Their BOLD is exchanged for a mixture of collaterals at face value: redeeming 1 BOLD token returns $1 worth of collaterals (minus a dynamic redemption fee), priced at their current market values according to their respective oracles. Redemptions have two purposes: diff --git a/contracts/src/BorrowerOperations.sol b/contracts/src/BorrowerOperations.sol index 4553aec8..365e0a95 100644 --- a/contracts/src/BorrowerOperations.sol +++ b/contracts/src/BorrowerOperations.sol @@ -989,7 +989,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio newBatchTroveChange.newWeightedRecordedDebt = (vars.newBatch.entireDebtWithoutRedistribution + vars.trove.entireDebt) * vars.newBatch.annualInterestRate; - // TODO: We may check the old rate to see if it’s different than the new one, but then we should check the + // We may check the old rate to see if it’s different than the new one, but then we should check the // last interest adjustment times to avoid gaming. So we decided to keep it simple and account it always // as a change. It’s probably not so common to join a batch with the exact same interest rate. // Apply upfront fee on premature adjustments @@ -1066,7 +1066,7 @@ contract BorrowerOperations is LiquityBase, AddRemoveManagers, IBorrowerOperatio // Apply upfront fee on premature adjustments if ( vars.batch.annualInterestRate != _newAnnualInterestRate - && block.timestamp < vars.batch.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN + && block.timestamp < vars.trove.lastInterestRateAdjTime + INTEREST_RATE_ADJ_COOLDOWN ) { vars.trove.entireDebt = _applyUpfrontFee(vars.trove.entireColl, vars.trove.entireDebt, batchChange, _maxUpfrontFee); diff --git a/contracts/src/HintHelpers.sol b/contracts/src/HintHelpers.sol index c5bf9882..0cb4f120 100644 --- a/contracts/src/HintHelpers.sol +++ b/contracts/src/HintHelpers.sol @@ -216,15 +216,40 @@ contract HintHelpers is IHintHelpers { return 0; } + return _predictJoinBatchInterestRateUpfrontFee(activePool, trove, batch); + } + + function forcePredictJoinBatchInterestRateUpfrontFee(uint256 _collIndex, uint256 _troveId, address _batchAddress) + external + view + returns (uint256) + { + ITroveManager troveManager = collateralRegistry.getTroveManager(_collIndex); + IActivePool activePool = troveManager.activePool(); + LatestTroveData memory trove = troveManager.getLatestTroveData(_troveId); + LatestBatchData memory batch = troveManager.getLatestBatchData(_batchAddress); + + return _predictJoinBatchInterestRateUpfrontFee(activePool, trove, batch); + } + + function _predictJoinBatchInterestRateUpfrontFee( + IActivePool _activePool, + LatestTroveData memory _trove, + LatestBatchData memory _batch + ) + internal + view + returns (uint256) + { TroveChange memory newBatchTroveChange; - newBatchTroveChange.appliedRedistBoldDebtGain = trove.redistBoldDebtGain; - newBatchTroveChange.batchAccruedManagementFee = batch.accruedManagementFee; - newBatchTroveChange.oldWeightedRecordedDebt = batch.weightedRecordedDebt + trove.weightedRecordedDebt; + newBatchTroveChange.appliedRedistBoldDebtGain = _trove.redistBoldDebtGain; + newBatchTroveChange.batchAccruedManagementFee = _batch.accruedManagementFee; + newBatchTroveChange.oldWeightedRecordedDebt = _batch.weightedRecordedDebt + _trove.weightedRecordedDebt; newBatchTroveChange.newWeightedRecordedDebt = - (batch.entireDebtWithoutRedistribution + trove.entireDebt) * batch.annualInterestRate; + (_batch.entireDebtWithoutRedistribution + _trove.entireDebt) * _batch.annualInterestRate; - uint256 avgInterestRate = activePool.getNewApproxAvgInterestRateFromTroveChange(newBatchTroveChange); - return _calcUpfrontFee(trove.entireDebt, avgInterestRate); + uint256 avgInterestRate = _activePool.getNewApproxAvgInterestRateFromTroveChange(newBatchTroveChange); + return _calcUpfrontFee(_trove.entireDebt, avgInterestRate); } function predictRemoveFromBatchUpfrontFee(uint256 _collIndex, uint256 _troveId, uint256 _newInterestRate) diff --git a/contracts/src/TroveManager.sol b/contracts/src/TroveManager.sol index f13e4294..8c89a02b 100644 --- a/contracts/src/TroveManager.sol +++ b/contracts/src/TroveManager.sol @@ -1223,6 +1223,7 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { Troves[_troveId].status = Status.active; Troves[_troveId].arrayIndex = uint64(TroveIds.length); Troves[_troveId].interestBatchManager = _batchAddress; + Troves[_troveId].lastInterestRateAdjTime = uint64(block.timestamp); _updateTroveRewardSnapshots(_troveId); diff --git a/contracts/src/test/TestContracts/BaseTest.sol b/contracts/src/test/TestContracts/BaseTest.sol index e7d845de..0728d1dd 100644 --- a/contracts/src/test/TestContracts/BaseTest.sol +++ b/contracts/src/test/TestContracts/BaseTest.sol @@ -127,6 +127,14 @@ contract BaseTest is TestAccounts, Logging { return hintHelpers.predictJoinBatchInterestRateUpfrontFee(0, _troveId, _batchAddress); } + function forcePredictJoinBatchInterestRateUpfrontFee(uint256 _troveId, address _batchAddress) + internal + view + returns (uint256) + { + return hintHelpers.forcePredictJoinBatchInterestRateUpfrontFee(0, _troveId, _batchAddress); + } + // Quick and dirty binary search instead of Newton's, because it's easier function findAmountToBorrowWithOpenTrove(uint256 targetDebt, uint256 interestRate) internal diff --git a/contracts/src/test/interestBatchManagement.t.sol b/contracts/src/test/interestBatchManagement.t.sol index 715ddbb9..aeb959f6 100644 --- a/contracts/src/test/interestBatchManagement.t.sol +++ b/contracts/src/test/interestBatchManagement.t.sol @@ -774,6 +774,274 @@ contract InterestBatchManagementTest is DevTestSetup { assertEq(tmBatchManagerAddress, B, "Wrong batch manager in TM"); } + function testJoinBatchBatchManagerChargesUpfrontFeeIfTroveShortChangeBatchNotSameInterestRate() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinBatchBatchManagerDoesNotChargeUpfrontFeeIfBatchShortChangeTroveNotSameInterestRate() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 6e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // B changes interest rate + setBatchInterestRate(B, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertEq(upfrontFee, 0, "Upfront fee should be 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore, + 1e14, + "A debt should stay the same" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinBatchBatchManagerChargesUpfrontFeeIfTroveAndBatchShortChangeSameInterestRate() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 4e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + // B changes interest rate + setBatchInterestRate(B, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinBatchBatchManagerDoesNotChargeUpfrontFeeIfNotTroveNorBatchShortChangeSameInterestRate() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 6e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertEq(upfrontFee, 0, "Upfront fee should be 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore, + 1e14, + "A debt should stay the same" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testCannotGameUpfrontFeeByJoiningABatchAndChangingInterest() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // Interest rate change and cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + MIN_INTEREST_RATE_CHANGE_PERIOD + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + // B changes interest rate + setBatchInterestRate(B, 4e16); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinABatchWithSameInterestAndLeaveToSameInterestChargesUpfrontFeeOnlyOnce() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + // A leaves B + removeFromBatch(A, troveId, 5e16); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinABatchWithDifferentInterestAndLeaveToSameInterestChargesUpfrontFeeOnlyOnce() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 6e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + // A leaves B + removeFromBatch(A, troveId, 5e16); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinABatchWithSameInterestAndLeaveToDifferentInterestChargesUpfrontFeeTwice() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee1 = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee1, 0, "Upfront 1 fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + uint256 upfrontFee2 = predictAdjustInterestRateUpfrontFee(troveId, 4e16); + assertGt(upfrontFee2, 0, "Upfront 2 fee should be > 0"); + + // A leaves B + removeFromBatch(A, troveId, 4e16); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee1 + upfrontFee2, + 1e14, + "A debt should have increased by upfront fee twice" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testJoinABatchWithDifferentInterestAndLeaveToDifferentInterestChargesUpfrontFeeTwice() public { + // B registers as batch manager + registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 6e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + + // A opens trove + uint256 troveId = openTroveNoHints100pct(A, 100e18, 5000e18, 5e16); + + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee1 = predictJoinBatchInterestRateUpfrontFee(troveId, B); + assertGt(upfrontFee1, 0, "Upfront 1 fee should be > 0"); + + // A joins B + setInterestBatchManager(A, troveId, B); + + uint256 upfrontFee2 = predictAdjustInterestRateUpfrontFee(troveId, 4e16); + assertGt(upfrontFee2, 0, "Upfront 2 fee should be > 0"); + + // A leaves B + removeFromBatch(A, troveId, 4e16); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee1 + upfrontFee2, + 1e14, + "A debt should have increased by upfront fee twice" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + function testSwitchBatchBatchManagerChargesUpfrontFeeIfJoinedOldLessThanCooldownAgo() public { // C registers as batch manager registerBatchManager(C, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); @@ -783,7 +1051,32 @@ contract InterestBatchManagementTest is DevTestSetup { // Cool down period not gone by yet vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN - 60); uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); - uint256 upfrontFee = predictAdjustInterestRateUpfrontFee(troveId, 5e16); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, C); + assertGt(upfrontFee, 0, "Upfront fee should be > 0"); + + // Switch from B to C + switchBatchManager(A, troveId, C); + + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), + ADebtBefore + upfrontFee, + 1e14, + "A debt should have increased by upfront fee" + ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); + } + + function testSwitchBatchBatchManagerChargesUpfrontFeeIfJoinedOldMoreThanCooldownAgo() public { + // C registers as batch manager + registerBatchManager(C, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + // A opens trove and joins batch manager B (which has the same interest) + uint256 troveId = openTroveAndJoinBatchManager(A, 100 ether, 2000e18, B, 5e16); + + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); + uint256 upfrontFee = forcePredictJoinBatchInterestRateUpfrontFee(troveId, C); assertGt(upfrontFee, 0, "Upfront fee should be > 0"); // Switch from B to C @@ -795,6 +1088,8 @@ contract InterestBatchManagementTest is DevTestSetup { 1e14, "A debt should have increased by upfront fee" ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); } function testSwitchBatchBatchManagerChargesUpfrontFeeIfOldBatchChangedFeeLessThanCooldownAgo() public { @@ -811,7 +1106,7 @@ contract InterestBatchManagementTest is DevTestSetup { batch = troveManager.getLatestBatchData(B); uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); - uint256 upfrontFee = predictAdjustInterestRateUpfrontFee(troveId, 5e16); + uint256 upfrontFee = predictJoinBatchInterestRateUpfrontFee(troveId, C); assertGt(upfrontFee, 0, "Upfront fee should be > 0"); // Switch from B to C switchBatchManager(A, troveId, C); @@ -821,17 +1116,20 @@ contract InterestBatchManagementTest is DevTestSetup { 1, "A debt should have increased by upfront fee" ); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); } function testSwitchBatchBatchManagerDoesNotChargeTroveUpfrontFeeIfBatchChangesRateWithoutUpfrontFee() public { + // B registers as batch manager registerBatchManager(B, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); - // Cool down period not gone by yet - vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN - 60); - // C registers as batch manager registerBatchManager(C, uint128(MIN_ANNUAL_INTEREST_RATE), 1e18, 5e16, 0, MIN_INTEREST_RATE_CHANGE_PERIOD); + // Cool down period gone by + vm.warp(block.timestamp + INTEREST_RATE_ADJ_COOLDOWN + 1); + // A opens trove and joins batch manager B (which has the same interest) uint256 troveId = openTroveAndJoinBatchManager(A, 100 ether, 2000e18, B, 5e16); @@ -839,10 +1137,12 @@ contract InterestBatchManagementTest is DevTestSetup { switchBatchManager(A, troveId, C); uint256 ADebtBefore = troveManager.getTroveEntireDebt(troveId); - // B changes interest rate, but it doesn’t trigger upfront fee - setBatchInterestRate(B, 10e16); + // C changes interest rate, but it doesn’t trigger upfront fee + setBatchInterestRate(C, 10e16); assertEq(troveManager.getTroveEntireDebt(troveId), ADebtBefore, "A debt should be the same"); + LatestTroveData memory troveData = troveManager.getLatestTroveData(troveId); + assertEq(troveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); } function testAnUnredeemableTroveGoesBackToTheBatch() public { @@ -900,6 +1200,8 @@ contract InterestBatchManagementTest is DevTestSetup { uint256 expectedUpfrontFeeA = initialDebt * avgInterestRate * UPFRONT_INTEREST_PERIOD / ONE_YEAR / DECIMAL_PRECISION; assertEq(ATroveEntireDebt - initialDebt, expectedUpfrontFeeA, "Wrong upfront fee for A"); + LatestTroveData memory ATroveData = troveManager.getLatestTroveData(ATroveId); + assertEq(ATroveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for A"); vm.warp(block.timestamp + 10 days); @@ -914,5 +1216,7 @@ contract InterestBatchManagementTest is DevTestSetup { uint256 expectedUpfrontFeeC = initialDebt * avgInterestRate * UPFRONT_INTEREST_PERIOD / ONE_YEAR / DECIMAL_PRECISION; assertApproxEqAbs(CTroveEntireDebt - initialDebt, expectedUpfrontFeeC, 1, "Wrong upfront fee for C"); + LatestTroveData memory CTroveData = troveManager.getLatestTroveData(CTroveId); + assertEq(CTroveData.lastInterestRateAdjTime, block.timestamp, "Wrong interest rate adj time for C"); } }