From 57e2cf87ab3ce5535cfe4296eeec7e0b6e1643ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Fri, 6 Sep 2024 17:21:15 +0100 Subject: [PATCH 01/10] wip: test: Add simple test for SP yield rounding error --- contracts/src/test/SPYield.t.sol | 250 +++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 contracts/src/test/SPYield.t.sol diff --git a/contracts/src/test/SPYield.t.sol b/contracts/src/test/SPYield.t.sol new file mode 100644 index 00000000..f34cd682 --- /dev/null +++ b/contracts/src/test/SPYield.t.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "./TestContracts/DevTestSetup.sol"; +import {SPInvariantsTestHandler} from "./TestContracts/SPInvariantsTestHandler.t.sol"; +import {Logging} from "./Utils/Logging.sol"; + +contract SPYieldTest is DevTestSetup { + using StringFormatting for uint256; + + struct Actor { + string label; + address account; + } + + SPInvariantsTestHandler handler; + + address constant adam = 0x1111111111111111111111111111111111111111; + address constant barb = 0x2222222222222222222222222222222222222222; + address constant carl = 0x3333333333333333333333333333333333333333; + address constant dana = 0x4444444444444444444444444444444444444444; + address constant eric = 0x5555555555555555555555555555555555555555; + address constant fran = 0x6666666666666666666666666666666666666666; + address constant gabe = 0x7777777777777777777777777777777777777777; + address constant hope = 0x8888888888888888888888888888888888888888; + + Actor[] actors; + + function setUp() public override { + super.setUp(); + + TestDeployer deployer = new TestDeployer(); + (TestDeployer.LiquityContractsDev memory contracts,, IBoldToken boldToken, HintHelpers hintHelpers,,,) = + deployer.deployAndConnectContracts(); + stabilityPool = contracts.stabilityPool; + + handler = new SPInvariantsTestHandler( + SPInvariantsTestHandler.Contracts({ + boldToken: boldToken, + borrowerOperations: contracts.borrowerOperations, + collateralToken: contracts.collToken, + priceFeed: contracts.priceFeed, + stabilityPool: contracts.stabilityPool, + troveManager: contracts.troveManager, + collSurplusPool: contracts.collSurplusPool + }), + hintHelpers + ); + + actors.push(Actor("adam", adam)); + actors.push(Actor("barb", barb)); + actors.push(Actor("carl", carl)); + actors.push(Actor("dana", dana)); + actors.push(Actor("eric", eric)); + actors.push(Actor("fran", fran)); + actors.push(Actor("gabe", gabe)); + actors.push(Actor("hope", hope)); + for (uint256 i = 0; i < actors.length; ++i) { + vm.label(actors[i].account, actors[i].label); + } + + vm.label(address(handler), "handler"); + } + + function invariant_allFundsClaimable() internal view { + uint256 stabilityPoolColl = stabilityPool.getCollBalance(); + uint256 stabilityPoolBold = stabilityPool.getTotalBoldDeposits(); + uint256 yieldGainsOwed = stabilityPool.getYieldGainsOwed(); + + uint256 claimableColl = 0; + uint256 claimableBold = 0; + uint256 sumYieldGains = 0; + + for (uint256 i = 0; i < actors.length; ++i) { + claimableColl += stabilityPool.getDepositorCollGain(actors[i].account); + claimableBold += stabilityPool.getCompoundedBoldDeposit(actors[i].account); + sumYieldGains += stabilityPool.getDepositorYieldGain(actors[i].account); + //info("+sumYieldGains: ", sumYieldGains.decimal()); + } + + info("stabilityPoolColl: ", stabilityPoolColl.decimal()); + info("claimableColl: ", claimableColl.decimal()); + info("claimableColl E: ", stabilityPool.getDepositorCollGain(eric).decimal()); + info("claimableColl G: ", stabilityPool.getDepositorCollGain(gabe).decimal()); + info("stabilityPoolBold: ", stabilityPoolBold.decimal()); + info("claimableBold: ", claimableBold.decimal()); + info("claimableBold E: ", stabilityPool.getCompoundedBoldDeposit(eric).decimal()); + info("claimableBold G: ", stabilityPool.getCompoundedBoldDeposit(gabe).decimal()); + info("yieldGainsOwed: ", yieldGainsOwed.decimal()); + info("sumYieldGains: ", sumYieldGains.decimal()); + info("yieldGains E: ", stabilityPool.getDepositorYieldGain(eric).decimal()); + info("yieldGains G: ", stabilityPool.getDepositorYieldGain(gabe).decimal()); + info(""); + assertApproxEqAbsDecimal(stabilityPoolColl, claimableColl, 0.00001 ether, 18, "SP Coll !~ claimable Coll"); + assertApproxEqAbsDecimal(stabilityPoolBold, claimableBold, 0.001 ether, 18, "SP BOLD !~ claimable BOLD"); + assertApproxEqAbsDecimal(yieldGainsOwed, sumYieldGains, 0.001 ether, 18, "SP yieldGainsOwed !~= sum(yieldGain)"); + } + + function testYieldGlobalTracker() external { + vm.prank(adam); + handler.openTrove(18_250 ether); + + vm.prank(eric); + handler.openTrove(10_220 ether); + + vm.prank(gabe); + handler.provideToSp(18_251.7500000000001 ether, false); + + vm.prank(adam); + handler.liquidateMe(); + + vm.prank(adam); + handler.openTrove(18_250 ether); + + invariant_allFundsClaimable(); + } + + function testYieldGlobalTracker2() external { + // coll = 750.071917808219178083 ether, debt = 100_009.589041095890410958 ether + vm.prank(adam); + handler.openTrove(100_000 ether); + + // coll = 15.001438356164383562 ether, debt = 2_000.191780821917808219 ether + vm.prank(eric); + handler.openTrove(2_000 ether); + + vm.prank(eric); + handler.provideToSp(2_000.19178082191781022 ether, false); + + // totalBoldDeposits = 2_000.19178082191781022 ether + + vm.prank(eric); + handler.liquidateMe(); + + // totalBoldDeposits = 0.000000000000002001 ether + // P = 1.000404070842521948 ether + + // coll = 15.001438356164383562 ether, debt = 2_000.191780821917808219 ether + vm.prank(eric); + handler.openTrove(2_000 ether); + + vm.prank(eric); + handler.provideToSp(2_000.191780821917808219 ether, false); + + // totalBoldDeposits = 2_000.19178082191781022 ether + + vm.prank(eric); + handler.liquidateMe(); + + invariant_allFundsClaimable(); + } + + function testYieldGlobalTracker3() external { + // coll = 609.865977378640299561 ether, debt = 81_315.463650485373274767 ether + vm.prank(barb); + handler.openTrove(81_307.667024880247771557 ether); + + // coll = 735.070479452054794543 ether, debt = 98_009.397260273972605648 ether + vm.prank(dana); + handler.openTrove(98_000.000000000000002908 ether); + + // coll = 373.873319035600269508 ether, debt = 49_849.775871413369267714 ether + vm.prank(eric); + handler.openTrove(49_844.996214242140569304 ether); + + // pulling `deposited` from fixture + vm.prank(gabe); + handler.provideToSp(81_315.463650485373356083 ether, false); + + // totalBoldDeposits = 81_315.463650485373356083 ether + + // pulling `deposited` from fixture + vm.prank(eric); + handler.provideToSp(98_009.397260273972605648 ether, false); + + // totalBoldDeposits = 179_324.860910759345961731 ether + + vm.prank(barb); + handler.liquidateMe(); + + // totalBoldDeposits = 98_009.397260273972686964 ether + // P = 0.546546623610923366 ether + + vm.prank(dana); + handler.liquidateMe(); + invariant_allFundsClaimable(); + + // totalBoldDeposits = 0.000000000000081316 ether + // P = 0.000000001294434626 ether + + // coll = 448.153242320289758012 ether, debt = 59_753.765642705301068218 ether + vm.prank(gabe); + handler.openTrove(59_748.03637894293667703 ether); + + invariant_allFundsClaimable(); + + // pulling `deposited` from fixture + vm.prank(gabe); + handler.provideToSp(98_009.397260273972703658 ether, false); + // [FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] + } + + function testYieldGlobalTracker4() external { + // coll = 735.07047945205479457 ether, debt = 98_009.397260273972609312 ether + vm.prank(eric); + handler.openTrove(98_000.000000000000006572 ether); + + // coll = 15.073363060611747045 ether, debt = 2_009.781741414899605927 ether + vm.prank(adam); + handler.openTrove(2_009.589041095890410957 ether); + + // coll = 674.842356181481975293 ether, debt = 89_978.98082419759670561 ether + vm.prank(gabe); + handler.openTrove(89_970.353530023484864596 ether); + + // coll = 562.802728905215994793 ether, debt = 75_040.363854028799305609 ether + vm.prank(carl); + handler.openTrove(75_033.168892628136333632 ether); + + // pulling `deposited` from fixture + vm.prank(eric); + handler.provideToSp(75_040.36385402879938065 ether, false); + + // totalBoldDeposits = 75_040.36385402879938065 ether + + vm.prank(carl); + handler.liquidateMe(); + + // totalBoldDeposits = 0.000000000000075041 ether + // P = 0.000000001000008477 ether + + // pulling `deposited` from fixture + vm.prank(gabe); + handler.provideToSp(98_009.397260273972609312 ether, false); + + // totalBoldDeposits = 98_009.397260273972684353 ether + + vm.prank(eric); + handler.liquidateMe(); + + // totalBoldDeposits = 0.000000000000075041 ether + // P = 0.000000000765657561 ether + + // coll = 456.581526480883492157 ether, debt = 60_877.53686411779895416 ether + vm.prank(hope); + handler.openTrove(60_871.699851803242478854 ether); + + invariant_allFundsClaimable(); + } +} From e2615c735389335f0a3200982ed3849548f10f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Wed, 11 Sep 2024 09:52:03 +0100 Subject: [PATCH 02/10] fix: Hold yield in SP when total deposits amount is low --- contracts/src/ActivePool.sol | 6 ++++-- contracts/src/Interfaces/IStabilityPool.sol | 1 + contracts/src/StabilityPool.sol | 23 ++++++++++++++------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/contracts/src/ActivePool.sol b/contracts/src/ActivePool.sol index 62376420..ccdfab21 100644 --- a/contracts/src/ActivePool.sol +++ b/contracts/src/ActivePool.sol @@ -258,9 +258,11 @@ contract ActivePool is IActivePool { uint256 remainderToLPs = mintedAmount - spYield; _boldToken.mint(address(interestRouter), remainderToLPs); - _boldToken.mint(address(stabilityPool), spYield); - stabilityPool.triggerBoldRewards(spYield); + if (spYield > 0) { + _boldToken.mint(address(stabilityPool), spYield); + stabilityPool.triggerBoldRewards(spYield); + } } lastAggUpdateTime = block.timestamp; diff --git a/contracts/src/Interfaces/IStabilityPool.sol b/contracts/src/Interfaces/IStabilityPool.sol index d4d7d567..7f894bed 100644 --- a/contracts/src/Interfaces/IStabilityPool.sol +++ b/contracts/src/Interfaces/IStabilityPool.sol @@ -76,6 +76,7 @@ interface IStabilityPool is ILiquityBase, IBoldRewardsReceiver { function getTotalBoldDeposits() external view returns (uint256); function getYieldGainsOwed() external view returns (uint256); + function getYieldGainsPending() external view returns (uint256); /* * Calculates the Coll gain earned by the deposit since its last snapshots were taken. diff --git a/contracts/src/StabilityPool.sol b/contracts/src/StabilityPool.sol index 63de2df9..5dd6a8db 100644 --- a/contracts/src/StabilityPool.sol +++ b/contracts/src/StabilityPool.sol @@ -146,6 +146,9 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { // TODO: from the contract's perspective, this is a write-only variable. It is only ever read in tests, so it would // be better to keep it outside the core contract. uint256 internal yieldGainsOwed; + // Total remaining Bold yield gains (from Trove interest mints) held by SP, not yet paid out to depositors, + // and not accounted for because they were received when the total deposits were too small + uint256 internal yieldGainsPending; // --- Data structures --- @@ -227,6 +230,10 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { return yieldGainsOwed; } + function getYieldGainsPending() external view override returns (uint256) { + return yieldGainsPending; + } + // --- External Depositor Functions --- /* provideToSP(): @@ -360,16 +367,18 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { _requireCallerIsActivePool(); uint256 totalBoldDepositsCached = totalBoldDeposits; // cached to save an SLOAD - /* - * When total deposits is 0, B is not updated. In this case, the BOLD issued can not be obtained by later - * depositors - it is missed out on, and remains in the balance of the SP. - * - */ - if (totalBoldDepositsCached == 0 || _boldYield == 0) { + + assert(_boldYield > 0); // TODO: remove before deploying + + // When total deposits is very small, B is not updated. In this case, the BOLD issued can not be obtained by later + // depositors - it is missed out on, and remains in the balance of the SP. + if (totalBoldDepositsCached < DECIMAL_PRECISION) { + yieldGainsPending += _boldYield; return; } - yieldGainsOwed += _boldYield; + yieldGainsOwed += yieldGainsPending + _boldYield; + yieldGainsPending = 0; uint256 yieldPerUnitStaked = _computeYieldPerUnitStaked(_boldYield, totalBoldDepositsCached); From c5be546d166f8bb6f8b62d6d9473172b38b43f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 11:47:54 +0100 Subject: [PATCH 03/10] fix: Update SP pending yields if deposit makes it reach threshold --- contracts/src/StabilityPool.sol | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/contracts/src/StabilityPool.sol b/contracts/src/StabilityPool.sol index 5dd6a8db..6bb9a7b3 100644 --- a/contracts/src/StabilityPool.sol +++ b/contracts/src/StabilityPool.sol @@ -270,10 +270,16 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { _updateDepositAndSnapshots(msg.sender, newDeposit, newStashedColl); boldToken.sendToPool(msg.sender, address(this), _topUp); - _updateTotalBoldDeposits(_topUp + keptYieldGain, 0); + uint256 totalBoldDepositsCached = _updateTotalBoldDeposits(_topUp + keptYieldGain, 0); _decreaseYieldGainsOwed(currentYieldGain); _sendBoldtoDepositor(msg.sender, yieldGainToSend); _sendCollGainToDepositor(collToSend); + + // If there were pending yields and with the new deposit we are reaching the threshold, let’s move the yield to owed + uint256 yieldGainsPendingCached = yieldGainsPending; + if (yieldGainsPendingCached > 0 && totalBoldDepositsCached >= DECIMAL_PRECISION) { + _updateYieldRewardsSum(yieldGainsPendingCached, totalBoldDepositsCached); + } } function _getYieldToKeepOrSend(uint256 _currentYieldGain, bool _doClaim) internal pure returns (uint256, uint256) { @@ -365,11 +371,10 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { function triggerBoldRewards(uint256 _boldYield) external { _requireCallerIsActivePool(); + assert(_boldYield > 0); // TODO: remove before deploying uint256 totalBoldDepositsCached = totalBoldDeposits; // cached to save an SLOAD - assert(_boldYield > 0); // TODO: remove before deploying - // When total deposits is very small, B is not updated. In this case, the BOLD issued can not be obtained by later // depositors - it is missed out on, and remains in the balance of the SP. if (totalBoldDepositsCached < DECIMAL_PRECISION) { @@ -377,10 +382,16 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { return; } - yieldGainsOwed += yieldGainsPending + _boldYield; + _updateYieldRewardsSum(yieldGainsPending + _boldYield, totalBoldDepositsCached); + } + + function _updateYieldRewardsSum(uint256 _accumulatedYield, uint256 _totalBoldDeposits) internal { + assert(_accumulatedYield > 0); // TODO: remove before deploying + + yieldGainsOwed += _accumulatedYield; yieldGainsPending = 0; - uint256 yieldPerUnitStaked = _computeYieldPerUnitStaked(_boldYield, totalBoldDepositsCached); + uint256 yieldPerUnitStaked = _computeYieldPerUnitStaked(_accumulatedYield, _totalBoldDeposits); uint256 marginalYieldGain = yieldPerUnitStaked * P; epochToScaleToB[currentEpoch][currentScale] = epochToScaleToB[currentEpoch][currentScale] + marginalYieldGain; @@ -544,11 +555,13 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { emit StabilityPoolCollBalanceUpdated(newCollBalance); } - function _updateTotalBoldDeposits(uint256 _depositIncrease, uint256 _depositDecrease) internal { - if (_depositIncrease == 0 && _depositDecrease == 0) return; + function _updateTotalBoldDeposits(uint256 _depositIncrease, uint256 _depositDecrease) internal returns (uint256) { + if (_depositIncrease == 0 && _depositDecrease == 0) return totalBoldDeposits; uint256 newTotalBoldDeposits = totalBoldDeposits + _depositIncrease - _depositDecrease; totalBoldDeposits = newTotalBoldDeposits; emit StabilityPoolBoldBalanceUpdated(newTotalBoldDeposits); + + return newTotalBoldDeposits; } function _decreaseYieldGainsOwed(uint256 _amount) internal { From a05c0398d600e7363743834cfcdfe7a3736d96f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 11:48:30 +0100 Subject: [PATCH 04/10] fix: Add pending yield to depositor yield gains with pending getter --- contracts/src/StabilityPool.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/StabilityPool.sol b/contracts/src/StabilityPool.sol index 6bb9a7b3..7112c8e9 100644 --- a/contracts/src/StabilityPool.sol +++ b/contracts/src/StabilityPool.sol @@ -606,11 +606,11 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { Snapshots memory snapshots = depositSnapshots[_depositor]; - uint256 pendingSPYield = activePool.calcPendingSPYield(); + uint256 pendingSPYield = activePool.calcPendingSPYield() + yieldGainsPending; uint256 firstPortionPending; uint256 secondPortionPending; - if (pendingSPYield > 0 && snapshots.epoch == currentEpoch) { + if (pendingSPYield > 0 && snapshots.epoch == currentEpoch && totalBoldDeposits >= DECIMAL_PRECISION) { uint256 yieldNumerator = pendingSPYield * DECIMAL_PRECISION + lastYieldError; uint256 yieldPerUnitStaked = yieldNumerator / totalBoldDeposits; uint256 marginalYieldGain = yieldPerUnitStaked * P; From c9bdcfe985242f99ab1e5e35d8bcd28c47ef53a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 11:50:36 +0100 Subject: [PATCH 05/10] test: Adapt test to new SP pending yield changes --- contracts/src/test/Invariants.t.sol | 6 +- .../src/test/TestContracts/DevTestSetup.sol | 16 ++ .../TestContracts/InvariantsTestHandler.t.sol | 98 +++++++------ contracts/src/test/events.t.sol | 1 + .../src/test/interestRateAggregate.t.sol | 10 +- contracts/src/test/stabilityPool.t.sol | 137 +++++++++++------- 6 files changed, 167 insertions(+), 101 deletions(-) diff --git a/contracts/src/test/Invariants.t.sol b/contracts/src/test/Invariants.t.sol index 0b6c40a9..b3fad5c2 100644 --- a/contracts/src/test/Invariants.t.sol +++ b/contracts/src/test/Invariants.t.sol @@ -133,7 +133,7 @@ contract InvariantsTest is Logging, BaseInvariantTest, BaseMultiCollateralTest { "Wrong StabilityPool deposits" ); assertEqDecimal( - c.stabilityPool.getYieldGainsOwed(), handler.spBoldYield(i), 18, "Wrong StabilityPool yield" + c.stabilityPool.getYieldGainsOwed() + c.stabilityPool.getYieldGainsPending(), handler.spBoldYield(i), 18, "Wrong StabilityPool yield" ); assertEqDecimal(c.stabilityPool.getCollBalance(), handler.spColl(i), 18, "Wrong StabilityPool coll"); @@ -276,10 +276,10 @@ contract InvariantsTest is Logging, BaseInvariantTest, BaseMultiCollateralTest { // This only holds as long as no one sends BOLD directly to the SP's address other than ActivePool assertApproxEqAbsDecimal( boldToken.balanceOf(address(stabilityPool)), - sumBoldDeposit + sumYieldGain + handler.spUnclaimableBoldYield(j), + sumBoldDeposit + sumYieldGain + stabilityPool.getYieldGainsPending(), 1e-3 ether, 18, - "SP BOLD balance !~= claimable + unclaimable BOLD" + "SP BOLD balance !~= claimable + pending" ); } } diff --git a/contracts/src/test/TestContracts/DevTestSetup.sol b/contracts/src/test/TestContracts/DevTestSetup.sol index 17e0374f..9ce9d087 100644 --- a/contracts/src/test/TestContracts/DevTestSetup.sol +++ b/contracts/src/test/TestContracts/DevTestSetup.sol @@ -161,6 +161,22 @@ contract DevTestSetup is BaseTest { assertEq(uint8(troveManager.getTroveStatus(troveIDs.C)), uint8(ITroveManager.Status.closedByLiquidation)); } + function _setupForSPDepositAdjustmentsWithoutOwedYieldRewards() internal returns (ABCDEF memory troveIDs) { + (troveIDs.A, troveIDs.B, troveIDs.C, troveIDs.D) = _setupForBatchLiquidateTrovesPureOffset(); + + // A claims yield rewards + makeSPWithdrawalAndClaim(A, 0); + + // A liquidates C + liquidate(A, troveIDs.C); + + // D sends BOLD to A and B so they have some to use in tests + transferBold(D, A, boldToken.balanceOf(D) / 2); + transferBold(D, B, boldToken.balanceOf(D)); + + assertEq(uint8(troveManager.getTroveStatus(troveIDs.C)), uint8(ITroveManager.Status.closedByLiquidation)); + } + function _setupForPTests() internal returns (ABCDEF memory) { ABCDEF memory troveIDs; (troveIDs.A, troveIDs.B, troveIDs.C, troveIDs.D) = _setupForBatchLiquidateTrovesPureOffset(); diff --git a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol index bd2c5031..9d74355b 100644 --- a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol +++ b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol @@ -198,6 +198,21 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { string errorString; } + struct ProvideToSPContext { + TestDeployer.LiquityContractsDev c; + uint256 pendingInterest; + uint256 pendingYield; + uint256 initialBoldDeposit; + uint256 boldDeposit; + uint256 boldYield; + uint256 ethGain; + uint256 ethStash; + uint256 ethClaimed; + uint256 boldClaimed; + uint256 depositorPendingYield; + string errorString; + } + struct WithdrawFromSPContext { TestDeployer.LiquityContractsDev c; uint256 pendingInterest; @@ -333,10 +348,6 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { // Price per branch mapping(uint256 branchIdx => uint256) _price; - // Bold yield sent to the SP at a time when there are no deposits is lost forever - // We keep track of the lost amount so we can use it in invariants - mapping(uint256 branchIdx => uint256) public spUnclaimableBoldYield; - // All free-floating BOLD is kept in the handler, to be dealt out to actors as needed uint256 _handlerBold; @@ -1529,24 +1540,33 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { } function provideToSP(uint256 i, uint256 amount, bool claim) external { + ProvideToSPContext memory v; + i = _bound(i, 0, branches.length - 1); amount = _bound(amount, 0, _handlerBold); - TestDeployer.LiquityContractsDev memory c = branches[i]; - uint256 pendingInterest = c.activePool.calcPendingAggInterest(); - uint256 initialBoldDeposit = c.stabilityPool.deposits(msg.sender); - uint256 boldDeposit = c.stabilityPool.getCompoundedBoldDeposit(msg.sender); - uint256 boldYield = c.stabilityPool.getDepositorYieldGainWithPending(msg.sender); - uint256 ethGain = c.stabilityPool.getDepositorCollGain(msg.sender); - uint256 ethStash = c.stabilityPool.stashedColl(msg.sender); - uint256 ethClaimed = claim ? ethStash + ethGain : 0; - uint256 boldClaimed = claim ? boldYield : 0; - - info("initial deposit: ", initialBoldDeposit.decimal()); - info("compounded deposit: ", boldDeposit.decimal()); - info("yield gain: ", boldYield.decimal()); - info("coll gain: ", ethGain.decimal()); - info("stashed coll: ", ethStash.decimal()); + v.c = branches[i]; + v.pendingInterest = v.c.activePool.calcPendingAggInterest(); + v.pendingYield = v.c.stabilityPool.getYieldGainsPending(); + v.initialBoldDeposit = v.c.stabilityPool.deposits(msg.sender); + v.boldDeposit = v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender); + v.boldYield = v.c.stabilityPool.getDepositorYieldGainWithPending(msg.sender); + v.ethGain = v.c.stabilityPool.getDepositorCollGain(msg.sender); + v.ethStash = v.c.stabilityPool.stashedColl(msg.sender); + v.ethClaimed = claim ? v.ethStash + v.ethGain : 0; + v.boldClaimed = claim ? v.boldYield : 0; + uint256 totalBoldDepositsBefore = v.c.stabilityPool.getTotalBoldDeposits(); + if (totalBoldDepositsBefore < DECIMAL_PRECISION && amount + totalBoldDepositsBefore >= DECIMAL_PRECISION) { + v.depositorPendingYield = v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18; + } + + info("initial deposit: ", v.initialBoldDeposit.decimal()); + info("compounded deposit: ", v.boldDeposit.decimal()); + info("yield gain: ", v.boldYield.decimal()); + info("coll gain: ", v.ethGain.decimal()); + info("stashed coll: ", v.ethStash.decimal()); + info("pendingYield: ", v.pendingYield.decimal()); + info("pendingInterest: ", v.pendingInterest.decimal()); logCall("provideToSP", i.toString(), amount.decimal(), claim.toString()); // TODO: randomly deal less than amount? @@ -1555,31 +1575,31 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { string memory errorString; vm.prank(msg.sender); - try c.stabilityPool.provideToSP(amount, claim) { + try v.c.stabilityPool.provideToSP(amount, claim) { // Preconditions assertGtDecimal(amount, 0, 18, "Should have failed as amount was zero"); // Effects (deposit) - ethStash += ethGain; - ethStash -= ethClaimed; + v.ethStash += v.ethGain; + v.ethStash -= v.ethClaimed; - boldDeposit += amount; - boldDeposit += boldYield; - boldDeposit -= boldClaimed; + v.boldDeposit += amount; + v.boldDeposit += v.boldYield; + v.boldDeposit -= v.boldClaimed; - assertEqDecimal(c.stabilityPool.getCompoundedBoldDeposit(msg.sender), boldDeposit, 18, "Wrong deposit"); - assertEqDecimal(c.stabilityPool.getDepositorYieldGain(msg.sender), 0, 18, "Wrong yield gain"); - assertEqDecimal(c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain"); - assertEqDecimal(c.stabilityPool.stashedColl(msg.sender), ethStash, 18, "Wrong stashed coll"); + assertEqDecimal(v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender), v.boldDeposit, 18, "Wrong deposit"); + assertApproxEqAbsDecimal(v.c.stabilityPool.getDepositorYieldGain(msg.sender), v.depositorPendingYield, 1e6, 18, "Wrong yield gain"); + assertEqDecimal(v.c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain"); + assertEqDecimal(v.c.stabilityPool.stashedColl(msg.sender), v.ethStash, 18, "Wrong stashed coll"); // Effects (system) - _mintYield(i, pendingInterest, 0); + _mintYield(i, v.pendingInterest, 0); - spColl[i] -= ethClaimed; + spColl[i] -= v.ethClaimed; spBoldDeposits[i] += amount; - spBoldDeposits[i] += boldYield; - spBoldDeposits[i] -= boldClaimed; - spBoldYield[i] -= boldYield; + spBoldDeposits[i] += v.boldYield; + spBoldDeposits[i] -= v.boldClaimed; + spBoldYield[i] -= v.boldYield; } catch Error(string memory reason) { errorString = reason; @@ -1601,8 +1621,8 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { _sweepBold(msg.sender, amount); // Take back the BOLD that was dealt } else { // Cleanup (success) - _sweepBold(msg.sender, boldClaimed); - _sweepColl(i, msg.sender, ethClaimed); + _sweepBold(msg.sender, v.boldClaimed); + _sweepColl(i, msg.sender, v.ethClaimed); } } @@ -2445,11 +2465,7 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { uint256 mintedYield = pendingInterest + upfrontFee; uint256 mintedSPBoldYield = mintedYield * SP_YIELD_SPLIT / DECIMAL_PRECISION; - if (spBoldDeposits[i] == 0) { - spUnclaimableBoldYield[i] += mintedSPBoldYield; - } else { - spBoldYield[i] += mintedSPBoldYield; - } + spBoldYield[i] += mintedSPBoldYield; _pendingInterest[i] = 0; } diff --git a/contracts/src/test/events.t.sol b/contracts/src/test/events.t.sol index 7e50b090..93f8490c 100644 --- a/contracts/src/test/events.t.sol +++ b/contracts/src/test/events.t.sol @@ -674,6 +674,7 @@ contract StabilityPoolEventsTest is EventsTest, IStabilityPoolEvents { // Increase epoch makeSPDepositNoClaim(A, liquidatedDebt); + makeSPWithdrawalAndClaim(A, 0); // Claim yield from first troves troveManager.liquidate(liquidatedTroveId[0]); current.epoch = stabilityPool.currentEpoch(); diff --git a/contracts/src/test/interestRateAggregate.t.sol b/contracts/src/test/interestRateAggregate.t.sol index 3c8d8f0c..cc760cd8 100644 --- a/contracts/src/test/interestRateAggregate.t.sol +++ b/contracts/src/test/interestRateAggregate.t.sol @@ -476,6 +476,8 @@ contract InterestRateAggregate is DevTestSetup { priceFeed.setPrice(2000e18); openTroveNoHints100pct(A, 2 ether, troveDebtRequest, 25e16); makeSPDepositAndClaim(A, sPdeposit); + // claim gains from first trove + makeSPWithdrawalAndClaim(A, 0); // fast-forward time vm.warp(block.timestamp + 1 days); @@ -2148,7 +2150,7 @@ contract InterestRateAggregate is DevTestSetup { // --- claimALLCollGains --- function testClaimAllCollGainsIncreasesAggRecordedDebtByPendingAggInterest() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws depsoiit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -2171,7 +2173,7 @@ contract InterestRateAggregate is DevTestSetup { } function testClaimAllCollGainsReducesPendingAggInterestTo0() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws depsoiit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -2192,7 +2194,7 @@ contract InterestRateAggregate is DevTestSetup { // // Update last agg. update time to now function testClaimAllCollGainsUpdatesLastAggUpdateTimeToNow() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws deposit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -2216,7 +2218,7 @@ contract InterestRateAggregate is DevTestSetup { // mints interest to SP function testClaimAllCollGainsMintsAggInterestToSP() public { ABCDEF memory troveIDs; - troveIDs = _setupForSPDepositAdjustments(); + troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws depsoiit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); diff --git a/contracts/src/test/stabilityPool.t.sol b/contracts/src/test/stabilityPool.t.sol index 7c161e89..7dc2bc9e 100644 --- a/contracts/src/test/stabilityPool.t.sol +++ b/contracts/src/test/stabilityPool.t.sol @@ -71,6 +71,8 @@ contract SPTest is DevTestSetup { uint256 totalDepositsBefore; uint256 spEthBal1; uint256 spEthBal2; + uint256 initialBoldGainA; + uint256 initialBoldGainB; } function _setupStashedAndCurrentCollGains() internal { @@ -777,7 +779,7 @@ contract SPTest is DevTestSetup { } function testClaimAllCollGainsDoesntChangeCurrentCollGain() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws deposit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -795,7 +797,7 @@ contract SPTest is DevTestSetup { } function testClaimAllCollGainsZerosStashedCollGain() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws deposit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -813,7 +815,7 @@ contract SPTest is DevTestSetup { } function testClaimAllCollGainsIncreasesUserBalanceByStashedCollGain() public { - _setupForSPDepositAdjustments(); + _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // A withdraws deposit and stashes gain uint256 deposit_A = stabilityPool.getCompoundedBoldDeposit(A); @@ -859,16 +861,16 @@ contract SPTest is DevTestSetup { ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); uint256 pendingAggInterest = activePool.calcPendingAggInterest(); - assertEq(pendingAggInterest, 0); + assertEq(pendingAggInterest, 0, "Pending interest should be zero"); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0, "BOLD reward sum 1"); // Adjust a Trove in a way that doesn't incur an upfront fee repayBold(B, troveIDs.B, 1_000 ether); uint256 boldRewardSum_2 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_2, boldRewardSum_1); + assertEq(boldRewardSum_2, boldRewardSum_1, "BOLD reward sum 2"); } function testBoldRewardSumIncreasesWhenTroveOpened() public { @@ -880,7 +882,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); openTroveNoHints100pct(E, 3 ether, 2000e18, 25e16); @@ -897,7 +899,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); changeInterestRateNoHints(B, troveIDs.B, 75e16); @@ -937,7 +939,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); adjustTrove100pct(A, troveIDs.A, 1, 1, true, true); @@ -954,7 +956,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); // B applies A's pending interest applyPendingDebt(B, troveIDs.A); @@ -972,7 +974,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); // A liquidates D liquidate(A, troveIDs.D); @@ -991,7 +993,7 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 boldRewardSum_1 = stabilityPool.epochToScaleToB(0, 0); - assertEq(boldRewardSum_1, 0); + assertGt(boldRewardSum_1, 0); uint256 wethBalBefore_A = collToken.balanceOf(A); // A redeems @@ -1089,12 +1091,16 @@ contract SPTest is DevTestSetup { assertEq(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); (, uint256 upfrontFee) = openTroveHelper(E, 0, 3 ether, 2000e18, 25e16); uint256 yieldGainsOwed_2 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_2, yieldGainsOwed_1 + _getSPYield(upfrontFee)); + uint256 yieldGainsPending_2 = stabilityPool.getYieldGainsPending(); + assertEq(yieldGainsOwed_2, yieldGainsOwed_1 + _getSPYield(upfrontFee), "Yield owed mismatch 2"); + assertEq(yieldGainsPending_2, 0, "Yield pending mismatch 2"); } function testBoldRewardsOwedIncreasesWhenTroveOpened() public { @@ -1106,7 +1112,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); openTroveNoHints100pct(E, 3 ether, 2000e18, 25e16); @@ -1123,7 +1131,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); changeInterestRateNoHints(B, troveIDs.B, 75e16); @@ -1142,7 +1152,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertGt(yieldGainsOwed_1, 0); // yield from upfront fee + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); // F sends E his bold so he can close vm.startPrank(F); @@ -1163,7 +1175,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); adjustTrove100pct(A, troveIDs.A, 1, 1, true, true); @@ -1180,7 +1194,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); // B applies A's pending interest applyPendingDebt(B, troveIDs.A); @@ -1198,7 +1214,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); // A liquidates D liquidate(A, troveIDs.D); @@ -1217,8 +1235,11 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertEq(yieldGainsOwed_1, 0); + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); uint256 wethBalBefore_A = collToken.balanceOf(A); + // A redeems redeem(A, 1e18); assertGt(collToken.balanceOf(A), wethBalBefore_A); @@ -1237,7 +1258,9 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 yieldGainsOwed_1 = stabilityPool.getYieldGainsOwed(); - assertGt(yieldGainsOwed_1, 0); // yield from upfront fee + uint256 yieldGainsPending_1 = stabilityPool.getYieldGainsPending(); + assertGt(yieldGainsOwed_1, 0, "Yield owed mismatch 1"); + assertEq(yieldGainsPending_1, 0, "Yield pending mismatch 1"); // E Makes deposit makeSPDepositAndClaim(E, 1e18); @@ -1287,7 +1310,7 @@ contract SPTest is DevTestSetup { // --- depositor BOLD rewards tests --- function testGetDepositorBoldGain_1SPDepositor1RewardEvent_EarnsAllSPYield() public { - ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); + ABCDEF memory troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // B withdraws entirely makeSPWithdrawalAndClaim(B, stabilityPool.getCompoundedBoldDeposit(B)); @@ -1302,14 +1325,14 @@ contract SPTest is DevTestSetup { assertGt(pendingAggInterest, 0); uint256 expectedSpYield = SP_YIELD_SPLIT * pendingAggInterest / 1e18; - // A trove gets poked, interst minted and yield paid to SP + // A trove gets poked, interest minted and yield paid to SP applyPendingDebt(B, troveIDs.A); assertApproximatelyEqual(stabilityPool.getDepositorYieldGain(A), expectedSpYield, 1e4); } function testGetDepositorBoldGain_2SPDepositor1RewardEvent_EarnFairShareOfSPYield() public { - ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); + ABCDEF memory troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); vm.warp(block.timestamp + STALE_TROVE_DURATION + 1); @@ -1332,7 +1355,7 @@ contract SPTest is DevTestSetup { } function testGetDepositorBoldGain_1SPDepositor2RewardEvent_EarnsAllSPYield() public { - ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); + ABCDEF memory troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); // B withdraws entirely makeSPWithdrawalAndClaim(B, stabilityPool.getCompoundedBoldDeposit(B)); @@ -1367,7 +1390,7 @@ contract SPTest is DevTestSetup { } function testGetDepositorBoldGain_2SPDepositor2RewardEvent_EarnFairShareOfSPYield() public { - ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); + ABCDEF memory troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); vm.warp(block.timestamp + STALE_TROVE_DURATION + 1); @@ -1414,7 +1437,7 @@ contract SPTest is DevTestSetup { } function testGetDepositorBoldGain_2SPDepositor1Liq1FreshDeposit_EarnFairShareOfSPYield() public { - ABCDEF memory troveIDs = _setupForSPDepositAdjustments(); + ABCDEF memory troveIDs = _setupForSPDepositAdjustmentsWithoutOwedYieldRewards(); vm.warp(block.timestamp + STALE_TROVE_DURATION + 1); @@ -1781,13 +1804,13 @@ contract SPTest is DevTestSetup { // Cheat 1: manipulate contract state to make value of P low vm.store( address(stabilityPool), - bytes32(uint256(9)), // 9th storage slot where P is stored + bytes32(uint256(10)), // 10th storage slot where P is stored bytes32(uint256(_cheatP)) ); - // Confirm that storage slot 9 is set - uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(9)))); - assertEq(storedVal, _cheatP, "value of slot 9 is not set"); + // Confirm that storage slot 10 is set + uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(10)))); + assertEq(storedVal, _cheatP, "value of slot 10 is not set"); // Confirm that P specfically is set assertEq(stabilityPool.P(), _cheatP, "P is not set"); @@ -1826,13 +1849,13 @@ contract SPTest is DevTestSetup { // Cheat 1: manipulate contract state to make value of P low vm.store( address(stabilityPool), - bytes32(uint256(9)), // 9th storage slot where P is stored + bytes32(uint256(10)), // 10th storage slot where P is stored bytes32(uint256(_cheatP)) ); - // Confirm that storage slot 9 is set - uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(9)))); - assertEq(storedVal, _cheatP, "value of slot 9 is not set"); + // Confirm that storage slot 10 is set + uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(10)))); + assertEq(storedVal, _cheatP, "value of slot 10 is not set"); // Confirm that P specfically is set assertEq(stabilityPool.P(), _cheatP, "P is not set"); @@ -1946,17 +1969,19 @@ contract SPTest is DevTestSetup { // Cheat 1: manipulate contract state to make value of P low vm.store( address(stabilityPool), - bytes32(uint256(9)), // 9th storage slot where P is stored + bytes32(uint256(10)), // 10th storage slot where P is stored bytes32(uint256(_cheatP)) ); - // Confirm that storage slot 9 is set - uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(9)))); - assertEq(storedVal, _cheatP, "value of slot 9 is not set"); + // Confirm that storage slot 10 is set + uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(10)))); + assertEq(storedVal, _cheatP, "value of slot 10 is not set"); // Confirm that P specfically is set assertEq(stabilityPool.P(), _cheatP, "P is not set"); ABCDEF memory troveIDs = _setupForPTests(); + testVars.initialBoldGainA = stabilityPool.getDepositorYieldGain(A); + testVars.initialBoldGainB = stabilityPool.getDepositorYieldGain(B); uint256 troveDebt = troveManager.getTroveEntireDebt(troveIDs.D); uint256 debtDelta = troveDebt - stabilityPool.getTotalBoldDeposits(); @@ -1974,6 +1999,7 @@ contract SPTest is DevTestSetup { uint256 spEthBalBefore = collToken.balanceOf(address(stabilityPool)); liquidate(A, troveIDs.C); + bool spTooLowAfterLiquidateA = stabilityPool.getTotalBoldDeposits() < DECIMAL_PRECISION; uint256 spEthBalAfter = collToken.balanceOf(address(stabilityPool)); testVars.spEthGain1 = spEthBalAfter - spEthBalBefore; @@ -2010,9 +2036,6 @@ contract SPTest is DevTestSetup { testVars.expectedShareOfYield1_A = getShareofSPReward(A, expectedSpYield1); testVars.expectedShareOfYield1_B = getShareofSPReward(B, expectedSpYield1); - assertGt(testVars.expectedShareOfYield1_A, 0); - assertGt(testVars.expectedShareOfYield1_B, 0); - testVars.troveDebt_D = troveManager.getTroveEntireDebt(troveIDs.D); // D makes fresh deposit so that SP can cover the liq @@ -2021,6 +2044,14 @@ contract SPTest is DevTestSetup { testVars.totalSPBeforeLiq_D = stabilityPool.getTotalBoldDeposits(); assertGt(testVars.totalSPBeforeLiq_D, testVars.troveDebt_D); + if (spTooLowAfterLiquidateA) { + testVars.expectedShareOfYield1_A = getShareofSPReward(A, expectedSpYield1); + testVars.expectedShareOfYield1_B = getShareofSPReward(B, expectedSpYield1); + } + + assertGt(testVars.expectedShareOfYield1_A, 0); + assertGt(testVars.expectedShareOfYield1_B, 0); + // D's trove liquidated spEthBalBefore = collToken.balanceOf(address(stabilityPool)); liquidate(A, troveIDs.D); @@ -2045,8 +2076,8 @@ contract SPTest is DevTestSetup { // Check all BOLD and Coll gains are as expected testVars.boldGainA = stabilityPool.getDepositorYieldGain(A); testVars.boldGainB = stabilityPool.getDepositorYieldGain(B); - assertApproximatelyEqual(testVars.expectedShareOfYield1_A, testVars.boldGainA, 1e4); - assertApproximatelyEqual(testVars.expectedShareOfYield1_B, testVars.boldGainB, 1e4); + assertApproximatelyEqual(testVars.initialBoldGainA + testVars.expectedShareOfYield1_A, testVars.boldGainA, 1e4, "A yield gain mismatch"); + assertApproximatelyEqual(testVars.initialBoldGainB + testVars.expectedShareOfYield1_B, testVars.boldGainB, 1e4, "B yield gain mismatch"); uint256 ethGainA = stabilityPool.getDepositorCollGain(A); uint256 ethGainB = stabilityPool.getDepositorCollGain(B); @@ -2063,13 +2094,13 @@ contract SPTest is DevTestSetup { // Cheat 1: manipulate contract state to make value of P low vm.store( address(stabilityPool), - bytes32(uint256(9)), // 9th storage slot where P is stored + bytes32(uint256(10)), // 10th storage slot where P is stored bytes32(uint256(_cheatP)) ); - // Confirm that storage slot 9 is set - uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(9)))); - assertEq(storedVal, _cheatP, "value of slot 9 is not set"); + // Confirm that storage slot 10 is set + uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(10)))); + assertEq(storedVal, _cheatP, "value of slot 10 is not set"); // Confirm that P specfically is set console2.log(stabilityPool.P(), "stabilityPool.P()"); console2.log(_cheatP, "_cheatP"); @@ -2103,13 +2134,13 @@ contract SPTest is DevTestSetup { // Cheat 1: manipulate contract state to make value of P low vm.store( address(stabilityPool), - bytes32(uint256(9)), // 9th storage slot where P is stored + bytes32(uint256(10)), // 10th storage slot where P is stored bytes32(uint256(_cheatP)) ); - // Confirm that storage slot 9 is set - uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(9)))); - assertEq(storedVal, _cheatP, "value of slot 9 is not set"); + // Confirm that storage slot 10 is set + uint256 storedVal = uint256(vm.load(address(stabilityPool), bytes32(uint256(10)))); + assertEq(storedVal, _cheatP, "value of slot 10 is not set"); // Confirm that P specfically is set assertEq(stabilityPool.P(), _cheatP, "P is not set"); From ee2762b1d61ee7134aa2bfbe4df6447484e95f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 16:54:09 +0100 Subject: [PATCH 06/10] =?UTF-8?q?test:=20Add=20=E2=80=9Canchored=E2=80=9D?= =?UTF-8?q?=20invariant=20test=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To make it easy to copy paste output from invariant tests and debug. Also to be able to fix specific test cases without relying on randomness, and make sure those ones pass. --- .../src/test/AnchoredInvariantsTest.t.sol | 390 ++++++++++++++++++ ...d.t.sol => AnchoredSPInvariantsTest.t.sol} | 2 +- 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 contracts/src/test/AnchoredInvariantsTest.t.sol rename contracts/src/test/{SPYield.t.sol => AnchoredSPInvariantsTest.t.sol} (99%) diff --git a/contracts/src/test/AnchoredInvariantsTest.t.sol b/contracts/src/test/AnchoredInvariantsTest.t.sol new file mode 100644 index 00000000..7bfc3f27 --- /dev/null +++ b/contracts/src/test/AnchoredInvariantsTest.t.sol @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "./TestContracts/DevTestSetup.sol"; +import {BaseInvariantTest} from "./TestContracts/BaseInvariantTest.sol"; +import {BaseMultiCollateralTest} from "./TestContracts/BaseMultiCollateralTest.sol"; +import {AdjustedTroveProperties, InvariantsTestHandler} from "./TestContracts/InvariantsTestHandler.t.sol"; +import {Logging} from "./Utils/Logging.sol"; + +contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollateralTest { + using StringFormatting for uint256; + + InvariantsTestHandler handler; + + function setUp() public override { + super.setUp(); + + TestDeployer.TroveManagerParams[] memory p = new TestDeployer.TroveManagerParams[](4); + p[0] = TestDeployer.TroveManagerParams(1.5 ether, 1.1 ether, 1.01 ether, 0.05 ether, 0.1 ether); + p[1] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether); + p[2] = TestDeployer.TroveManagerParams(1.6 ether, 1.2 ether, 1.01 ether, 0.05 ether, 0.1 ether); + p[3] = TestDeployer.TroveManagerParams(1.6 ether, 1.25 ether, 1.01 ether, 0.05 ether, 0.1 ether); + TestDeployer deployer = new TestDeployer(); + Contracts memory contracts; + (contracts.branches, contracts.collateralRegistry, contracts.boldToken, contracts.hintHelpers,, contracts.weth,) + = deployer.deployAndConnectContractsMultiColl(p); + setupContracts(contracts); + + handler = new InvariantsTestHandler({contracts: contracts, assumeNoExpectedFailures: true}); + vm.label(address(handler), "handler"); + + actors.push(Actor("adam", adam)); + actors.push(Actor("barb", barb)); + actors.push(Actor("carl", carl)); + actors.push(Actor("dana", dana)); + actors.push(Actor("eric", eric)); + actors.push(Actor("fran", fran)); + actors.push(Actor("gabe", gabe)); + actors.push(Actor("hope", hope)); + for (uint256 i = 0; i < actors.length; ++i) { + vm.label(actors[i].account, actors[i].label); + } + } + + function testWrongYield() external { + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(adam); + handler.registerBatchManager(0, 0.257486338754888547 ether, 0.580260126400716372 ether, 0.474304801140122485 ether, 0.84978254245815657 ether, 2121012); + + vm.prank(eric); + handler.registerBatchManager(2, 0.995000000000011223 ether, 0.999999999997818617 ether, 0.999999999561578875 ether, 0.000000000000010359 ether, 5174410); + + vm.prank(fran); + handler.warp(3_662_052); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(hope); + handler.addMeToLiquidationBatch(); + + vm.prank(barb); + handler.addMeToLiquidationBatch(); + + // upper hint: 0 + // lower hint: 0 + // upfront fee: 1_246.586073354248297808 ether + vm.prank(hope); + handler.openTrove(0, 99_999.999999999999999997 ether, 2.251600954885856105 ether, 0.650005595391858041 ether, 8768, 0); + + vm.prank(adam); + handler.addMeToLiquidationBatch(); + + vm.prank(eric); + handler.addMeToLiquidationBatch(); + + vm.prank(hope); + handler.warp(9_396_472); + + vm.prank(gabe); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(dana); + handler.registerBatchManager(2, 0.995000000000011139 ether, 0.998635073564148166 ether, 0.996010156573547401 ether, 0.000000000000011577 ether, 9078342); + + vm.prank(carl); + handler.registerBatchManager(1, 0.995000004199127012 ether, 1 ether, 0.999139502777974999 ether, 0.059938454189132239 ether, 1706585); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 897.541972815058774421 ether + vm.prank(gabe); + handler.provideToSP(0, 58_897.613356828171795189 ether, false); + } + + function testRedeemUnderflow() external { + vm.prank(fran); + handler.warp(18_162); + + vm.prank(carl); + handler.registerBatchManager(0, 0.995000001857124003 ether, 0.999999628575220679 ether, 0.999925530120657388 ether, 0.249999999999999999 ether, 12664); + + vm.prank(hope); + handler.addMeToLiquidationBatch(); + + vm.prank(fran); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(fran); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(gabe); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(dana); + handler.addMeToLiquidationBatch(); + + vm.prank(eric); + handler.warp(4_641_555); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(dana); + handler.addMeToLiquidationBatch(); + + vm.prank(gabe); + handler.addMeToLiquidationBatch(); + + vm.prank(fran); + handler.addMeToLiquidationBatch(); + + vm.prank(hope); + handler.registerBatchManager(0, 0.739903753088089514 ether, 0.780288740735740819 ether, 0.767858707410717411 ether, 0.000000000000022941 ether, 21644); + + // upper hint: 80084422859880547211683076133703299733277748156566366325829078699459944778998 + // lower hint: 104346312485569601582594868672255666718935311025283394307913733247512361320190 + // upfront fee: 290.81243876303301812 ether + vm.prank(adam); + handler.openTrove(3, 39_503.887731534058892956 ether, 1.6863644596244192 ether, 0.38385567397413886 ether, 1, 7433679); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(hope); + handler.warp(23_201); + + vm.prank(carl); + handler.warp(18_593_995); + + // redemption rate: 0.195871664252157123 ether + // redeemed BOLD: 15_191.361299840412827416 ether + // redeemed Troves: [ + // [], + // [], + // [], + // [adam], + // ] + vm.prank(carl); + handler.redeemCollateral(15_191.361299840412827416 ether, 0); + + // redemption rate: 0.195871664252157123 ether + // redeemed BOLD: 0.000000000000006302 ether + // redeemed Troves: [ + // [], + // [], + // [], + // [adam], + // ] + vm.prank(dana); + handler.redeemCollateral(0.000000000000006302 ether, 1); + + vm.prank(hope); + handler.registerBatchManager(1, 0.822978751289802582 ether, 0.835495454680029657 ether, 0.833312890646159679 ether, 0.422857251385135959 ether, 29470036); + + vm.prank(gabe); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(barb); + handler.addMeToLiquidationBatch(); + + vm.prank(gabe); + handler.warp(31); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 0 ether + // pendingInterest: 0.012686316538387649 ether + vm.prank(carl); + handler.provideToSP(3, 0.000000000000021916 ether, false); + + // upper hint: 0 + // lower hint: 39695913545351040647077841548061220386885435874215782275463606055905069661493 + // upfront fee: 0 ether + vm.prank(carl); + handler.setBatchManagerAnnualInterestRate(0, 0.998884384586837808 ether, 15539582, 63731457); + + vm.prank(gabe); + handler.registerBatchManager(0, 0.351143076054309979 ether, 0.467168361632094569 ether, 0.433984569464653931 ether, 0.000000000000000026 ether, 16482089); + + vm.prank(adam); + handler.registerBatchManager(3, 0.995000000000006201 ether, 0.996462074472343849 ether, 0.995351673013151748 ether, 0.045759837128294745 ether, 10150905); + + vm.prank(dana); + handler.warp(23_299); + + vm.prank(carl); + handler.warp(13_319_679); + + // redemption rate: 0.246264103698059017 ether + // redeemed BOLD: 16_223.156659761268542045 ether + // redeemed Troves: [ + // [], + // [], + // [], + // [adam], + // ] + vm.prank(eric); + handler.redeemCollateral(16_223.156659761268542045 ether, 0); + } + + function testWrongYieldPrecision() external { + vm.prank(carl); + handler.addMeToLiquidationBatch(); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(barb); + handler.warp(19_326); + + vm.prank(carl); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(dana); + handler.registerBatchManager(3, 0.30820256993275862 ether, 0.691797430067250243 ether, 0.383672204747583321 ether, 0.000000000000018015 ether, 11403); + + vm.prank(eric); + handler.registerBatchManager(3, 0.018392910495297323 ether, 0.98160708950470919 ether, 0.963214179009414206 ether, 0.000000000000019546 ether, 13319597); + + vm.prank(fran); + handler.warp(354); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(eric); + handler.warp(15_305_108); + + // upper hint: 84669063888545001427406517193344625874395507444463583314999084271619652858036 + // lower hint: 69042136817699606427763587628766179145825895354994492055731203083594873444699 + // upfront fee: 1_702.831959251916404109 ether + vm.prank(fran); + handler.openTrove(1, 99_999.999999999999999998 ether, 1.883224555937797003 ether, 0.887905235895642125 ether, 4164477, 39); + + vm.prank(dana); + handler.warp(996); + + vm.prank(eric); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(barb); + handler.warp(4_143_017); + + vm.prank(fran); + handler.addMeToLiquidationBatch(); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 0 ether + // pendingInterest: 0 ether + vm.prank(adam); + handler.provideToSP(0, 0.000000000000011094 ether, true); + + vm.prank(carl); + handler.addMeToUrgentRedemptionBatch(); + + // upper hint: 0 + // lower hint: 0 + // upfront fee: 1_513.428916567114728229 ether + vm.prank(barb); + handler.openTrove(2, 79_311.063107967331806055 ether, 1.900000000000001559 ether, 0.995000000000007943 ether, 3270556590, 1229144376); + + vm.prank(fran); + handler.addMeToLiquidationBatch(); + + // price: 221.052631578948441462 ether + vm.prank(dana); + handler.setPrice(2, 2.100000000000011917 ether); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 1_226.039010661379810958 ether + // pendingInterest: 11_866.268348193546380256 ether + vm.prank(carl); + handler.provideToSP(1, 0.027362680048399155 ether, false); + + // upper hint: 0 + // lower hint: 109724453348421969168156614404527408958334892291486496459024204968877369036377 + // upfront fee: 9.807887080131946403 ether + vm.prank(eric); + handler.openTrove(3, 30_260.348082017558572105 ether, 1.683511222023706186 ether, 0.016900375815455486 ether, 108, 14159); + + vm.prank(carl); + handler.addMeToUrgentRedemptionBatch(); + + vm.prank(adam); + handler.addMeToLiquidationBatch(); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + // redemption rate: 0.1474722457669512 ether + // redeemed BOLD: 64_016.697525751186019703 ether + // redeemed Troves: [ + // [], + // [fran], + // [barb], + // [eric], + // ] + vm.prank(dana); + handler.redeemCollateral(64_016.697525751186019705 ether, 0); + + // upper hint: 102052496222650354016228296600262737092032771006947291868573062530791731100756 + // lower hint: 0 + vm.prank(eric); + handler.applyMyPendingDebt(3, 2542, 468); + + vm.prank(gabe); + handler.warp(20_216); + + vm.prank(carl); + handler.registerBatchManager(1, 0.995000000000425732 ether, 0.998288014105982235 ether, 0.996095220733623871 ether, 0.000000000000027477 ether, 3299); + + vm.prank(carl); + handler.addMeToLiquidationBatch(); + + // redemption rate: 0.108097849716691371 ether + // redeemed BOLD: 0.000151948988774207 ether + // redeemed Troves: [ + // [], + // [fran], + // [barb], + // [eric], + // ] + vm.prank(hope); + handler.redeemCollateral(0.000151948988774209 ether, 0); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 0 ether + // pendingInterest: 0 ether + vm.prank(eric); + handler.provideToSP(0, 76_740.446487959260685533 ether, true); + + vm.prank(adam); + handler.addMeToUrgentRedemptionBatch(); + + // initial deposit: 0 ether + // compounded deposit: 0 ether + // yield gain: 0 ether + // coll gain: 0 ether + // stashed coll: 0 ether + // pendingYield: 9_803.032557027063219919 ether + // pendingInterest: 0 ether + vm.prank(hope); + handler.provideToSP(1, 4.127947448768090932 ether, false); + + } +} diff --git a/contracts/src/test/SPYield.t.sol b/contracts/src/test/AnchoredSPInvariantsTest.t.sol similarity index 99% rename from contracts/src/test/SPYield.t.sol rename to contracts/src/test/AnchoredSPInvariantsTest.t.sol index f34cd682..489a393b 100644 --- a/contracts/src/test/SPYield.t.sol +++ b/contracts/src/test/AnchoredSPInvariantsTest.t.sol @@ -5,7 +5,7 @@ import "./TestContracts/DevTestSetup.sol"; import {SPInvariantsTestHandler} from "./TestContracts/SPInvariantsTestHandler.t.sol"; import {Logging} from "./Utils/Logging.sol"; -contract SPYieldTest is DevTestSetup { +contract AnchoredSPInvariantsTest is DevTestSetup { using StringFormatting for uint256; struct Actor { From 4398f35eed41f03968ef07c425c580ce283dfcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 17:27:28 +0100 Subject: [PATCH 07/10] test: Fix yield accountancy check in invariant tests --- contracts/src/test/TestContracts/InvariantsTestHandler.t.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol index 9d74355b..49991195 100644 --- a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol +++ b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol @@ -1556,8 +1556,9 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.ethClaimed = claim ? v.ethStash + v.ethGain : 0; v.boldClaimed = claim ? v.boldYield : 0; uint256 totalBoldDepositsBefore = v.c.stabilityPool.getTotalBoldDeposits(); - if (totalBoldDepositsBefore < DECIMAL_PRECISION && amount + totalBoldDepositsBefore >= DECIMAL_PRECISION) { - v.depositorPendingYield = v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18; + uint256 totalBoldDepositsAfter = amount + totalBoldDepositsBefore; + if (totalBoldDepositsBefore < DECIMAL_PRECISION && totalBoldDepositsAfter >= DECIMAL_PRECISION) { + v.depositorPendingYield = (v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18) * amount / totalBoldDepositsAfter; } info("initial deposit: ", v.initialBoldDeposit.decimal()); From 4db10e24eb054b2b89ce532547e00b3f2dc59f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Thu, 12 Sep 2024 18:05:34 +0100 Subject: [PATCH 08/10] chore: forge fmt --- .../src/test/AnchoredInvariantsTest.t.sol | 131 +++++++++++++++--- .../src/test/AnchoredSPInvariantsTest.t.sol | 2 +- contracts/src/test/Invariants.t.sol | 5 +- .../TestContracts/InvariantsTestHandler.t.sol | 11 +- contracts/src/test/stabilityPool.t.sol | 14 +- 5 files changed, 138 insertions(+), 25 deletions(-) diff --git a/contracts/src/test/AnchoredInvariantsTest.t.sol b/contracts/src/test/AnchoredInvariantsTest.t.sol index 7bfc3f27..212b0f5e 100644 --- a/contracts/src/test/AnchoredInvariantsTest.t.sol +++ b/contracts/src/test/AnchoredInvariantsTest.t.sol @@ -23,7 +23,7 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater TestDeployer deployer = new TestDeployer(); Contracts memory contracts; (contracts.branches, contracts.collateralRegistry, contracts.boldToken, contracts.hintHelpers,, contracts.weth,) - = deployer.deployAndConnectContractsMultiColl(p); + = deployer.deployAndConnectContractsMultiColl(p); setupContracts(contracts); handler = new InvariantsTestHandler({contracts: contracts, assumeNoExpectedFailures: true}); @@ -47,10 +47,24 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.addMeToUrgentRedemptionBatch(); vm.prank(adam); - handler.registerBatchManager(0, 0.257486338754888547 ether, 0.580260126400716372 ether, 0.474304801140122485 ether, 0.84978254245815657 ether, 2121012); + handler.registerBatchManager( + 0, + 0.257486338754888547 ether, + 0.580260126400716372 ether, + 0.474304801140122485 ether, + 0.84978254245815657 ether, + 2121012 + ); vm.prank(eric); - handler.registerBatchManager(2, 0.995000000000011223 ether, 0.999999999997818617 ether, 0.999999999561578875 ether, 0.000000000000010359 ether, 5174410); + handler.registerBatchManager( + 2, + 0.995000000000011223 ether, + 0.999999999997818617 ether, + 0.999999999561578875 ether, + 0.000000000000010359 ether, + 5174410 + ); vm.prank(fran); handler.warp(3_662_052); @@ -68,7 +82,9 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater // lower hint: 0 // upfront fee: 1_246.586073354248297808 ether vm.prank(hope); - handler.openTrove(0, 99_999.999999999999999997 ether, 2.251600954885856105 ether, 0.650005595391858041 ether, 8768, 0); + handler.openTrove( + 0, 99_999.999999999999999997 ether, 2.251600954885856105 ether, 0.650005595391858041 ether, 8768, 0 + ); vm.prank(adam); handler.addMeToLiquidationBatch(); @@ -86,10 +102,19 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.addMeToUrgentRedemptionBatch(); vm.prank(dana); - handler.registerBatchManager(2, 0.995000000000011139 ether, 0.998635073564148166 ether, 0.996010156573547401 ether, 0.000000000000011577 ether, 9078342); + handler.registerBatchManager( + 2, + 0.995000000000011139 ether, + 0.998635073564148166 ether, + 0.996010156573547401 ether, + 0.000000000000011577 ether, + 9078342 + ); vm.prank(carl); - handler.registerBatchManager(1, 0.995000004199127012 ether, 1 ether, 0.999139502777974999 ether, 0.059938454189132239 ether, 1706585); + handler.registerBatchManager( + 1, 0.995000004199127012 ether, 1 ether, 0.999139502777974999 ether, 0.059938454189132239 ether, 1706585 + ); // initial deposit: 0 ether // compounded deposit: 0 ether @@ -106,7 +131,14 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.warp(18_162); vm.prank(carl); - handler.registerBatchManager(0, 0.995000001857124003 ether, 0.999999628575220679 ether, 0.999925530120657388 ether, 0.249999999999999999 ether, 12664); + handler.registerBatchManager( + 0, + 0.995000001857124003 ether, + 0.999999628575220679 ether, + 0.999925530120657388 ether, + 0.249999999999999999 ether, + 12664 + ); vm.prank(hope); handler.addMeToLiquidationBatch(); @@ -139,13 +171,22 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.addMeToLiquidationBatch(); vm.prank(hope); - handler.registerBatchManager(0, 0.739903753088089514 ether, 0.780288740735740819 ether, 0.767858707410717411 ether, 0.000000000000022941 ether, 21644); + handler.registerBatchManager( + 0, + 0.739903753088089514 ether, + 0.780288740735740819 ether, + 0.767858707410717411 ether, + 0.000000000000022941 ether, + 21644 + ); // upper hint: 80084422859880547211683076133703299733277748156566366325829078699459944778998 // lower hint: 104346312485569601582594868672255666718935311025283394307913733247512361320190 // upfront fee: 290.81243876303301812 ether vm.prank(adam); - handler.openTrove(3, 39_503.887731534058892956 ether, 1.6863644596244192 ether, 0.38385567397413886 ether, 1, 7433679); + handler.openTrove( + 3, 39_503.887731534058892956 ether, 1.6863644596244192 ether, 0.38385567397413886 ether, 1, 7433679 + ); vm.prank(adam); handler.addMeToUrgentRedemptionBatch(); @@ -179,7 +220,14 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.redeemCollateral(0.000000000000006302 ether, 1); vm.prank(hope); - handler.registerBatchManager(1, 0.822978751289802582 ether, 0.835495454680029657 ether, 0.833312890646159679 ether, 0.422857251385135959 ether, 29470036); + handler.registerBatchManager( + 1, + 0.822978751289802582 ether, + 0.835495454680029657 ether, + 0.833312890646159679 ether, + 0.422857251385135959 ether, + 29470036 + ); vm.prank(gabe); handler.addMeToUrgentRedemptionBatch(); @@ -207,10 +255,24 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.setBatchManagerAnnualInterestRate(0, 0.998884384586837808 ether, 15539582, 63731457); vm.prank(gabe); - handler.registerBatchManager(0, 0.351143076054309979 ether, 0.467168361632094569 ether, 0.433984569464653931 ether, 0.000000000000000026 ether, 16482089); + handler.registerBatchManager( + 0, + 0.351143076054309979 ether, + 0.467168361632094569 ether, + 0.433984569464653931 ether, + 0.000000000000000026 ether, + 16482089 + ); vm.prank(adam); - handler.registerBatchManager(3, 0.995000000000006201 ether, 0.996462074472343849 ether, 0.995351673013151748 ether, 0.045759837128294745 ether, 10150905); + handler.registerBatchManager( + 3, + 0.995000000000006201 ether, + 0.996462074472343849 ether, + 0.995351673013151748 ether, + 0.045759837128294745 ether, + 10150905 + ); vm.prank(dana); handler.warp(23_299); @@ -244,10 +306,24 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.addMeToUrgentRedemptionBatch(); vm.prank(dana); - handler.registerBatchManager(3, 0.30820256993275862 ether, 0.691797430067250243 ether, 0.383672204747583321 ether, 0.000000000000018015 ether, 11403); + handler.registerBatchManager( + 3, + 0.30820256993275862 ether, + 0.691797430067250243 ether, + 0.383672204747583321 ether, + 0.000000000000018015 ether, + 11403 + ); vm.prank(eric); - handler.registerBatchManager(3, 0.018392910495297323 ether, 0.98160708950470919 ether, 0.963214179009414206 ether, 0.000000000000019546 ether, 13319597); + handler.registerBatchManager( + 3, + 0.018392910495297323 ether, + 0.98160708950470919 ether, + 0.963214179009414206 ether, + 0.000000000000019546 ether, + 13319597 + ); vm.prank(fran); handler.warp(354); @@ -262,7 +338,9 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater // lower hint: 69042136817699606427763587628766179145825895354994492055731203083594873444699 // upfront fee: 1_702.831959251916404109 ether vm.prank(fran); - handler.openTrove(1, 99_999.999999999999999998 ether, 1.883224555937797003 ether, 0.887905235895642125 ether, 4164477, 39); + handler.openTrove( + 1, 99_999.999999999999999998 ether, 1.883224555937797003 ether, 0.887905235895642125 ether, 4164477, 39 + ); vm.prank(dana); handler.warp(996); @@ -293,7 +371,14 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater // lower hint: 0 // upfront fee: 1_513.428916567114728229 ether vm.prank(barb); - handler.openTrove(2, 79_311.063107967331806055 ether, 1.900000000000001559 ether, 0.995000000000007943 ether, 3270556590, 1229144376); + handler.openTrove( + 2, + 79_311.063107967331806055 ether, + 1.900000000000001559 ether, + 0.995000000000007943 ether, + 3270556590, + 1229144376 + ); vm.prank(fran); handler.addMeToLiquidationBatch(); @@ -316,7 +401,9 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater // lower hint: 109724453348421969168156614404527408958334892291486496459024204968877369036377 // upfront fee: 9.807887080131946403 ether vm.prank(eric); - handler.openTrove(3, 30_260.348082017558572105 ether, 1.683511222023706186 ether, 0.016900375815455486 ether, 108, 14159); + handler.openTrove( + 3, 30_260.348082017558572105 ether, 1.683511222023706186 ether, 0.016900375815455486 ether, 108, 14159 + ); vm.prank(carl); handler.addMeToUrgentRedemptionBatch(); @@ -347,7 +434,14 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater handler.warp(20_216); vm.prank(carl); - handler.registerBatchManager(1, 0.995000000000425732 ether, 0.998288014105982235 ether, 0.996095220733623871 ether, 0.000000000000027477 ether, 3299); + handler.registerBatchManager( + 1, + 0.995000000000425732 ether, + 0.998288014105982235 ether, + 0.996095220733623871 ether, + 0.000000000000027477 ether, + 3299 + ); vm.prank(carl); handler.addMeToLiquidationBatch(); @@ -385,6 +479,5 @@ contract AnchoredInvariantsTest is Logging, BaseInvariantTest, BaseMultiCollater // pendingInterest: 0 ether vm.prank(hope); handler.provideToSP(1, 4.127947448768090932 ether, false); - } } diff --git a/contracts/src/test/AnchoredSPInvariantsTest.t.sol b/contracts/src/test/AnchoredSPInvariantsTest.t.sol index 489a393b..ee766940 100644 --- a/contracts/src/test/AnchoredSPInvariantsTest.t.sol +++ b/contracts/src/test/AnchoredSPInvariantsTest.t.sol @@ -43,7 +43,7 @@ contract AnchoredSPInvariantsTest is DevTestSetup { stabilityPool: contracts.stabilityPool, troveManager: contracts.troveManager, collSurplusPool: contracts.collSurplusPool - }), + }), hintHelpers ); diff --git a/contracts/src/test/Invariants.t.sol b/contracts/src/test/Invariants.t.sol index b3fad5c2..d861a11c 100644 --- a/contracts/src/test/Invariants.t.sol +++ b/contracts/src/test/Invariants.t.sol @@ -133,7 +133,10 @@ contract InvariantsTest is Logging, BaseInvariantTest, BaseMultiCollateralTest { "Wrong StabilityPool deposits" ); assertEqDecimal( - c.stabilityPool.getYieldGainsOwed() + c.stabilityPool.getYieldGainsPending(), handler.spBoldYield(i), 18, "Wrong StabilityPool yield" + c.stabilityPool.getYieldGainsOwed() + c.stabilityPool.getYieldGainsPending(), + handler.spBoldYield(i), + 18, + "Wrong StabilityPool yield" ); assertEqDecimal(c.stabilityPool.getCollBalance(), handler.spColl(i), 18, "Wrong StabilityPool coll"); diff --git a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol index 49991195..f3020f76 100644 --- a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol +++ b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol @@ -1558,7 +1558,8 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { uint256 totalBoldDepositsBefore = v.c.stabilityPool.getTotalBoldDeposits(); uint256 totalBoldDepositsAfter = amount + totalBoldDepositsBefore; if (totalBoldDepositsBefore < DECIMAL_PRECISION && totalBoldDepositsAfter >= DECIMAL_PRECISION) { - v.depositorPendingYield = (v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18) * amount / totalBoldDepositsAfter; + v.depositorPendingYield = + (v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18) * amount / totalBoldDepositsAfter; } info("initial deposit: ", v.initialBoldDeposit.decimal()); @@ -1589,7 +1590,13 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.boldDeposit -= v.boldClaimed; assertEqDecimal(v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender), v.boldDeposit, 18, "Wrong deposit"); - assertApproxEqAbsDecimal(v.c.stabilityPool.getDepositorYieldGain(msg.sender), v.depositorPendingYield, 1e6, 18, "Wrong yield gain"); + assertApproxEqAbsDecimal( + v.c.stabilityPool.getDepositorYieldGain(msg.sender), + v.depositorPendingYield, + 1e6, + 18, + "Wrong yield gain" + ); assertEqDecimal(v.c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain"); assertEqDecimal(v.c.stabilityPool.stashedColl(msg.sender), v.ethStash, 18, "Wrong stashed coll"); diff --git a/contracts/src/test/stabilityPool.t.sol b/contracts/src/test/stabilityPool.t.sol index 7dc2bc9e..be7ef79c 100644 --- a/contracts/src/test/stabilityPool.t.sol +++ b/contracts/src/test/stabilityPool.t.sol @@ -2076,8 +2076,18 @@ contract SPTest is DevTestSetup { // Check all BOLD and Coll gains are as expected testVars.boldGainA = stabilityPool.getDepositorYieldGain(A); testVars.boldGainB = stabilityPool.getDepositorYieldGain(B); - assertApproximatelyEqual(testVars.initialBoldGainA + testVars.expectedShareOfYield1_A, testVars.boldGainA, 1e4, "A yield gain mismatch"); - assertApproximatelyEqual(testVars.initialBoldGainB + testVars.expectedShareOfYield1_B, testVars.boldGainB, 1e4, "B yield gain mismatch"); + assertApproximatelyEqual( + testVars.initialBoldGainA + testVars.expectedShareOfYield1_A, + testVars.boldGainA, + 1e4, + "A yield gain mismatch" + ); + assertApproximatelyEqual( + testVars.initialBoldGainB + testVars.expectedShareOfYield1_B, + testVars.boldGainB, + 1e4, + "B yield gain mismatch" + ); uint256 ethGainA = stabilityPool.getDepositorCollGain(A); uint256 ethGainB = stabilityPool.getDepositorCollGain(B); From b3ad0aecfadde91e5e50c4f1f72ef1731eb4e196 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Fri, 13 Sep 2024 14:39:33 +0700 Subject: [PATCH 09/10] fix: pending yields not being unblock upon withdrawal --- contracts/src/StabilityPool.sol | 8 ++- .../TestContracts/InvariantsTestHandler.t.sol | 60 ++++++++++++------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/contracts/src/StabilityPool.sol b/contracts/src/StabilityPool.sol index 7112c8e9..69a8928e 100644 --- a/contracts/src/StabilityPool.sol +++ b/contracts/src/StabilityPool.sol @@ -332,9 +332,15 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { _updateDepositAndSnapshots(msg.sender, newDeposit, newStashedColl); _decreaseYieldGainsOwed(currentYieldGain); - _updateTotalBoldDeposits(keptYieldGain, boldToWithdraw); + uint256 totalBoldDepositsCached = _updateTotalBoldDeposits(keptYieldGain, boldToWithdraw); _sendBoldtoDepositor(msg.sender, boldToWithdraw + yieldGainToSend); _sendCollGainToDepositor(collToSend); + + // If there were pending yields and with the new deposit we are reaching the threshold, let’s move the yield to owed + uint256 yieldGainsPendingCached = yieldGainsPending; + if (yieldGainsPendingCached > 0 && totalBoldDepositsCached >= DECIMAL_PRECISION) { + _updateYieldRewardsSum(yieldGainsPendingCached, totalBoldDepositsCached); + } } function _getNewStashedCollAndCollToSend(address _depositor, uint256 _currentCollGain, bool _doClaim) diff --git a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol index f3020f76..617a0bc1 100644 --- a/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol +++ b/contracts/src/test/TestContracts/InvariantsTestHandler.t.sol @@ -201,7 +201,8 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { struct ProvideToSPContext { TestDeployer.LiquityContractsDev c; uint256 pendingInterest; - uint256 pendingYield; + uint256 totalBoldDeposits; + uint256 blockedSPYield; uint256 initialBoldDeposit; uint256 boldDeposit; uint256 boldYield; @@ -209,13 +210,14 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { uint256 ethStash; uint256 ethClaimed; uint256 boldClaimed; - uint256 depositorPendingYield; string errorString; } struct WithdrawFromSPContext { TestDeployer.LiquityContractsDev c; uint256 pendingInterest; + uint256 totalBoldDeposits; + uint256 blockedSPYield; uint256 initialBoldDeposit; uint256 boldDeposit; uint256 boldYield; @@ -1547,7 +1549,10 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.c = branches[i]; v.pendingInterest = v.c.activePool.calcPendingAggInterest(); - v.pendingYield = v.c.stabilityPool.getYieldGainsPending(); + v.totalBoldDeposits = v.c.stabilityPool.getTotalBoldDeposits(); + v.blockedSPYield = v.totalBoldDeposits < DECIMAL_PRECISION + ? v.c.activePool.calcPendingSPYield() + v.c.stabilityPool.getYieldGainsPending() + : 0; v.initialBoldDeposit = v.c.stabilityPool.deposits(msg.sender); v.boldDeposit = v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender); v.boldYield = v.c.stabilityPool.getDepositorYieldGainWithPending(msg.sender); @@ -1555,28 +1560,19 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.ethStash = v.c.stabilityPool.stashedColl(msg.sender); v.ethClaimed = claim ? v.ethStash + v.ethGain : 0; v.boldClaimed = claim ? v.boldYield : 0; - uint256 totalBoldDepositsBefore = v.c.stabilityPool.getTotalBoldDeposits(); - uint256 totalBoldDepositsAfter = amount + totalBoldDepositsBefore; - if (totalBoldDepositsBefore < DECIMAL_PRECISION && totalBoldDepositsAfter >= DECIMAL_PRECISION) { - v.depositorPendingYield = - (v.pendingYield + SP_YIELD_SPLIT * v.pendingInterest / 1e18) * amount / totalBoldDepositsAfter; - } info("initial deposit: ", v.initialBoldDeposit.decimal()); info("compounded deposit: ", v.boldDeposit.decimal()); info("yield gain: ", v.boldYield.decimal()); info("coll gain: ", v.ethGain.decimal()); info("stashed coll: ", v.ethStash.decimal()); - info("pendingYield: ", v.pendingYield.decimal()); - info("pendingInterest: ", v.pendingInterest.decimal()); + info("blocked SP yield: ", v.blockedSPYield.decimal()); logCall("provideToSP", i.toString(), amount.decimal(), claim.toString()); // TODO: randomly deal less than amount? _dealBold(msg.sender, amount); - string memory errorString; vm.prank(msg.sender); - try v.c.stabilityPool.provideToSP(amount, claim) { // Preconditions assertGtDecimal(amount, 0, 18, "Should have failed as amount was zero"); @@ -1589,13 +1585,17 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.boldDeposit += v.boldYield; v.boldDeposit -= v.boldClaimed; + // See if the change unblocked any pending yield + v.totalBoldDeposits += amount; + v.totalBoldDeposits += v.boldYield; + v.totalBoldDeposits -= v.boldClaimed; + + uint256 newBoldYield = + v.totalBoldDeposits >= DECIMAL_PRECISION ? v.blockedSPYield * v.boldDeposit / v.totalBoldDeposits : 0; + assertEqDecimal(v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender), v.boldDeposit, 18, "Wrong deposit"); assertApproxEqAbsDecimal( - v.c.stabilityPool.getDepositorYieldGain(msg.sender), - v.depositorPendingYield, - 1e6, - 18, - "Wrong yield gain" + v.c.stabilityPool.getDepositorYieldGain(msg.sender), newBoldYield, 1e6, 18, "Wrong yield gain" ); assertEqDecimal(v.c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain"); assertEqDecimal(v.c.stabilityPool.stashedColl(msg.sender), v.ethStash, 18, "Wrong stashed coll"); @@ -1609,7 +1609,7 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { spBoldDeposits[i] -= v.boldClaimed; spBoldYield[i] -= v.boldYield; } catch Error(string memory reason) { - errorString = reason; + v.errorString = reason; // Justify failures if (reason.equals("StabilityPool: Amount must be non-zero")) { @@ -1619,10 +1619,10 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { } } - if (bytes(errorString).length > 0) { + if (bytes(v.errorString).length > 0) { if (_assumeNoExpectedFailures) vm.assume(false); - info("Expected error: ", errorString); + info("Expected error: ", v.errorString); _log(); // Cleanup (failure) @@ -1641,6 +1641,10 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.c = branches[i]; v.pendingInterest = v.c.activePool.calcPendingAggInterest(); + v.totalBoldDeposits = v.c.stabilityPool.getTotalBoldDeposits(); + v.blockedSPYield = v.totalBoldDeposits < DECIMAL_PRECISION + ? v.c.activePool.calcPendingSPYield() + v.c.stabilityPool.getYieldGainsPending() + : 0; v.initialBoldDeposit = v.c.stabilityPool.deposits(msg.sender); v.boldDeposit = v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender); v.boldYield = v.c.stabilityPool.getDepositorYieldGainWithPending(msg.sender); @@ -1657,6 +1661,7 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { info("yield gain: ", v.boldYield.decimal()); info("coll gain: ", v.ethGain.decimal()); info("stashed coll: ", v.ethStash.decimal()); + info("blocked SP yield: ", v.blockedSPYield.decimal()); logCall("withdrawFromSP", i.toString(), amount.decimal(), claim.toString()); vm.prank(msg.sender); @@ -1672,8 +1677,18 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { v.boldDeposit -= v.boldClaimed; v.boldDeposit -= v.withdrawn; + // See if the change unblocked any pending yield + v.totalBoldDeposits += v.boldYield; + v.totalBoldDeposits -= v.boldClaimed; + v.totalBoldDeposits -= v.withdrawn; + + uint256 newBoldYield = + v.totalBoldDeposits >= DECIMAL_PRECISION ? v.blockedSPYield * v.boldDeposit / v.totalBoldDeposits : 0; + assertEqDecimal(v.c.stabilityPool.getCompoundedBoldDeposit(msg.sender), v.boldDeposit, 18, "Wrong deposit"); - assertEqDecimal(v.c.stabilityPool.getDepositorYieldGain(msg.sender), 0, 18, "Wrong yield gain"); + assertApproxEqAbsDecimal( + v.c.stabilityPool.getDepositorYieldGain(msg.sender), newBoldYield, 1e6, 18, "Wrong yield gain" + ); assertEqDecimal(v.c.stabilityPool.getDepositorCollGain(msg.sender), 0, 18, "Wrong coll gain"); assertEqDecimal(v.c.stabilityPool.stashedColl(msg.sender), v.ethStash, 18, "Wrong stashed coll"); @@ -2474,7 +2489,6 @@ contract InvariantsTestHandler is BaseHandler, BaseMultiCollateralTest { uint256 mintedSPBoldYield = mintedYield * SP_YIELD_SPLIT / DECIMAL_PRECISION; spBoldYield[i] += mintedSPBoldYield; - _pendingInterest[i] = 0; } From 9fb336a33292b8f28f24bf227f1b61f1d74c6c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Fingen?= Date: Fri, 13 Sep 2024 10:37:05 +0100 Subject: [PATCH 10/10] fix: Fix outdated comment in SP --- contracts/src/StabilityPool.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/StabilityPool.sol b/contracts/src/StabilityPool.sol index 69a8928e..ce759aca 100644 --- a/contracts/src/StabilityPool.sol +++ b/contracts/src/StabilityPool.sol @@ -381,8 +381,8 @@ contract StabilityPool is LiquityBase, IStabilityPool, IStabilityPoolEvents { uint256 totalBoldDepositsCached = totalBoldDeposits; // cached to save an SLOAD - // When total deposits is very small, B is not updated. In this case, the BOLD issued can not be obtained by later - // depositors - it is missed out on, and remains in the balance of the SP. + // When total deposits is very small, B is not updated. In this case, the BOLD issued is hold + // until the total deposits reach 1 BOLD (remains in the balance of the SP). if (totalBoldDepositsCached < DECIMAL_PRECISION) { yieldGainsPending += _boldYield; return;