diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 171aa474b..9b827a882 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -73,7 +73,7 @@ - **RE6**: Reserves are unchanged by removing collateral token from a bucket - **RE7**: Reserves increase by bond penalty/reward plus borrower penalty on take above TP. - **RE8**: Reserves increase by bond penalty/reward plus borrower penalty on bucket takes above TP. -- **RE9**: Reserves unchanges by takes and bucket takes below TP. +- **RE9**: Reserves unchanged by takes and bucket takes below TP (at the time of kick). - **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt - **RE11**: Reserves decrease by claimableReserves by kickReserveAuction - **RE12**: Reserves decrease by amount of reserve used to settle a auction diff --git a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 51c90a089..59719c382 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol @@ -74,8 +74,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl numberOfCalls['BBasicHandler.pledgeCollateral']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _prePledgeCollateral(amountToPledge_); @@ -92,8 +91,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl numberOfCalls['BBasicHandler.pullCollateral']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _prePullCollateral(amountToPull_); @@ -110,8 +108,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl numberOfCalls['BBasicHandler.drawDebt']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _preDrawDebt(amountToBorrow_); @@ -128,8 +125,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl numberOfCalls['BBasicHandler.repayDebt']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _preRepayDebt(amountToRepay_); @@ -145,39 +141,49 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function _preAddCollateral( uint256 amountToAdd_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } function _preRemoveCollateral( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); // ensure actor has collateral to remove - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if (lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); + if (_getLenderInfo(_lenderBucketIndex, _actor).lpBalance == 0) { + _addCollateral(boundedAmount_, _lenderBucketIndex); + } } function _prePledgeCollateral( uint256 amountToPledge_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, _erc20Pool.collateralScale(), MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToPledge_, _erc20Pool.collateralScale(), MAX_COLLATERAL_AMOUNT + ); } function _prePullCollateral( uint256 amountToPull_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPull_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToPull_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } function _preDrawDebt( uint256 amountToBorrow_ ) internal override returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToBorrow_, MIN_DEBT_AMOUNT, MAX_DEBT_AMOUNT); + boundedAmount_ = constrictToRange( + amountToBorrow_, MIN_DEBT_AMOUNT, MAX_DEBT_AMOUNT + ); // borrower cannot make any action when in auction - (uint256 kickTime, uint256 collateral,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return boundedAmount_; + if (_getAuctionInfo(_actor).kickTime != 0) return boundedAmount_; // Pre Condition // 1. borrower's debt should exceed minDebt @@ -185,7 +191,6 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl // 3. drawDebt should not make borrower under collateralized // 1. borrower's debt should exceed minDebt - (uint256 debt,, ) = _poolInfo.borrowerInfo(address(_pool), _actor); (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); if (boundedAmount_ < minDebt && minDebt < MAX_DEBT_AMOUNT) boundedAmount_ = minDebt + 1; @@ -202,27 +207,29 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl (uint256 currentPoolDebt, , , ) = _pool.debtInfo(); uint256 nextPoolDebt = currentPoolDebt + boundedAmount_; uint256 newLup = _priceAt(_pool.depositIndex(nextPoolDebt)); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(_actor); // repay debt if borrower becomes undercollateralized with new debt at new lup - if (!_isCollateralized(debt + boundedAmount_, collateral, newLup, _pool.poolType())) { + if (!_isCollateralized(borrowerInfo.debt + boundedAmount_, borrowerInfo.collateral, newLup, _pool.poolType())) { _repayDebt(type(uint256).max); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - _pullCollateral(collateral); + borrowerInfo = _getBorrowerInfo(_actor); + _pullCollateral(borrowerInfo.collateral); - require(debt == 0, "borrower has debt"); + require(borrowerInfo.debt == 0, "borrower has debt"); } } function _preRepayDebt( uint256 amountToRepay_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); + boundedAmount_ = constrictToRange( + amountToRepay_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT + ); // ensure actor has debt to repay - (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); - if (debt == 0) { + if (_getBorrowerInfo(_actor).debt == 0) { boundedAmount_ = _preDrawDebt(boundedAmount_); _drawDebt(boundedAmount_); } diff --git a/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol index ee3a1ae06..eafd8e1be 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.18; -import { LiquidationPoolHandler } from '../../base/handlers/LiquidationPoolHandler.sol'; -import { BasicERC20PoolHandler } from './BasicERC20PoolHandler.sol'; +import { LiquidationPoolHandler } from '../../base/handlers/LiquidationPoolHandler.sol'; +import { BasicERC20PoolHandler } from './BasicERC20PoolHandler.sol'; contract LiquidationERC20PoolHandler is LiquidationPoolHandler, BasicERC20PoolHandler { @@ -18,7 +18,9 @@ contract LiquidationERC20PoolHandler is LiquidationPoolHandler, BasicERC20PoolHa } function _constrictTakeAmount(uint256 amountToTake_) internal view override returns(uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } } \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol index a6932f66d..9c3fc7822 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol @@ -42,7 +42,7 @@ contract PanicExitERC20PoolHandler is UnboundedLiquidationPoolHandler, Unbounded _setupLendersAndDeposits(LENDERS); _setupBorrowersAndLoans(LOANS_COUNT); - ( , , uint256 totalLoans) = _pool.loansInfo(); + uint256 totalLoans = _getLoansInfo().noOfLoans; require(totalLoans == LOANS_COUNT, "loans setup failed"); vm.warp(block.timestamp + 100_000 days); @@ -56,19 +56,22 @@ contract PanicExitERC20PoolHandler is UnboundedLiquidationPoolHandler, Unbounded uint256 borrowerIndex_, uint256 skippedTime_ ) external useTimestamps skipTime(skippedTime_) writeLogs { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, _activeBorrowers.values().length - 1); + borrowerIndex_ = constrictToRange( + borrowerIndex_, 0, _activeBorrowers.values().length - 1 + ); _actor = _borrowers[borrowerIndex_]; + changePrank(_actor); - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(_actor); - if (block.timestamp > kickTime + 72 hours) { + if (block.timestamp > _getAuctionInfo(_actor).kickTime + 72 hours) { numberOfCalls['BPanicExitPoolHandler.settleDebt']++; _settleAuction(_actor, numberOfBuckets); } else { numberOfCalls['BPanicExitPoolHandler.repayLoan']++; _repayDebt(type(uint256).max); } - (, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + + uint256 collateral = _getBorrowerInfo(_actor).collateral; _pullCollateral(collateral); _resetSettledAuction(_actor, borrowerIndex_); @@ -98,7 +101,8 @@ contract PanicExitERC20PoolHandler is UnboundedLiquidationPoolHandler, Unbounded if (takeAuction_) { vm.warp(block.timestamp + 61 minutes); - ( , uint256 auctionedCollateral, ) = _pool.borrowerInfo(borrower); + + uint256 auctionedCollateral = _getBorrowerInfo(borrower).collateral; _takeAuction(borrower, auctionedCollateral, _actor); _resetSettledAuction(borrower, borrowerIndex_); } @@ -151,20 +155,18 @@ contract PanicExitERC20PoolHandler is UnboundedLiquidationPoolHandler, Unbounded ) external useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BPanicExitPoolHandler.withdrawBonds']++; - kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); - address kicker = _lenders[kickerIndex_]; - - (uint256 kickerClaimable, ) = _pool.kickerInfo(kicker); + kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); + address kicker = _lenders[kickerIndex_]; _actor = kicker; changePrank(_actor); - _withdrawBonds(kicker, kickerClaimable); + _withdrawBonds(kicker, _getKickerInfo(kicker).claimableBond); } function settleHeadAuction( uint256 skippedTime_ ) external useTimestamps skipTime(skippedTime_) writeLogs { - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); + address headAuction = _getAuctionInfo(address(0)).head; if (headAuction != address(0)) { _settleAuction(headAuction, 10); _resetSettledAuction(headAuction, 0); @@ -218,8 +220,7 @@ contract PanicExitERC20PoolHandler is UnboundedLiquidationPoolHandler, Unbounded } function _resetSettledAuction(address borrower_, uint256 borrowerIndex_) internal { - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower_); - if (kickTime == 0) { + if (_getAuctionInfo(borrower_).kickTime == 0) { if (borrowerIndex_ != 0) _activeBorrowers.remove(borrowerIndex_); } } diff --git a/tests/forge/invariants/ERC20Pool/handlers/SettleERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/SettleERC20PoolHandler.sol index 8c0fcc438..64e8b2a4b 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/SettleERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/SettleERC20PoolHandler.sol @@ -43,7 +43,7 @@ contract SettleERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBas _setupLendersAndDeposits(LENDERS); _setupBorrowersAndLoans(LOANS_COUNT); - ( , , uint256 totalLoans) = _pool.loansInfo(); + uint256 totalLoans = _getLoansInfo().noOfLoans; require(totalLoans == LOANS_COUNT, "loans setup failed"); vm.warp(block.timestamp + 1_000 days); @@ -62,7 +62,7 @@ contract SettleERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBas kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); address kicker = _lenders[kickerIndex_]; - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower); + uint256 kickTime = _getAuctionInfo(borrower).kickTime; // Kick auction if not already kicked if (kickTime == 0) { @@ -71,7 +71,7 @@ contract SettleERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBas _kickAuction(borrower); } - (,,, kickTime,,,,,,) = _pool.auctionInfo(borrower); + kickTime = _getAuctionInfo(borrower).kickTime; if (kickTime == 0) return; @@ -82,174 +82,11 @@ contract SettleERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBas changePrank(borrower); _actor = borrower; - _settle(borrower, numberOfBuckets); + _settleAuction(borrower, numberOfBuckets); _resetSettledAuction(borrower, borrowerIndex_); } - function repayDebtByThirdParty( - uint256 actorIndex_, - uint256 kickerIndex_, - uint256 borrowerIndex_, - uint256 skippedTime_ - ) external useTimestamps skipTime(skippedTime_) writeLogs { - numberOfCalls['SettlePoolHandler.repayLoan']++; - - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, _activeBorrowers.values().length - 1); - address borrower = _borrowers[borrowerIndex_]; - - kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); - address kicker = _lenders[kickerIndex_]; - - actorIndex_ = constrictToRange(actorIndex_, 0, LENDERS - 1); - address payer = _lenders[actorIndex_]; - - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower); - - // Kick auction if not already kicked - if (kickTime == 0) { - changePrank(kicker); - _actor = kicker; - _kickAuction(borrower); - } - - (,,, kickTime,,,,,,) = _pool.auctionInfo(borrower); - - if (kickTime == 0) return; - - // skip time to make auction settleable - if (block.timestamp < kickTime + 72 hours) { - skip(kickTime + 73 hours - block.timestamp); - } - - // skip time to make auction settleable - if (block.timestamp < kickTime + 72 hours) { - skip(kickTime + 73 hours - block.timestamp); - } - - changePrank(payer); - _repayDebtByThirdParty(payer, borrower, type(uint256).max); - - _resetSettledAuction(borrower, borrowerIndex_); - } - - function _settle( - address borrower_, - uint256 maxDepth_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBLiquidationHandler.settleAuction']++; - ( - uint256 borrowerT0Debt, - uint256 collateral, - ) = _pool.borrowerInfo(borrower_); - (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - (uint256 inflator, ) = _pool.inflatorInfo(); - - _pool.settle(borrower_, maxDepth_); - // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb - while (maxDepth_ != 0 && borrowerT0Debt != 0 && collateral != 0) { - uint256 bucketIndex = fenwickIndexForSum(1); - uint256 maxSettleableDebt = Maths.floorWmul(collateral, _priceAt(bucketIndex)); - uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); - - if (fenwickDeposit == 0 && maxSettleableDebt != 0) { - collateral = 0; - // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 - // **B5**: when settle with collateral: record min bucket where collateral added - buckets.add(7388); - lenderDepositTime[borrower_][7388] = block.timestamp; - } else { - if (bucketIndex != MAX_FENWICK_INDEX) { - // enough deposit in bucket and collateral avail to settle entire debt - if (fenwickDeposit >= borrowerDebt && maxSettleableDebt >= borrowerDebt) { - fenwickDeposits[bucketIndex] -= borrowerDebt; - collateral -= Maths.ceilWdiv(borrowerDebt, _priceAt(bucketIndex)); - borrowerT0Debt = 0; - } - // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount - else if (maxSettleableDebt >= fenwickDeposit) { - fenwickDeposits[bucketIndex] = 0; - collateral -= Maths.ceilWdiv(fenwickDeposit, _priceAt(bucketIndex)); - borrowerT0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); - } - // exchange all collateral with deposit - else { - fenwickDeposits[bucketIndex] -= maxSettleableDebt; - collateral = 0; - borrowerT0Debt -= Maths.floorWdiv(maxSettleableDebt, inflator); - } - } else { - collateral = 0; - // **B5**: when settle with collateral: record min bucket where collateral added. - // Lender doesn't get any LP when settle bad debt. - buckets.add(7388); - } - } - - maxDepth_ -= 1; - } - - // if collateral becomes 0 and still debt is left, settle debt by reserves and hpb making buckets bankrupt - if (borrowerT0Debt != 0 && collateral == 0) { - - (uint256 reservesAfterAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - if (reservesBeforeAction > reservesAfterAction) { - // **RE12**: Reserves decrease by amount of reserve used to settle a auction - decreaseInReserves = reservesBeforeAction - reservesAfterAction; - } else { - // Reserves might increase upto 2 WAD due to rounding issue - increaseInReserves = reservesAfterAction - reservesBeforeAction; - } - borrowerT0Debt -= Maths.min(Maths.wdiv(decreaseInReserves, inflator), borrowerT0Debt); - - while (maxDepth_ != 0 && borrowerT0Debt != 0) { - uint256 bucketIndex = fenwickIndexForSum(1); - uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); - - if (bucketIndex != MAX_FENWICK_INDEX) { - // debt is greater than bucket deposit - if (borrowerDebt > fenwickDeposit) { - fenwickDeposits[bucketIndex] = 0; - borrowerT0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); - } - // bucket deposit is greater than debt - else { - fenwickDeposits[bucketIndex] -= borrowerDebt; - borrowerT0Debt = 0; - } - } - - maxDepth_ -= 1; - } - } - // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower_); - if (kickTime == 0 && collateral % 1e18 != 0 && _pool.poolType() == 1) { - buckets.add(7388); - lenderDepositTime[borrower_][7388] = block.timestamp; - } - } - - function _repayDebtByThirdParty( - address payer_, - address borrower_, - uint256 amountToRepay_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.repayDebt']++; - - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); - - // ensure actor always has amount of quote to repay - _ensureQuoteAmount(payer_, borrowerDebt + 10 * 1e18); - - try _erc20Pool.repayDebt(borrower_, amountToRepay_, 0, borrower_, 7388) { - } catch (bytes memory err) { - _ensurePoolError(err); - } - } - function _setupLendersAndDeposits(uint256 count_) internal virtual { uint256[] memory buckets = buckets.values(); for (uint256 i; i < count_;) { @@ -297,8 +134,7 @@ contract SettleERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBas } function _resetSettledAuction(address borrower_, uint256 borrowerIndex_) internal { - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower_); - if (kickTime == 0) { + if (_getAuctionInfo(borrower_).kickTime == 0) { if (borrowerIndex_ != 0) _activeBorrowers.remove(borrowerIndex_); } } diff --git a/tests/forge/invariants/ERC20Pool/handlers/TradingERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/TradingERC20PoolHandler.sol index 0a74f3036..d80340964 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/TradingERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/TradingERC20PoolHandler.sol @@ -71,13 +71,12 @@ contract TradingERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBa changePrank(_actor); - uint256 rateBeforeSwap = _pool.bucketExchangeRate(_lenderBucketIndex); - (uint256 lpBeforeSwap, , , ,) = _pool.bucketInfo(_lenderBucketIndex); + BucketInfo memory bucketInfoBeforeSwap = _getBucketInfo(_lenderBucketIndex); _addQuoteToken(tradeAmount_, _lenderBucketIndex); _removeCollateral(type(uint256).max, _lenderBucketIndex); - (uint256 lpAfterSwap, , , ,) = _pool.bucketInfo(_lenderBucketIndex); + BucketInfo memory bucketInfoAfterSwap = _getBucketInfo(_lenderBucketIndex); printLine( string( @@ -85,10 +84,13 @@ contract TradingERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBa ) ); printLog("Trading price = ", _priceAt(_lenderBucketIndex)); - printLog("Bucket LP before = ", lpBeforeSwap); - printLog("Bucket LP after = ", lpAfterSwap); + printLog("Bucket LP before = ", bucketInfoBeforeSwap.lpBalance); + printLog("Bucket LP after = ", bucketInfoAfterSwap.lpBalance); - require (rateBeforeSwap == _pool.bucketExchangeRate(_lenderBucketIndex), "R1-R8: Exchange Rate Invariant"); + require ( + bucketInfoBeforeSwap.exchangeRate == bucketInfoAfterSwap.exchangeRate, + "R1-R8: Exchange Rate Invariant" + ); } function swapCollateralForQuote( @@ -104,13 +106,12 @@ contract TradingERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBa changePrank(_actor); - uint256 rateBeforeSwap = _pool.bucketExchangeRate(_lenderBucketIndex); - (uint256 lpBeforeSwap, , , ,) = _pool.bucketInfo(_lenderBucketIndex); + BucketInfo memory bucketInfoBeforeSwap = _getBucketInfo(_lenderBucketIndex); _addCollateral(tradeAmount_, _lenderBucketIndex); _removeQuoteToken(type(uint256).max, _lenderBucketIndex); - (uint256 lpAfterSwap, , , ,) = _pool.bucketInfo(_lenderBucketIndex); + BucketInfo memory bucketInfoAfterSwap = _getBucketInfo(_lenderBucketIndex); printLine( string( @@ -118,10 +119,13 @@ contract TradingERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBa ) ); printLog("Trading price = ", _priceAt(_lenderBucketIndex)); - printLog("Bucket LP before = ", lpBeforeSwap); - printLog("Bucket LP after = ", lpAfterSwap); + printLog("Bucket LP before = ", bucketInfoBeforeSwap.lpBalance); + printLog("Bucket LP after = ", bucketInfoAfterSwap.lpBalance); - require (rateBeforeSwap == _pool.bucketExchangeRate(_lenderBucketIndex), "R1-R8: Exchange Rate Invariant"); + require ( + bucketInfoBeforeSwap.exchangeRate == bucketInfoAfterSwap.exchangeRate, + "R1-R8: Exchange Rate Invariant" + ); } function _setupQuoteDeposits(uint256 count_) internal virtual { @@ -171,8 +175,7 @@ contract TradingERC20PoolHandler is UnboundedLiquidationPoolHandler, UnboundedBa } function _resetSettledAuction(address borrower_, uint256 borrowerIndex_) internal { - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower_); - if (kickTime == 0) { + if (_getAuctionInfo(borrower_).kickTime == 0) { if (borrowerIndex_ != 0) _activeBorrowers.remove(borrowerIndex_); } } diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol index ae6885259..0b3a4071b 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol @@ -36,17 +36,24 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B // ensure actor always has amount of collateral to add _ensureCollateralAmount(_actor, amount_); - (uint256 lpBalanceBeforeAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); + uint256 lpBalanceBeforeAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; - try _erc20Pool.addCollateral(amount_, bucketIndex_, block.timestamp + 1 minutes) { + try _erc20Pool.addCollateral( + amount_, + bucketIndex_, + block.timestamp + 1 minutes + ) { // **B5**: when adding collateral: lender deposit time = timestamp of block when deposit happened lenderDepositTime[_actor][bucketIndex_] = block.timestamp; // **R5**: Exchange rates are unchanged by adding collateral token into a bucket exchangeRateShouldNotChange[bucketIndex_] = true; // Post action condition - (uint256 lpBalanceAfterAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + uint256 lpBalanceAfterAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + require( + lpBalanceAfterAction > lpBalanceBeforeAction, + "LP balance should increase" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -58,17 +65,21 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.removeCollateral']++; - (uint256 lpBalanceBeforeAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); - - try _erc20Pool.removeCollateral(amount_, bucketIndex_) { + uint256 lpBalanceBeforeAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + try _erc20Pool.removeCollateral( + amount_, + bucketIndex_ + ) { // **R6**: Exchange rates are unchanged by removing collateral token from a bucket exchangeRateShouldNotChange[bucketIndex_] = true; // Post action condition - (uint256 lpBalanceAfterAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); - + uint256 lpBalanceAfterAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + require( + lpBalanceAfterAction < lpBalanceBeforeAction, + "LP balance should decrease" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -91,7 +102,12 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B exchangeRateShouldNotChange[bucketIndex] = true; } - try _erc20Pool.drawDebt(_actor, 0, 0, amount_) { + try _erc20Pool.drawDebt( + _actor, + 0, + 0, + amount_ + ) { } catch (bytes memory err) { _ensurePoolError(err); } @@ -107,8 +123,13 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B exchangeRateShouldNotChange[bucketIndex] = true; } - try _erc20Pool.repayDebt(_actor, 0, amount_, _actor, 7388) { - + try _erc20Pool.repayDebt( + _actor, + 0, + amount_, + _actor, + 7388 + ) { } catch (bytes memory err) { _ensurePoolError(err); } @@ -134,7 +155,12 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B (uint256 interestRate, ) = _erc20Pool.interestRateInfo(); - try _erc20Pool.drawDebt(_actor, amount_, 7388, collateralToPledge) { + try _erc20Pool.drawDebt( + _actor, + amount_, + 7388, + collateralToPledge + ) { // amount is rounded by pool to token scale amount_ = _roundToScale(amount_, _pool.quoteTokenScale()); @@ -142,7 +168,6 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B increaseInReserves += Maths.wmul( amount_, _borrowFeeRate(interestRate) ); - } catch (bytes memory err) { _ensurePoolError(err); } @@ -153,13 +178,18 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.repayDebt']++; - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), _actor); + uint256 borrowerDebt = _getBorrowerInfo(_actor).debt; // ensure actor always has amount of quote to repay _ensureQuoteAmount(_actor, borrowerDebt + 10 * 1e18); - try _erc20Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { - + try _erc20Pool.repayDebt( + _actor, + amountToRepay_, + 0, + _actor, + 7388 + ) { } catch (bytes memory err) { _ensurePoolError(err); } diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol index 74c352e53..88cf0dd1e 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -70,7 +70,10 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan numberOfCalls['BBasicHandler.mergeCollateral']++; // Prepare test phase - (uint256 NFTAmount, uint256[] memory bucketIndexes) = _preMergeCollateral(); + ( + uint256 NFTAmount, + uint256[] memory bucketIndexes + ) = _preMergeCollateral(); // Action phase _mergeCollateral(NFTAmount, bucketIndexes); @@ -88,8 +91,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan numberOfCalls['BBasicHandler.pledgeCollateral']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _prePledgeCollateral(amountToPledge_); @@ -106,8 +108,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan numberOfCalls['BBasicHandler.pullCollateral']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _prePullCollateral(amountToPull_); @@ -124,8 +125,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan numberOfCalls['BBasicHandler.drawDebt']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _preDrawDebt(amountToBorrow_); @@ -142,8 +142,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan numberOfCalls['BBasicHandler.repayDebt']++; // borrower cannot make any action when in auction - (uint256 kickTime,,,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return; + if (_getAuctionInfo(_actor).kickTime != 0) return; // Prepare test phase uint256 boundedAmount = _preRepayDebt(amountToRepay_); @@ -159,17 +158,22 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function _preAddCollateral( uint256 amountToAdd_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } function _preRemoveCollateral( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); // ensure actor has collateral to remove - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if (lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); + if (_getLenderInfo(_lenderBucketIndex, _actor).lpBalance == 0) { + _addCollateral(boundedAmount_, _lenderBucketIndex); + } } function _preMergeCollateral() internal returns(uint256 NFTAmount_, uint256[] memory bucketIndexes_) { @@ -179,12 +183,16 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan uint256 bucketIndex = bucketIndexes_[i]; // Add Quote token in each bucket such that user has enough lps in each bucket to merge collateral - uint256 price = _poolInfo.indexToPrice(bucketIndex); - _addQuoteToken(price, bucketIndex); + _addQuoteToken( + _poolInfo.indexToPrice(bucketIndex), + bucketIndex + ); + + uint256 collateralAmount = _poolInfo.lpToCollateral( + address(_erc721Pool), _getLenderInfo(bucketIndex, _actor).lpBalance, bucketIndex + ); - (uint256 lenderLps, ) = _erc721Pool.lenderInfo(bucketIndex, _actor); - uint256 collateralAmount =_poolInfo.lpToCollateral(address(_erc721Pool), lenderLps, bucketIndex); - NFTAmount_ += collateralAmount; + NFTAmount_ += collateralAmount; } // Round collateral amount @@ -194,23 +202,28 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function _prePledgeCollateral( uint256 amountToPledge_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToPledge_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } function _prePullCollateral( uint256 amountToPull_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPull_, 0, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToPull_, 0, MAX_COLLATERAL_AMOUNT + ); } function _preDrawDebt( uint256 amountToBorrow_ ) internal override returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToBorrow_, MIN_DEBT_AMOUNT, MAX_DEBT_AMOUNT); + boundedAmount_ = constrictToRange( + amountToBorrow_, MIN_DEBT_AMOUNT, MAX_DEBT_AMOUNT + ); // borrower cannot make any action when in auction - (uint256 kickTime, uint256 collateral, uint256 debt,,,,,,) = _poolInfo.auctionStatus(address(_pool), _actor); - if (kickTime != 0) return boundedAmount_; + if (_getAuctionInfo(_actor).kickTime != 0) return boundedAmount_; // Pre Condition // 1. borrower's debt should exceed minDebt @@ -218,7 +231,6 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan // 3. drawDebt should not make borrower under collateralized // 1. borrower's debt should exceed minDebt - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); if (boundedAmount_ < minDebt && minDebt < MAX_DEBT_AMOUNT) boundedAmount_ = minDebt + 1; @@ -235,27 +247,33 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan (uint256 currentPoolDebt, , , ) = _pool.debtInfo(); uint256 nextPoolDebt = currentPoolDebt + boundedAmount_; uint256 newLup = _priceAt(_pool.depositIndex(nextPoolDebt)); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(_actor); // repay debt if borrower becomes undercollateralized with new debt at new lup - if (!_isCollateralized(debt + boundedAmount_, collateral, newLup, _pool.poolType())) { + if (!_isCollateralized( + borrowerInfo.debt + boundedAmount_, borrowerInfo.collateral, newLup, _pool.poolType()) + ) { _repayDebt(type(uint256).max); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - _pullCollateral(collateral); + borrowerInfo = _getBorrowerInfo(_actor); + _pullCollateral(borrowerInfo.collateral); - require(debt == 0, "borrower has debt"); + require( + borrowerInfo.debt == 0, + "borrower has debt" + ); } } function _preRepayDebt( uint256 amountToRepay_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); + boundedAmount_ = constrictToRange( + amountToRepay_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT + ); // ensure actor has debt to repay - (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); - if (debt == 0) { + if (_getBorrowerInfo(_actor).debt == 0) { boundedAmount_ = _preDrawDebt(boundedAmount_); _drawDebt(boundedAmount_); } diff --git a/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol index 176f2d2c1..dd3467a14 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol @@ -18,7 +18,9 @@ contract LiquidationERC721PoolHandler is LiquidationPoolHandler, BasicERC721Pool } function _constrictTakeAmount(uint256 amountToTake_) internal view override returns(uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + boundedAmount_ = constrictToRange( + amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT + ); } } \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol index 8d16ad0cb..574a36007 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol @@ -40,7 +40,7 @@ contract PanicExitERC721PoolHandler is UnboundedLiquidationPoolHandler, Unbounde _setupLendersAndDeposits(LENDERS); _setupBorrowersAndLoans(LOANS_COUNT); - ( , , uint256 totalLoans) = _pool.loansInfo(); + uint256 totalLoans = _getLoansInfo().noOfLoans; require(totalLoans == LOANS_COUNT, "loans setup failed"); vm.warp(block.timestamp + 100_000 days); @@ -54,19 +54,20 @@ contract PanicExitERC721PoolHandler is UnboundedLiquidationPoolHandler, Unbounde uint256 borrowerIndex_, uint256 skippedTime_ ) external useTimestamps skipTime(skippedTime_) writeLogs { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, _activeBorrowers.values().length - 1); + borrowerIndex_ = constrictToRange( + borrowerIndex_, 0, _activeBorrowers.values().length - 1 + ); _actor = _borrowers[borrowerIndex_]; changePrank(_actor); - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(_actor); - if (block.timestamp > kickTime + 72 hours) { + if (block.timestamp > _getAuctionInfo(_actor).kickTime + 72 hours) { numberOfCalls['BPanicExitPoolHandler.settleDebt']++; _settleAuction(_actor, numberOfBuckets); } else { numberOfCalls['BPanicExitPoolHandler.repayLoan']++; _repayDebt(type(uint256).max); } - (, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + uint256 collateral = _getBorrowerInfo(_actor).collateral; _pullCollateral(collateral); _resetSettledAuction(_actor, borrowerIndex_); @@ -96,7 +97,8 @@ contract PanicExitERC721PoolHandler is UnboundedLiquidationPoolHandler, Unbounde if (takeAuction_) { vm.warp(block.timestamp + 61 minutes); - ( , uint256 auctionedCollateral, ) = _pool.borrowerInfo(borrower); + uint256 auctionedCollateral = _getBorrowerInfo(borrower).collateral; + _takeAuction(borrower, auctionedCollateral, _actor); _resetSettledAuction(borrower, borrowerIndex_); } @@ -149,20 +151,18 @@ contract PanicExitERC721PoolHandler is UnboundedLiquidationPoolHandler, Unbounde ) external useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BPanicExitPoolHandler.withdrawBonds']++; - kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); - address kicker = _lenders[kickerIndex_]; - - (uint256 kickerClaimable, ) = _pool.kickerInfo(kicker); + kickerIndex_ = constrictToRange(kickerIndex_, 0, LENDERS - 1); + address kicker = _lenders[kickerIndex_]; _actor = kicker; changePrank(_actor); - _withdrawBonds(kicker, kickerClaimable); + _withdrawBonds(kicker, _getKickerInfo(kicker).claimableBond); } function settleHeadAuction( uint256 skippedTime_ ) external useTimestamps skipTime(skippedTime_) writeLogs { - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); + address headAuction = _getAuctionInfo(address(0)).head; if (headAuction != address(0)) { _settleAuction(headAuction, 10); _resetSettledAuction(headAuction, 0); @@ -216,8 +216,7 @@ contract PanicExitERC721PoolHandler is UnboundedLiquidationPoolHandler, Unbounde } function _resetSettledAuction(address borrower_, uint256 borrowerIndex_) internal { - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower_); - if (kickTime == 0) { + if (_getAuctionInfo(borrower_).kickTime == 0) { if (borrowerIndex_ != 0) _activeBorrowers.remove(borrowerIndex_); } } diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol index 70b249508..daaa1b099 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol @@ -103,15 +103,19 @@ abstract contract BaseERC721PoolHandler is BaseHandler { , , , ) = _poolInfo.auctionStatus(address(_erc721Pool), borrower_); - try _erc721Pool.repayDebt(borrower_, amount_, 0, borrower_, 7388) { - + try _erc721Pool.repayDebt( + borrower_, + amount_, + 0, + borrower_, + 7388 + ) { _recordSettleBucket( borrower_, borrowerCollateralBefore, kickTimeBefore, auctionPrice ); - } catch (bytes memory err) { _ensurePoolError(err); } diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol index 80649c99f..56a6ad876 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol @@ -40,7 +40,7 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.addCollateral']++; - (uint256 lpBalanceBeforeAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); + uint256 lpBalanceBeforeAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; _ensureCollateralAmount(_actor, amount_); uint256[] memory tokenIds = new uint256[](amount_); @@ -48,15 +48,22 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } - try _erc721Pool.addCollateral(tokenIds, bucketIndex_, block.timestamp + 1 minutes) { + try _erc721Pool.addCollateral( + tokenIds, + bucketIndex_, + block.timestamp + 1 minutes + ) { // **B5**: when adding collateral: lender deposit time = timestamp of block when deposit happened lenderDepositTime[_actor][bucketIndex_] = block.timestamp; // **R5**: Exchange rates are unchanged by adding collateral token into a bucket exchangeRateShouldNotChange[bucketIndex_] = true; // Post action condition - (uint256 lpBalanceAfterAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + uint256 lpBalanceAfterAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + require( + lpBalanceAfterAction > lpBalanceBeforeAction, + "LP balance should increase" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -68,17 +75,21 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.removeCollateral']++; - (uint256 lpBalanceBeforeAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); - - try _erc721Pool.removeCollateral(amount_, bucketIndex_) { + uint256 lpBalanceBeforeAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + try _erc721Pool.removeCollateral( + amount_, + bucketIndex_ + ) { // **R6**: Exchange rates are unchanged by removing collateral token from a bucket exchangeRateShouldNotChange[bucketIndex_] = true; // Post action condition - (uint256 lpBalanceAfterAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); - + uint256 lpBalanceAfterAction = _getLenderInfo(bucketIndex_, _actor).lpBalance; + require( + lpBalanceAfterAction < lpBalanceBeforeAction, + "LP balance should decrease" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -90,14 +101,16 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.mergeCollateral']++; - try _erc721Pool.mergeOrRemoveCollateral(bucketIndexes_, amount_, 7388) { - + try _erc721Pool.mergeOrRemoveCollateral( + bucketIndexes_, + amount_, + 7388 + ) { for(uint256 i; i < bucketIndexes_.length; i++) { uint256 bucketIndex = bucketIndexes_[i]; // **R6**: Exchange rates are unchanged by removing collateral token from a bucket exchangeRateShouldNotChange[bucketIndex] = true; } - } catch (bytes memory err) { _ensurePoolError(err); } @@ -112,7 +125,7 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.pledgeCollateral']++; - (, uint256 borrowerCollateralBefore, ) = _pool.borrowerInfo(_actor); + uint256 borrowerCollateralBefore = _getBorrowerInfo(_actor).collateral; (uint256 kickTimeBefore, , , , uint256 auctionPrice, , , , ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); // **R1**: Exchange rates are unchanged by pledging collateral @@ -126,15 +139,18 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } - try _erc721Pool.drawDebt(_actor, 0, 0, tokenIds) { - + try _erc721Pool.drawDebt( + _actor, + 0, + 0, + tokenIds + ) { _recordSettleBucket( _actor, borrowerCollateralBefore, kickTimeBefore, auctionPrice ); - } catch (bytes memory err) { _ensurePoolError(err); } @@ -150,8 +166,13 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, exchangeRateShouldNotChange[bucketIndex] = true; } - try _erc721Pool.repayDebt(_actor, 0, amount_, _actor, 7388) { - + try _erc721Pool.repayDebt( + _actor, + 0, + amount_, + _actor, + 7388 + ) { } catch (bytes memory err) { _ensurePoolError(err); } @@ -175,7 +196,10 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, if (bucket > LENDER_MAX_BUCKET_INDEX) return; // calculates collateral required to borrow quote tokens, added 1 for roundup such that 0.8 NFT will become 1 - uint256 collateralToPledge = Maths.wdiv(Maths.wmul(COLLATERALIZATION_FACTOR, amount_), price) / 1e18 + 1; + uint256 collateralToPledge = Maths.wdiv( + Maths.wmul(COLLATERALIZATION_FACTOR, amount_), + price + ) / 1e18 + 1; _ensureCollateralAmount(_actor, collateralToPledge); uint256[] memory tokenIds = new uint256[](collateralToPledge); @@ -185,10 +209,15 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, (uint256 interestRate, ) = _erc721Pool.interestRateInfo(); - (, uint256 borrowerCollateralBefore, ) = _pool.borrowerInfo(_actor); + uint256 borrowerCollateralBefore = _getBorrowerInfo(_actor).collateral; (uint256 kickTimeBefore, , , , uint256 auctionPrice, , , , ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); - try _erc721Pool.drawDebt(_actor, amount_, 7388, tokenIds) { + try _erc721Pool.drawDebt( + _actor, + amount_, + 7388, + tokenIds + ) { // amount is rounded by pool to token scale amount_ = _roundToScale(amount_, _pool.quoteTokenScale()); @@ -203,7 +232,6 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, kickTimeBefore, auctionPrice ); - } catch (bytes memory err) { _ensurePoolError(err); } @@ -214,21 +242,25 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.repayDebt']++; - (uint256 borrowerDebt, uint256 borrowerCollateralBefore, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + BorrowerInfo memory borrowerInfoBeforeAction = _getBorrowerInfo(_actor); (uint256 kickTimeBefore, , , , uint256 auctionPrice, , , , ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); // ensure actor always has amount of quote to repay - _ensureQuoteAmount(_actor, borrowerDebt + 10 * 1e18); - - try _erc721Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { - + _ensureQuoteAmount(_actor, borrowerInfoBeforeAction.debt + 10 * 1e18); + + try _erc721Pool.repayDebt( + _actor, + amountToRepay_, + 0, + _actor, + 7388 + ) { _recordSettleBucket( _actor, - borrowerCollateralBefore, + borrowerInfoBeforeAction.collateral, kickTimeBefore, auctionPrice ); - } catch (bytes memory err) { _ensurePoolError(err); } diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/BasePositionPoolHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/BasePositionPoolHandler.sol index e54984c6a..116445f8a 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/BasePositionPoolHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/BasePositionPoolHandler.sol @@ -29,23 +29,32 @@ abstract contract BasePositionPoolHandler is UnboundedPositionPoolHandler { uint256 bucketIndex = indexes_[i]; // ensure actor has a position - (uint256 lpBalanceBefore,) = _pool.lenderInfo(bucketIndex, _actor); - // add quote token if they don't have a position - if (lpBalanceBefore == 0) { + if (_getLenderInfo(bucketIndex, _actor).lpBalance == 0) { // bound amount - uint256 boundedAmount = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); + uint256 boundedAmount = constrictToRange( + amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT + ); _ensureQuoteAmount(_actor, boundedAmount); - try _pool.addQuoteToken(boundedAmount, bucketIndex, block.timestamp + 1 minutes) { + + try _pool.addQuoteToken( + boundedAmount, + bucketIndex, + block.timestamp + 1 minutes + ) { } catch (bytes memory err) { _ensurePoolError(err); } } - (lpBalances[i], ) = _pool.lenderInfo(bucketIndex, _actor); + lpBalances[i] = _getLenderInfo(bucketIndex, _actor).lpBalance; } - _pool.increaseLPAllowance(address(_positionManager), indexes_, lpBalances); + _pool.increaseLPAllowance( + address(_positionManager), + indexes_, + lpBalances + ); // mint position NFT tokenId_ = _mint(); @@ -118,14 +127,19 @@ abstract contract BasePositionPoolHandler is UnboundedPositionPoolHandler { uint256 amountToAdd_ ) internal returns (uint256[] memory indexes_) { // ensure actor has a position - (uint256 lpBalanceBefore,) = _pool.lenderInfo(bucketIndex_, _actor); - // add quote token if they don't have a position - if (lpBalanceBefore == 0) { + if (_getLenderInfo(bucketIndex_, _actor).lpBalance == 0) { // bound amount - uint256 boundedAmount = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); + uint256 boundedAmount = constrictToRange( + amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT + ); _ensureQuoteAmount(_actor, boundedAmount); - try _pool.addQuoteToken(boundedAmount, bucketIndex_, block.timestamp + 1 minutes) { + + try _pool.addQuoteToken( + boundedAmount, + bucketIndex_, + block.timestamp + 1 minutes + ) { } catch (bytes memory err) { _ensurePoolError(err); } @@ -136,11 +150,19 @@ abstract contract BasePositionPoolHandler is UnboundedPositionPoolHandler { uint256[] memory lpBalances = new uint256[](1); - (lpBalances[0], ) = _pool.lenderInfo(bucketIndex_, _actor); - _pool.increaseLPAllowance(address(_positionManager), indexes_, lpBalances); + lpBalances[0] = _getLenderInfo(bucketIndex_, _actor).lpBalance; + + _pool.increaseLPAllowance( + address(_positionManager), + indexes_, + lpBalances + ); } - function getRandomElements(uint256 noOfElements, uint256[] memory arr) internal returns (uint256[] memory randomBuckets_) { + function getRandomElements( + uint256 noOfElements, + uint256[] memory arr + ) internal returns (uint256[] memory randomBuckets_) { randomBuckets_ = new uint256[](noOfElements); for (uint256 i = 0; i < noOfElements; i++) { diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/BucketBankruptcyERC20PoolRewardsHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/BucketBankruptcyERC20PoolRewardsHandler.sol index 318b67807..73ad8969d 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/BucketBankruptcyERC20PoolRewardsHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/BucketBankruptcyERC20PoolRewardsHandler.sol @@ -66,7 +66,7 @@ contract BucketBankruptcyERC20PoolRewardsHandler is UnboundedBasicERC20PoolHandl _setupLendersAndDeposits(LENDERS); _setupBorrowersAndLoans(LOANS_COUNT); - ( , , uint256 totalLoans) = _pool.loansInfo(); + uint256 totalLoans = _getLoansInfo().noOfLoans; require(totalLoans == LOANS_COUNT, "loans setup failed"); vm.warp(block.timestamp + 1_000 days); @@ -191,10 +191,11 @@ contract BucketBankruptcyERC20PoolRewardsHandler is UnboundedBasicERC20PoolHandl boundedAmount_ = constrictToRange(amountToMove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); // ensure actor has LP to move - (uint256 lpBalance, ) = _pool.lenderInfo(fromIndex_, _actor); - if (lpBalance == 0) _addQuoteToken(boundedAmount_, toIndex_); + if (_getLenderInfo(fromIndex_, _actor).lpBalance == 0) { + _addQuoteToken(boundedAmount_, toIndex_); // TODO: shouldn't be fromIndex_ instead toIndex_ ? + } - (uint256 lps, ) = _pool.lenderInfo(fromIndex_, _actor); + uint256 lps = _getLenderInfo(fromIndex_, _actor).lpBalance; // restrict amount to move by available deposit inside bucket uint256 availableDeposit = _poolInfo.lpToQuoteTokens(address(_pool), lps, fromIndex_); boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); @@ -255,8 +256,7 @@ contract BucketBankruptcyERC20PoolRewardsHandler is UnboundedBasicERC20PoolHandl /*******************************/ function _resetSettledAuction(address borrower_, uint256 borrowerIndex_) internal { - (,,, uint256 kickTime,,,,,,) = _pool.auctionInfo(borrower_); - if (kickTime == 0) { + if (_getAuctionInfo(borrower_).kickTime == 0) { if (borrowerIndex_ != 0) _activeBorrowers.remove(borrowerIndex_); } } diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/PositionPoolHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/PositionPoolHandler.sol index 1d7232e42..9990e917f 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/PositionPoolHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/PositionPoolHandler.sol @@ -20,6 +20,7 @@ abstract contract PositionPoolHandler is BasePositionPoolHandler { uint256 skippedTime_ ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) useRandomPool(skippedTime_) writeLogs writePositionLogs { numberOfCalls['BPositionHandler.memorialize']++; + // Pre action // (uint256 tokenId, uint256[] memory indexes) = _preMemorializePositions(noOfBuckets_, amountToAdd_); @@ -34,6 +35,7 @@ abstract contract PositionPoolHandler is BasePositionPoolHandler { uint256 skippedTime_ ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) useRandomPool(skippedTime_) writeLogs writePositionLogs { numberOfCalls['BPositionHandler.redeem']++; + // Pre action // (uint256 tokenId, uint256[] memory indexes) = _preRedeemPositions(noOfBuckets_, amountToAdd_); @@ -60,7 +62,8 @@ abstract contract PositionPoolHandler is BasePositionPoolHandler { uint256 skippedTime_, uint256 amountToAdd_ ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) useRandomPool(skippedTime_) writeLogs writePositionLogs { - numberOfCalls['BPositionHandler.burn']++; + numberOfCalls['BPositionHandler.burn']++; + // Pre action // (uint256 tokenId_) = _preBurn(_lenderBucketIndex, amountToAdd_); @@ -74,7 +77,8 @@ abstract contract PositionPoolHandler is BasePositionPoolHandler { uint256 amountToMove_, uint256 toIndex_ ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) useRandomPool(skippedTime_) writeLogs writePositionLogs { - numberOfCalls['BPositionHandler.moveLiquidity']++; + numberOfCalls['BPositionHandler.moveLiquidity']++; + // Pre action // ( uint256 tokenId, @@ -107,7 +111,8 @@ abstract contract PositionPoolHandler is BasePositionPoolHandler { uint256 skippedTime_, uint256 amountToAdd_ ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) writeLogs writePositionLogs { - numberOfCalls['BPositionHandler.transferPosition']++; + numberOfCalls['BPositionHandler.transferPosition']++; + // Pre action // (uint256 tokenId_, uint256[] memory indexes) = _getNFTPosition(_lenderBucketIndex, amountToAdd_); diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/RewardsPoolHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/RewardsPoolHandler.sol index e1c991aa9..4d55d1a77 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/RewardsPoolHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/RewardsPoolHandler.sol @@ -22,6 +22,7 @@ abstract contract RewardsPoolHandler is BaseRewardsPoolHandler, PositionPoolHand uint256 skippedTime_ ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) writeLogs writePositionLogs writeRewardsLogs { numberOfCalls['BRewardsHandler.stake']++; + // Pre action (uint256 tokenId, uint256[] memory indexes) = _preStake(_lenderBucketIndex, amountToAdd_); @@ -40,6 +41,7 @@ abstract contract RewardsPoolHandler is BaseRewardsPoolHandler, PositionPoolHand uint256 numberOfEpochs_ ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) writeLogs writePositionLogs writeRewardsLogs { numberOfCalls['BRewardsHandler.unstake']++; + // Pre action (uint256 tokenId, uint256[] memory indexes) = _preUnstake( _lenderBucketIndex, diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedPositionPoolHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedPositionPoolHandler.sol index 0e8b58618..68a50d684 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedPositionPoolHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedPositionPoolHandler.sol @@ -42,18 +42,22 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, for(uint256 i = 0; i < indexes_.length; i++) { // store vals pre action to check after memorializing: - (uint256 poolPreActionActorLps, uint256 actorDepositTime) = _pool.lenderInfo(indexes_[i], address(_actor)); - (uint256 poolPreActionPosManLps, uint256 posManDepositTime) = _pool.lenderInfo(indexes_[i], address(_positionManager)); + LenderInfo memory lenderInfoBeforeAction = _getLenderInfo(indexes_[i], address(_actor)); + LenderInfo memory positionManagerInfoBeforeAction = _getLenderInfo(indexes_[i], address(_positionManager)); - actorLpsBefore[address(_pool)][indexes_[i]] = poolPreActionActorLps; - posManLpsBefore[address(_pool)][indexes_[i]] = poolPreActionPosManLps; + actorLpsBefore[address(_pool)][indexes_[i]] = lenderInfoBeforeAction.lpBalance; + posManLpsBefore[address(_pool)][indexes_[i]] = positionManagerInfoBeforeAction.lpBalance; // positionManager is assigned the most recent depositTime - bucketIndexToDepositTime[address(_pool)][indexes_[i]] = (actorDepositTime >= posManDepositTime) ? actorDepositTime : posManDepositTime; + bucketIndexToDepositTime[address(_pool)][indexes_[i]] = (lenderInfoBeforeAction.depositTime >= positionManagerInfoBeforeAction.depositTime) ? + lenderInfoBeforeAction.depositTime : positionManagerInfoBeforeAction.depositTime; } - try _positionManager.memorializePositions(address(_pool), tokenId_, indexes_) { - + try _positionManager.memorializePositions( + address(_pool), + tokenId_, + indexes_ + ) { // track created positions for ( uint256 i = 0; i < indexes_.length; i++) { uint256 bucketIndex = indexes_[i]; @@ -64,19 +68,28 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, // info used to tearDown buckets bucketIndexesByTokenId[tokenId_].add(bucketIndex); - (uint256 poolLps, uint256 poolDepositTime) = _pool.lenderInfo(bucketIndex, address(_positionManager)); + LenderInfo memory positionManagerInfoBeforeAction = _getLenderInfo(indexes_[i], address(_positionManager)); - require(poolDepositTime == bucketIndexToDepositTime[address(_pool)][bucketIndex], - "PM7: positionManager depositTime does not match most recent depositTime"); + require( + positionManagerInfoBeforeAction.depositTime == bucketIndexToDepositTime[address(_pool)][bucketIndex], + "PM7: positionManager depositTime does not match most recent depositTime" + ); // assert that the LP that now exists in the pool contract matches the amount added by the actor - require(poolLps == actorLpsBefore[address(_pool)][bucketIndex] + posManLpsBefore[address(_pool)][bucketIndex], - "PM7: pool contract lps do not match amount added by actor"); + require( + positionManagerInfoBeforeAction.lpBalance == actorLpsBefore[address(_pool)][bucketIndex] + posManLpsBefore[address(_pool)][bucketIndex], + "PM7: pool contract lps do not match amount added by actor" + ); // assert that the positionManager LP balance of the actor has increased - (uint256 posLps,) = _positionManager.getPositionInfo(tokenId_, bucketIndex); - require(posLps == actorLpsBefore[address(_pool)][bucketIndex], - "PM7: positionManager lps do not match amount added by actor"); + ( + uint256 posLps, + ) = _positionManager.getPositionInfo(tokenId_, bucketIndex); + + require( + posLps == actorLpsBefore[address(_pool)][bucketIndex], + "PM7: positionManager lps do not match amount added by actor" + ); delete actorLpsBefore[address(_pool)][bucketIndex]; delete posManLpsBefore[address(_pool)][bucketIndex]; @@ -85,7 +98,6 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, // info used track actors positions tokenIdsByActor[address(_actor)].add(tokenId_); - } catch (bytes memory err) { // cleanup buckets so they don't interfere with future calls @@ -103,22 +115,33 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, function _mint() internal returns (uint256 tokenIdResult) { numberOfCalls['UBPositionHandler.mint']++; - try _positionManager.mint(address(_pool), _actor, _poolHash) returns (uint256 tokenId) { - + try _positionManager.mint( + address(_pool), + _actor, + _poolHash + ) returns (uint256 tokenId) { tokenIdResult = tokenId; // Post Action Checks // // assert that the minter is the owner - require(_positionManager.ownerOf(tokenId) == _actor, "PM4: minter is not owner"); + require( + _positionManager.ownerOf(tokenId) == _actor, + "PM4: minter is not owner" + ); // assert that poolKey is returns correct pool address address poolAddress = _positionManager.poolKey(tokenId); - require(poolAddress == address(_pool), "PM4: poolKey does not match pool address"); + require( + poolAddress == address(_pool), + "PM4: poolKey does not match pool address" + ); // assert that no positions are associated with this tokenId uint256[] memory posIndexes = _positionManager.getPositionIndexes(tokenId); - require(posIndexes.length == 0, "PM4: positions are associated with tokenId"); - + require( + posIndexes.length == 0, + "PM4: positions are associated with tokenId" + ); } catch (bytes memory err) { _ensurePositionsManagerError(err); } @@ -134,17 +157,15 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, uint256 totalPositionIndexes = _positionManager.getPositionIndexes(tokenId_).length; for (uint256 i = 0; i < indexes_.length; i++) { - - (uint256 poolPreActionActorLps,) = _pool.lenderInfo(indexes_[i], preActionOwner); - (uint256 poolPreActionPosManLps,) = _pool.lenderInfo(indexes_[i], address(_positionManager)); - // store vals in mappings to check lps - actorLpsBefore[address(_pool)][indexes_[i]] = poolPreActionActorLps; - posManLpsBefore[address(_pool)][indexes_[i]] = poolPreActionPosManLps; + actorLpsBefore[address(_pool)][indexes_[i]] = _getLenderInfo(indexes_[i], preActionOwner).lpBalance; + posManLpsBefore[address(_pool)][indexes_[i]] = _getLenderInfo(indexes_[i], address(_positionManager)).lpBalance; } - try _positionManager.redeemPositions(address(_pool), tokenId_, indexes_) { - + try _positionManager.redeemPositions( + address(_pool), + tokenId_, + indexes_) { // remove tracked positions for ( uint256 i = 0; i < indexes_.length; i++) { uint256 bucketIndex = indexes_[i]; @@ -153,27 +174,40 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, // if no other positions exist for this bucketIndex, remove from bucketIndexesWithPosition if (getTokenIdsByBucketIndex(address(_pool), bucketIndex).length == 0) { - bucketIndexesWithPosition[address(_pool)].remove(bucketIndex); } - (uint256 poolActorLps,) = _pool.lenderInfo(bucketIndex, preActionOwner); - (uint256 poolPosLps,) = _pool.lenderInfo(bucketIndex, address(_positionManager)); + uint256 poolActorLps = _getLenderInfo(bucketIndex, preActionOwner).lpBalance; + uint256 poolPosLps = _getLenderInfo(bucketIndex, address(_positionManager)).lpBalance; // assert PositionsMan LP in pool matches the amount redeemed by actor // positionMan has now == positionMan pre - actor's LP change - require(poolPosLps == posManLpsBefore[address(_pool)][bucketIndex] - (poolActorLps - actorLpsBefore[address(_pool)][bucketIndex]), - "PM8: positionManager's pool contract lps do not match amount redeemed by actor"); + require( + poolPosLps == posManLpsBefore[address(_pool)][bucketIndex] - (poolActorLps - actorLpsBefore[address(_pool)][bucketIndex]), + "PM8: positionManager's pool contract lps do not match amount redeemed by actor" + ); // assert actor LP in pool matches amount removed from the posMan's position // assert actor LP in pool = what actor LP had pre + what LP positionManager redeemed to actor - require(poolActorLps == actorLpsBefore[address(_pool)][bucketIndex] + (posManLpsBefore[address(_pool)][bucketIndex] - poolPosLps), - "PM8: actor's pool contract lps do not match amount redeemed by actor"); + require( + poolActorLps == actorLpsBefore[address(_pool)][bucketIndex] + (posManLpsBefore[address(_pool)][bucketIndex] - poolPosLps), + "PM8: actor's pool contract lps do not match amount redeemed by actor" + ); // assert that the underlying LP balance in PositionManager is zero - (uint256 posLps, uint256 posDepositTime) = _positionManager.getPositionInfo(tokenId_, bucketIndex); - require(posLps == 0, "PM8: tokenId has lps after redemption"); - require(posDepositTime == 0, "PM8: tokenId has depositTime after redemption"); + ( + uint256 posLps, + uint256 posDepositTime + ) = _positionManager.getPositionInfo(tokenId_, bucketIndex); + + require( + posLps == 0, + "PM8: tokenId has lps after redemption" + ); + require( + posDepositTime == 0, + "PM8: tokenId has depositTime after redemption" + ); // delete mappings for reuse delete actorLpsBefore[address(_pool)][bucketIndex]; @@ -183,27 +217,31 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, } // assert that the minter is still the owner - require(_positionManager.ownerOf(tokenId_) == preActionOwner, - 'PM8: previous owner is no longer owner on redemption'); + require( + _positionManager.ownerOf(tokenId_) == preActionOwner, + 'PM8: previous owner is no longer owner on redemption' + ); // assert that poolKey address matches pool address - require(_positionManager.poolKey(tokenId_) == address(_pool), - 'PM8: poolKey has changed on redemption'); + require( + _positionManager.poolKey(tokenId_) == address(_pool), + 'PM8: poolKey has changed on redemption' + ); // if all positions are redeemed if (totalPositionIndexes == indexes_.length) { - // assert that no positions are associated with this tokenId uint256[] memory posIndexes = _positionManager.getPositionIndexes(tokenId_); - require(posIndexes.length == 0, 'PM8: positions still exist after redemption'); + require( + posIndexes.length == 0, + "PM8: positions still exist after redemption" + ); // info for tear down delete bucketIndexesByTokenId[tokenId_]; tokenIdsByActor[address(_actor)].remove(tokenId_); } - } catch (bytes memory err) { - for ( uint256 i = 0; i < indexes_.length; i++) { uint256 bucketIndex = indexes_[i]; // delete mappings for reuse @@ -220,18 +258,13 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, uint256 index ) internal view returns (uint256 quoteAtIndex_) { // retrieve info of bucket from pool - ( - uint256 bucketLP, - uint256 bucketCollateral, - , - uint256 bucketDeposit, - ) = _pool.bucketInfo(index); + BucketInfo memory bucketInfo = _getBucketInfo(index); // calculate the max amount of quote tokens that can be moved, given the tracked LP quoteAtIndex_ = _lpToQuoteToken( - bucketLP, - bucketCollateral, - bucketDeposit, + bucketInfo.lpBalance, + bucketInfo.collateral, + bucketInfo.deposit, lp, _priceAt(index) ); @@ -255,12 +288,13 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, (uint256 preActionToLps,) = _positionManager.getPositionInfo(tokenId_, toIndex_); uint256 preActionToIndexQuote = _getQuoteAtIndex(preActionToLps, toIndex_); - /** - * @notice Struct holding parameters for moving the liquidity of a position. - */ - - try _positionManager.moveLiquidity(address(_pool), tokenId_, fromIndex_, toIndex_, block.timestamp + 30) { - + try _positionManager.moveLiquidity( + address(_pool), + tokenId_, + fromIndex_, + toIndex_, + block.timestamp + 30 + ) { bucketIndexesByTokenId[tokenId_].add(toIndex_); bucketIndexesByTokenId[tokenId_].remove(fromIndex_); @@ -278,15 +312,35 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, tokenIdsByBucketIndex[address(_pool)][toIndex_].add(tokenId_); // assert that fromIndex LP and deposit time are both zero - (uint256 fromLps, uint256 fromDepositTime) = _positionManager.getPositionInfo(tokenId_, fromIndex_); - require(fromLps == 0, "PM6: from bucket still has LPs after move"); - require(fromDepositTime == 0, "PM6: from bucket still has deposit time after move"); + ( + uint256 fromLps, + uint256 fromDepositTime + ) = _positionManager.getPositionInfo(tokenId_, fromIndex_); + + require( + fromLps == 0, + "PM6: from bucket still has LPs after move" + ); + require( + fromDepositTime == 0, + "PM6: from bucket still has deposit time after move" + ); // assert that toIndex LP is increased and deposit time matches positionManagers depositTime pre action - (uint256 toLps, uint256 toDepositTime) = _positionManager.getPositionInfo(tokenId_, toIndex_); - (,uint256 postActionDepositTime)= _pool.lenderInfo(toIndex_, address(_positionManager)); - require(toLps >= preActionToLps, "PM6: to bucket lps have not increased"); // difficult to estimate LPS, assert that it is greater than - require(toDepositTime == postActionDepositTime, "PM6: to bucket deposit time does not match positionManager"); + uint256 postActionDepositTime= _getLenderInfo(toIndex_, address(_positionManager)).depositTime; + ( + uint256 toLps, + uint256 toDepositTime + ) = _positionManager.getPositionInfo(tokenId_, toIndex_); + + require( + toLps >= preActionToLps, + "PM6: to bucket lps have not increased" + ); // difficult to estimate LPS, assert that it is greater than + require( + toDepositTime == postActionDepositTime, + "PM6: to bucket deposit time does not match positionManager" + ); // get post action QT represented in positionManager for tokenID uint256 postActionFromIndexQuote = _getQuoteAtIndex(fromLps, fromIndex_); @@ -300,7 +354,6 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, 1, "PM6: positiionManager QT balance has increased by `1` margin" ); - } catch (bytes memory err) { _ensurePositionsManagerError(err); } @@ -310,20 +363,33 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, uint256 tokenId_ ) internal { numberOfCalls['UBPositionHandler.burn']++; - try _positionManager.burn(address(_pool), tokenId_) { + + try _positionManager.burn( + address(_pool), + tokenId_ + ) { // Post Action Checks // // should revert if token id is burned vm.expectRevert("ERC721: invalid token ID"); - require(_positionManager.ownerOf(tokenId_) == address(0), "PM5: ownership is not zero address"); + + require( + _positionManager.ownerOf(tokenId_) == address(0), + "PM5: ownership is not zero address" + ); // assert that poolKey is returns zero address address poolAddress = _positionManager.poolKey(tokenId_); - require(poolAddress == address(0), "PM5: poolKey has not been reset on burn"); + require( + poolAddress == address(0), + "PM5: poolKey has not been reset on burn" + ); // assert that no positions are associated with this tokenId uint256[] memory posIndexes = _positionManager.getPositionIndexes(tokenId_); - require(posIndexes.length == 0, "PM5: positions still exist after burn"); - + require( + posIndexes.length == 0, + "PM5: positions still exist after burn" + ); } catch (bytes memory err) { _ensurePositionsManagerError(err); } @@ -334,14 +400,20 @@ abstract contract UnboundedPositionPoolHandler is UnboundedBasePositionHandler, uint256 tokenId_ ) internal { numberOfCalls['UBPositionHandler.transferPosition']++; - try _positionManager.transferFrom(_actor, receiver_, tokenId_) { + try _positionManager.transferFrom( + _actor, + receiver_, + tokenId_ + ) { // actor should loses ownership, receiver gains it tokenIdsByActor[address(_actor)].remove(tokenId_); tokenIdsByActor[receiver_].add(tokenId_); - require(_positionManager.ownerOf(tokenId_) == receiver_, "new NFT owner should be receiver"); - + require( + _positionManager.ownerOf(tokenId_) == receiver_, + "new NFT owner should be receiver" + ); } catch (bytes memory err) { _ensurePositionsManagerError(err); } diff --git a/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedRewardsPoolHandler.sol b/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedRewardsPoolHandler.sol index 6a1f68711..b94d54588 100644 --- a/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedRewardsPoolHandler.sol +++ b/tests/forge/invariants/PositionsAndRewards/handlers/unbounded/UnboundedRewardsPoolHandler.sol @@ -37,9 +37,14 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBRewardsHandler.stake']++; - require(_positionManager.ownerOf(tokenId_) == address(_actor), "the actor calling `stake()` is not the owner"); + require( + _positionManager.ownerOf(tokenId_) == address(_actor), + "the actor calling `stake()` is not the owner" + ); - try _rewardsManager.stake(tokenId_) { + try _rewardsManager.stake( + tokenId_ + ) { // actor should loses ownership, positionManager gains it tokenIdsByActor[address(_rewardsManager)].add(tokenId_); tokenIdsByActor[address(_actor)].remove(tokenId_); @@ -47,8 +52,10 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { // staked position is tracked stakedTokenIdsByActor[address(_actor)].add(tokenId_); - require(_positionManager.ownerOf(tokenId_) == address(_rewardsManager), "RW5: owner should be rewardsManager"); - + require( + _positionManager.ownerOf(tokenId_) == address(_rewardsManager), + "RW5: owner should be rewardsManager" + ); } catch (bytes memory err) { _ensureRewardsManagerError(err); } @@ -81,8 +88,9 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { totalRewardsEarnedPreAction += _rewardsManager.getRewardsClaimed(address(_pool), epoch) + _rewardsManager.getUpdateRewardsClaimed(address(_pool), epoch); } - try _rewardsManager.unstake(tokenId_) { - + try _rewardsManager.unstake( + tokenId_ + ) { // actor should receive tokenId, positionManager loses ownership tokenIdsByActor[address(_actor)].add(tokenId_); tokenIdsByActor[address(_rewardsManager)].remove(tokenId_); @@ -99,13 +107,17 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { // if lastClaimed is the same as epoch staked isEpochClaimed will return false if (epoch != preActionLastClaimedEpoch) { - require(_rewardsManager.isEpochClaimed(tokenId_, epoch) == true, - "RW6: epoch after claim rewards is not claimed"); + require( + _rewardsManager.isEpochClaimed(tokenId_, epoch) == true, + "RW6: epoch after claim rewards is not claimed" + ); } if (rewardsEarnedInEpochPreAction[epoch] > 0) { - require(rewardsEarnedInEpochPreAction[epoch] == _rewardsManager.getRewardsClaimed(address(_pool), epoch), - "RW10: staker has claimed rewards from the same epoch twice"); + require( + rewardsEarnedInEpochPreAction[epoch] == _rewardsManager.getRewardsClaimed(address(_pool), epoch), + "RW10: staker has claimed rewards from the same epoch twice" + ); } // total rewards earned across all actors in epoch post action @@ -116,19 +128,26 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { updateRewardsClaimedPerEpoch[address(_pool)][epoch] = _rewardsManager.getUpdateRewardsClaimed(address(_pool), epoch); } - require(_positionManager.ownerOf(tokenId_) == address(_actor), - "RW5: caller of unstake is not owner of NFT"); + require( + _positionManager.ownerOf(tokenId_) == address(_actor), + "RW5: caller of unstake is not owner of NFT" + ); - require(actorAjnaGain <= totalRewardsEarnedPostAction - totalRewardsEarnedPreAction, - "RW7: actor's total claimed is greater than rewards earned"); + require( + actorAjnaGain <= totalRewardsEarnedPostAction - totalRewardsEarnedPreAction, + "RW7: actor's total claimed is greater than rewards earned" + ); - require(actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), - "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor"); + require( + actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), + "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor" + ); (address owner, address pool, uint256 lastClaimedEpoch) = _rewardsManager.getStakeInfo(tokenId_); - require(owner == address(0) && pool == address(0) && lastClaimedEpoch == 0, - "RW9: stake info is not reset after unstake"); - + require( + owner == address(0) && pool == address(0) && lastClaimedEpoch == 0, + "RW9: stake info is not reset after unstake" + ); } catch (bytes memory err) { _ensureRewardsManagerError(err); } @@ -150,8 +169,9 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { totalRewardsEarnedPreAction += _rewardsManager.getRewardsClaimed(address(_pool), epoch) + _rewardsManager.getUpdateRewardsClaimed(address(_pool), epoch); } - try _rewardsManager.emergencyUnstake(tokenId_) { - + try _rewardsManager.emergencyUnstake( + tokenId_ + ) { // actor should receive tokenId, positionManager loses ownership tokenIdsByActor[address(_actor)].add(tokenId_); tokenIdsByActor[address(_rewardsManager)].remove(tokenId_); @@ -167,18 +187,25 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { totalRewardsEarnedPostAction += _rewardsManager.getRewardsClaimed(address(_pool), epoch) + _rewardsManager.getUpdateRewardsClaimed(address(_pool), epoch); } - require(totalRewardsEarnedPreAction == totalRewardsEarnedPostAction, - "rewards were earned on emergency unstake"); - - require(_positionManager.ownerOf(tokenId_) == address(_actor), - "RW5: caller of unstake is not owner of NFT"); - - require(contractAjnaBalanceBeforeClaim == _ajna.balanceOf(address(_rewardsManager)), - "RW8: ajna balance of rewardsManager changed"); - - require(actorAjnaBalanceBeforeClaim == _ajna.balanceOf(_actor), - "RW8: ajna balance of actor changed"); - + require( + totalRewardsEarnedPreAction == totalRewardsEarnedPostAction, + "rewards were earned on emergency unstake" + ); + + require( + _positionManager.ownerOf(tokenId_) == address(_actor), + "RW5: caller of unstake is not owner of NFT" + ); + + require( + contractAjnaBalanceBeforeClaim == _ajna.balanceOf(address(_rewardsManager)), + "RW8: ajna balance of rewardsManager changed" + ); + + require( + actorAjnaBalanceBeforeClaim == _ajna.balanceOf(_actor), + "RW8: ajna balance of actor changed" + ); } catch (bytes memory err) { _ensureRewardsManagerError(err); } @@ -196,17 +223,23 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { // total the rewards earned pre action uint256 totalRewardsEarnedPreAction = _rewardsManager.getUpdateRewardsClaimed(address(_pool), _pool.currentBurnEpoch()); - try _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), keccak256("ERC20_NON_SUBSET_HASH"), indexes_) { - + try _rewardsManager.updateBucketExchangeRatesAndClaim( + address(_pool), + keccak256("ERC20_NON_SUBSET_HASH"), + indexes_ + ) { // balance changes uint256 actorAjnaGain = _ajna.balanceOf(_actor) - actorAjnaBalanceBeforeClaim; - require(actorAjnaGain <= _rewardsManager.getUpdateRewardsClaimed(address(_pool), _pool.currentBurnEpoch()) - totalRewardsEarnedPreAction, - "RW7: actor's total claimed is greater than update rewards earned"); - - require(actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), - "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor"); + require( + actorAjnaGain <= _rewardsManager.getUpdateRewardsClaimed(address(_pool), _pool.currentBurnEpoch()) - totalRewardsEarnedPreAction, + "RW7: actor's total claimed is greater than update rewards earned" + ); + require( + actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), + "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor" + ); } catch (bytes memory err) { _ensureRewardsManagerError(err); } @@ -238,8 +271,11 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { totalRewardsEarnedPreAction += _rewardsManager.getRewardsClaimed(address(_pool), epoch) + _rewardsManager.getUpdateRewardsClaimed(address(_pool), epoch); } - try _rewardsManager.claimRewards(tokenId_, epoch_, 0) { - + try _rewardsManager.claimRewards( + tokenId_, + epoch_, + 0 + ) { // balance changes uint256 actorAjnaGain = _ajna.balanceOf(_actor) - actorAjnaBalanceBeforeClaim; @@ -249,13 +285,17 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { // if lastClaimed is the same as epoch staked isEpochClaimed will return false if (epoch != preActionLastClaimedEpoch) { - require(_rewardsManager.isEpochClaimed(tokenId_, epoch) == true, - "RW6: epoch after claim rewards is not claimed"); + require( + _rewardsManager.isEpochClaimed(tokenId_, epoch) == true, + "RW6: epoch after claim rewards is not claimed" + ); } if (rewardsEarnedInEpochPreAction[epoch] > 0) { - require(rewardsEarnedInEpochPreAction[epoch] == _rewardsManager.getRewardsClaimed(address(_pool), epoch), - "RW10: staker has claimed rewards from the same epoch twice"); + require( + rewardsEarnedInEpochPreAction[epoch] == _rewardsManager.getRewardsClaimed(address(_pool), epoch), + "RW10: staker has claimed rewards from the same epoch twice" + ); } // total rewards earned across all actors in epoch post action @@ -267,15 +307,20 @@ abstract contract UnboundedRewardsPoolHandler is UnboundedPositionPoolHandler { } (, , uint256 lastClaimedEpoch) = _rewardsManager.getStakeInfo(tokenId_); - require(lastClaimedEpoch == _pool.currentBurnEpoch(), - "RW6: lastClaimed is not current epoch"); - - require(actorAjnaGain <= totalRewardsEarnedPostAction - totalRewardsEarnedPreAction, - "RW7: actor's total claimed is greater than rewards earned"); - - require(actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), - "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor"); - + require( + lastClaimedEpoch == _pool.currentBurnEpoch(), + "RW6: lastClaimed is not current epoch" + ); + + require( + actorAjnaGain <= totalRewardsEarnedPostAction - totalRewardsEarnedPreAction, + "RW7: actor's total claimed is greater than rewards earned" + ); + + require( + actorAjnaGain == contractAjnaBalanceBeforeClaim - _ajna.balanceOf(address(_rewardsManager)), + "RW8: ajna deducted from rewardsManager doesn't equal ajna gained by actor" + ); } catch (bytes memory err) { _ensureRewardsManagerError(err); } diff --git a/tests/forge/invariants/base/ReserveInvariants.t.sol b/tests/forge/invariants/base/ReserveInvariants.t.sol index f1314e013..efa9754d9 100644 --- a/tests/forge/invariants/base/ReserveInvariants.t.sol +++ b/tests/forge/invariants/base/ReserveInvariants.t.sol @@ -14,21 +14,23 @@ abstract contract ReserveInvariants is LiquidationInvariants { function invariant_reserves() public useCurrentTimestamp { - uint256 previousReserves = IBaseHandler(_handler).previousReserves(); - uint256 increaseInReserves = IBaseHandler(_handler).increaseInReserves(); - uint256 decreaseInReserves = IBaseHandler(_handler).decreaseInReserves(); + uint256 previousReserves = IBaseHandler(_handler).previousReserves(); + uint256 increaseInReserves = IBaseHandler(_handler).increaseInReserves(); + uint256 decreaseInReserves = IBaseHandler(_handler).decreaseInReserves(); + uint256 reservesErrorMargin = Maths.max(IBaseHandler(_handler).reservesErrorMargin(), 1e13); (uint256 currentReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); console.log("Previous Reserves -->", previousReserves); console.log("Increase in Reserves -->", increaseInReserves); console.log("Decrease in Reserves -->", decreaseInReserves); console.log("Current Reserves -->", currentReserves); + console.log("Reserves Error margin -->", reservesErrorMargin); console.log("Required Reserves -->", previousReserves + increaseInReserves - decreaseInReserves); requireWithinDiff( currentReserves, previousReserves + increaseInReserves - decreaseInReserves, - Maths.max(_pool.quoteTokenScale(), 1e13), + Maths.max(_pool.quoteTokenScale(), reservesErrorMargin), "Incorrect Reserves change" ); } diff --git a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol index 82c7db254..2fea74e0d 100644 --- a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -29,7 +29,10 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 boundedAmount = _preAddQuoteToken(amountToAdd_); // Action phase - _addQuoteToken(boundedAmount, _lenderBucketIndex); + _addQuoteToken( + boundedAmount, + _lenderBucketIndex + ); } function removeQuoteToken( @@ -44,7 +47,10 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 boundedAmount = _preRemoveQuoteToken(amountToRemove_); // Action phase - _removeQuoteToken(boundedAmount, _lenderBucketIndex); + _removeQuoteToken( + boundedAmount, + _lenderBucketIndex + ); } function moveQuoteToken( @@ -64,7 +70,11 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { ) = _preMoveQuoteToken(amountToMove_, fromIndex_, toIndex_); // Action phase - _moveQuoteToken(boundedAmount, boundedFromIndex, boundedToIndex); + _moveQuoteToken( + boundedAmount, + boundedFromIndex, + boundedToIndex + ); } function transferLps( @@ -77,11 +87,22 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { numberOfCalls['BBasicHandler.transferLps']++; // Prepare test phase - (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); + ( + address receiver, + uint256 boundedLps + ) = _preTransferLps(toActorIndex_, lpsToTransfer_); // Action phase - _increaseLPAllowance(receiver, _lenderBucketIndex, boundedLps); - _transferLps(_actor, receiver, _lenderBucketIndex); + _increaseLPAllowance( + receiver, + _lenderBucketIndex, + boundedLps + ); + _transferLps( + _actor, + receiver, + _lenderBucketIndex + ); } function stampLoan( @@ -89,6 +110,7 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 skippedTime_ ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.stampLoan']++; + _stampLoan(); } @@ -99,18 +121,25 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preAddQuoteToken( uint256 amountToAdd_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); + boundedAmount_ = constrictToRange( + amountToAdd_, Maths.max(_pool.quoteTokenScale(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT + ); } function _preRemoveQuoteToken( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); + boundedAmount_ = constrictToRange( + amountToRemove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT + ); // ensure actor has quote tokens to remove - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if (lpBalanceBefore == 0) { - _addQuoteToken(boundedAmount_, _lenderBucketIndex); + LenderInfo memory lenderInfo = _getLenderInfo(_lenderBucketIndex, _actor); + if (lenderInfo.lpBalance == 0) { + _addQuoteToken( + boundedAmount_, + _lenderBucketIndex + ); } } @@ -119,17 +148,27 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 fromIndex_, uint256 toIndex_ ) internal returns (uint256 boundedFromIndex_, uint256 boundedToIndex_, uint256 boundedAmount_) { - boundedFromIndex_ = constrictToRange(fromIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - boundedToIndex_ = constrictToRange(toIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - boundedAmount_ = constrictToRange(amountToMove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); - - // ensure actor has LP to move - (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); - if (lpBalance == 0) _addQuoteToken(boundedAmount_, boundedToIndex_); + boundedFromIndex_ = constrictToRange( + fromIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX + ); + boundedToIndex_ = constrictToRange( + toIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX + ); + boundedAmount_ = constrictToRange( + amountToMove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT + ); + + LenderInfo memory lenderInfo = _getLenderInfo(boundedFromIndex_, _actor); + if (lenderInfo.lpBalance == 0) { + _addQuoteToken( + boundedAmount_, + boundedToIndex_ + ); + } + lenderInfo = _getLenderInfo(boundedFromIndex_, _actor); - (uint256 lps, ) = _pool.lenderInfo(boundedFromIndex_, _actor); // restrict amount to move by available deposit inside bucket - uint256 availableDeposit = _poolInfo.lpToQuoteTokens(address(_pool), lps, boundedFromIndex_); + uint256 availableDeposit = _lpToQuoteTokens(lenderInfo.lpBalance, boundedFromIndex_); boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); } @@ -138,14 +177,17 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 lpsToTransfer_ ) internal returns (address receiver_, uint256 boundedLps_) { // ensure actor has LP to transfer - (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if (senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); - - (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - - boundedLps_ = constrictToRange(lpsToTransfer_, 0, senderLpBalance); + LenderInfo memory senderInfo = _getLenderInfo(_lenderBucketIndex, _actor); + if (senderInfo.lpBalance == 0) { + _addQuoteToken( + 1e24, + _lenderBucketIndex + ); + } + senderInfo = _getLenderInfo(_lenderBucketIndex, _actor); - receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; + boundedLps_ = constrictToRange(lpsToTransfer_, 0, senderInfo.lpBalance); + receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; } function _preDrawDebt( diff --git a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol index a57130a80..5c4960c1b 100644 --- a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol @@ -20,7 +20,12 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas uint256 skippedTime_ ) external useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.kickAuction']++; - _kickAuction(borrowerIndex_, amount_, kickerIndex_); + + _kickAuction( + borrowerIndex_, + amount_, + kickerIndex_ + ); } function lenderKickAuction( @@ -29,6 +34,7 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas uint256 skippedTime_ ) external useRandomActor(kickerIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.lenderKickAuction']++; + _lenderKickAuction(_lenderBucketIndex); } @@ -38,7 +44,11 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas uint256 skippedTime_ ) external useRandomActor(kickerIndex_) useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.withdrawBonds']++; - _withdrawBonds(_actor, maxAmount_); + + _withdrawBonds( + _actor, + maxAmount_ + ); } /****************************/ @@ -53,29 +63,38 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas ) external useRandomActor(takerIndex_) useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.takeAuction']++; - address borrower; // try to take from head auction if any - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); - if (headAuction != address(0)) { - (, uint256 auctionedCollateral, ) = _poolInfo.borrowerInfo(address(_pool), headAuction); - borrower = headAuction; - amount_ = auctionedCollateral / 2; + AuctionInfo memory auctionInfo = _getAuctionInfo(address(0)); - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + address borrower; + if (auctionInfo.head != address(0)) { + borrower = auctionInfo.head; + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower); + amount_ = borrowerInfo.collateral / 2; + + auctionInfo = _getAuctionInfo(borrower); // TODO: eliminate this unnecessary skip, perhaps advance by single block instead - if (block.timestamp - kickTime < 1 hours) { + if (block.timestamp - auctionInfo.kickTime < 1 hours) { vm.warp(block.timestamp + 61 minutes); } - } else { address taker = _actor; // no head auction, prepare take action - (amount_, borrower) = _preTake(amount_, borrowerIndex_, takerIndex_); + ( + amount_, + borrower + ) = _preTake(amount_, borrowerIndex_, takerIndex_); + _actor = taker; changePrank(taker); } - _takeAuction(borrower, amount_, _actor); + _takeAuction( + borrower, + amount_, + _actor + ); } function bucketTake( @@ -87,20 +106,22 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas ) external useRandomActor(takerIndex_) useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.bucketTake']++; - bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + bucketIndex_ = constrictToRange( + bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX + ); - address borrower; // try to take from head auction if any - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); - if (headAuction != address(0)) { - borrower = headAuction; + AuctionInfo memory auctionInfo = _getAuctionInfo(address(0)); + + address borrower; + if (auctionInfo.head != address(0)) { + borrower = auctionInfo.head; - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + auctionInfo = _getAuctionInfo(borrower); // skip to make auction takeable - if (block.timestamp - kickTime < 1 hours) { + if (block.timestamp - auctionInfo.kickTime < 1 hours) { vm.warp(block.timestamp + 61 minutes); } - } else { address taker = _actor; // no head auction, prepare take action @@ -109,7 +130,12 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas changePrank(taker); } - _bucketTake(_actor, borrower, depositTake_, bucketIndex_); + _bucketTake( + _actor, + borrower, + depositTake_, + bucketIndex_ + ); } /******************************/ @@ -124,11 +150,12 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) writeLogs { numberOfCalls['BLiquidationHandler.settleAuction']++; - address borrower; // try to settle head auction if any - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); - if (headAuction != address(0)) { - borrower = headAuction; + AuctionInfo memory auctionInfo = _getAuctionInfo(address(0)); + + address borrower; + if (auctionInfo.head != address(0)) { + borrower = auctionInfo.head; } else { address settler = _actor; // no head auction, prepare take action @@ -137,7 +164,10 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas changePrank(settler); } - _settleAuction(borrower, LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX); + _settleAuction( + borrower, + LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX + ); } @@ -146,48 +176,65 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas /*******************************/ function _preKick(uint256 borrowerIndex_, uint256 amount_) internal returns(address borrower_, bool borrowerKicked_) { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - borrower_ = actors[borrowerIndex_]; - amount_ = constrictToRange(amount_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); + amount_ = constrictToRange( + amount_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT + ); + borrowerIndex_ = constrictToRange( + borrowerIndex_, 0, actors.length - 1 + ); - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower_); + borrower_ = actors[borrowerIndex_]; - borrowerKicked_ = kickTime != 0; + AuctionInfo memory auctionInfo = _getAuctionInfo(borrower_); + borrowerKicked_ = auctionInfo.kickTime != 0; if (!borrowerKicked_) { // if borrower not kicked then check if it is undercollateralized / kickable - uint256 lup = _poolInfo.lup(address(_pool)); - (uint256 debt, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower_); - if (_isCollateralized(debt, collateral, lup, _pool.poolType())) { + if (_isBorrowerCollateralized(borrowerInfo)) { changePrank(borrower_); _actor = borrower_; uint256 drawDebtAmount = _preDrawDebt(amount_); _drawDebt(drawDebtAmount); // skip to make borrower undercollateralized - (debt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); - if (debt != 0) vm.warp(block.timestamp + _getKickSkipTime()); + borrowerInfo = _getBorrowerInfo(borrower_); + if (borrowerInfo.debt != 0) vm.warp(block.timestamp + _getKickSkipTime()); } } } function _preTake(uint256 amount_, uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(uint256 boundedAmount_, address borrower_){ boundedAmount_ = _constrictTakeAmount(amount_); - borrower_ = _kickAuction(borrowerIndex_, boundedAmount_ * 100, kickerIndex_); + + borrower_ = _kickAuction( + borrowerIndex_, + boundedAmount_ * 100, + kickerIndex_ + ); // skip time to make auction takeable vm.warp(block.timestamp + 61 minutes); } function _preBucketTake(uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(address borrower_) { - borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_); + borrower_ = _kickAuction( + borrowerIndex_, + 1e24, + kickerIndex_ + ); + // skip time to make auction takeable vm.warp(block.timestamp + 61 minutes); } function _preSettleAuction(uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(address borrower_) { - borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_); + borrower_ = _kickAuction( + borrowerIndex_, + 1e24, + kickerIndex_ + ); // skip time to make auction clearable vm.warp(block.timestamp + 73 hours); @@ -206,7 +253,10 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas // Prepare test phase address kicker = _actor; bool borrowerKicked; - (borrower_, borrowerKicked)= _preKick(borrowerIndex_, amount_); + ( + borrower_, + borrowerKicked + )= _preKick(borrowerIndex_, amount_); // Action phase _actor = kicker; diff --git a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol index 7535a5cf4..1e65a882e 100644 --- a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol @@ -19,8 +19,7 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation numberOfCalls['BReserveHandler.kickReserveAuction']++; // take all reserves if available - (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - _takeReserves(claimableReservesRemaining); + _takeReserves(_getReservesInfo().claimableReservesRemaining); // Action phase _kickReserveAuction(); @@ -34,15 +33,17 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation numberOfCalls['BReserveHandler.takeReserves']++; // kick reserve auction if claimable reserves available - (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); - if (claimableReserves != 0) { + if (_getReservesInfo().claimableReserves != 0) { _kickReserveAuction(); } // take reserve auction if remaining claimable reserves - (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); + uint256 claimableReservesRemaining = _getReservesInfo().claimableReservesRemaining; if (claimableReservesRemaining != 0) { - uint256 boundedAmount = constrictToRange(amountToTake_, claimableReservesRemaining / 2, claimableReservesRemaining); + uint256 boundedAmount = constrictToRange( + amountToTake_, claimableReservesRemaining / 2, claimableReservesRemaining + ); + _takeReserves(boundedAmount); } } diff --git a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index 9f450b901..76a0681bb 100644 --- a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -13,7 +13,8 @@ import { MAX_FENWICK_INDEX, MAX_PRICE, MIN_PRICE, - _indexOf + _indexOf, + _isCollateralized } from 'src/libraries/helpers/PoolHelper.sol'; import { Maths } from 'src/libraries/internal/Maths.sol'; @@ -28,6 +29,61 @@ abstract contract BaseHandler is Test { using EnumerableSet for EnumerableSet.UintSet; + struct BorrowerInfo { + uint256 t0Debt; + uint256 debt; + uint256 collateral; + uint256 npTpRatio; + uint256 t0Np; + } + + struct BucketInfo { + uint256 lpBalance; + uint256 collateral; + uint256 deposit; + uint256 scale; + uint256 bankruptcyTime; + uint256 exchangeRate; + } + + struct AuctionInfo { + address kicker; + uint256 bondFactor; + uint256 bondSize; + uint256 kickTime; + uint256 referencePrice; + uint256 neutralPrice; + uint256 debtToCollateral; + uint256 auctionPrice; + uint256 auctionPriceIndex; + address head; + } + + struct KickerInfo { + uint256 claimableBond; + uint256 lockedBond; + uint256 totalBond; + } + + struct LenderInfo { + uint256 lpBalance; + uint256 depositTime; + } + + struct LoansInfo { + address maxBorrower; + uint256 maxT0DebtToCollateral; + uint256 noOfLoans; + } + + struct ReservesInfo { + uint256 reserves; + uint256 claimableReserves; + uint256 claimableReservesRemaining; + uint256 auctionPrice; + uint256 timeRemaining; + } + // Tokens TokenWithNDecimals internal _quote; BurnableToken internal _ajna; @@ -75,6 +131,7 @@ abstract contract BaseHandler is Test { uint256 public previousReserves; // reserves before action uint256 public increaseInReserves; // amount of reserve increase uint256 public decreaseInReserves; // amount of reserve decrease + uint256 public reservesErrorMargin; // change in reserve error acceptance margin // Auction bond invariant test state uint256 public previousTotalBonds; // total bond before action @@ -135,11 +192,11 @@ abstract contract BaseHandler is Test { address currentActor = _actor; // clear head auction if more than 72 hours passed - (, , , , , , , address headAuction, , ) = _pool.auctionInfo(address(0)); + address headAuction = _getAuctionInfo(address(0)).head; if (headAuction != address(0)) { - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(headAuction); + uint256 kickTime = _getAuctionInfo(headAuction).kickTime; if (block.timestamp - kickTime > 72 hours) { - (uint256 auctionedDebt, , ) = _poolInfo.borrowerInfo(address(_pool), headAuction); + uint256 auctionedDebt = _getBorrowerInfo(headAuction).debt; try vm.startPrank(headAuction) { } catch { @@ -163,10 +220,10 @@ abstract contract BaseHandler is Test { uint256 maxLoansRepayments = 5; while (maxPoolDebt < poolDebt && maxLoansRepayments > 0) { - (address borrower, , ) = _pool.loansInfo(); + address borrower = _getLoansInfo().maxBorrower; if (borrower != address(0)) { - (uint256 debt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + uint256 debt = _getBorrowerInfo(borrower).debt; try vm.startPrank(borrower) { } catch { @@ -223,7 +280,9 @@ abstract contract BaseHandler is Test { if (lenderBucketIndexes.length < 3) { // if actor has touched less than three buckets, add a new bucket - _lenderBucketIndex = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + _lenderBucketIndex = constrictToRange( + bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX + ); lenderBucketIndexes.push(_lenderBucketIndex); } else { @@ -243,7 +302,15 @@ abstract contract BaseHandler is Test { if (logToFile == true) { if (numberOfCalls["Write logs"]++ == 0) vm.writeFile(path, ""); - printInNextLine(string(abi.encodePacked("================= Handler Call : ", Strings.toString(numberOfCalls["Write logs"]), " =================="))); + printInNextLine( + string( + abi.encodePacked( + "================= Handler Call : ", + Strings.toString(numberOfCalls["Write logs"]), + " ==================" + ) + ) + ); } if (logVerbosity > 0) { @@ -259,6 +326,105 @@ abstract contract BaseHandler is Test { /*** Pool Helper Functions ***/ /*****************************/ + function _getAuctionInfo(address borrower_) internal view returns (AuctionInfo memory auctionInfo_) { + ( + auctionInfo_.kicker, + auctionInfo_.bondFactor, + auctionInfo_.bondSize, + auctionInfo_.kickTime, + auctionInfo_.referencePrice, + auctionInfo_.neutralPrice, + auctionInfo_.debtToCollateral, + auctionInfo_.head, + , + ) = _pool.auctionInfo(borrower_); + + (,,,, auctionInfo_.auctionPrice,,,, ) = _poolInfo.auctionStatus(address(_pool), borrower_); + + auctionInfo_.auctionPriceIndex = auctionInfo_.auctionPrice < MIN_PRICE ? 7388 : (auctionInfo_.auctionPrice > MAX_PRICE ? 0 : _indexOf(auctionInfo_.auctionPrice)); + } + + function _getBorrowerInfo(address borrower_) internal view returns (BorrowerInfo memory borrowerInfo_) { + ( + borrowerInfo_.debt, + borrowerInfo_.collateral, + borrowerInfo_.t0Np + ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + + ( + borrowerInfo_.t0Debt, + , + borrowerInfo_.npTpRatio + ) = _pool.borrowerInfo(borrower_); + } + + function _getBucketInfo(uint256 index_) internal view returns (BucketInfo memory bucketInfo_) { + ( + bucketInfo_.lpBalance, + bucketInfo_.collateral, + bucketInfo_.bankruptcyTime, + bucketInfo_.deposit, + bucketInfo_.scale + ) = _pool.bucketInfo(index_); + + bucketInfo_.exchangeRate = _pool.bucketExchangeRate(index_); + } + + function _getLenderInfo( + uint256 index_, + address lender_ + ) internal view returns (LenderInfo memory lenderInfo_) { + ( + lenderInfo_.lpBalance, + lenderInfo_.depositTime + ) = _pool.lenderInfo(index_, lender_); + } + + function _getLoansInfo() internal view returns (LoansInfo memory loansInfo_) { + ( + loansInfo_.maxBorrower, + loansInfo_.maxT0DebtToCollateral, + loansInfo_.noOfLoans + ) = _pool.loansInfo(); + } + + function _getKickerInfo(address kicker_) internal view returns (KickerInfo memory kickerInfo_) { + ( + kickerInfo_.claimableBond, + kickerInfo_.lockedBond + ) = _pool.kickerInfo(kicker_); + kickerInfo_.totalBond = kickerInfo_.claimableBond + kickerInfo_.lockedBond; + } + + function _getReservesInfo() internal view returns (ReservesInfo memory reservesInfo_) { + ( + reservesInfo_.reserves, + reservesInfo_.claimableReserves, + reservesInfo_.claimableReservesRemaining, + reservesInfo_.auctionPrice, + reservesInfo_.timeRemaining + ) = _poolInfo.poolReservesInfo(address(_pool)); + } + + function _getLup() internal view returns (uint256) { + return _poolInfo.lup(address(_pool)); + } + + function _getPoolQuoteBalance() internal view returns (uint256) { + return _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); + } + + function _isBorrowerCollateralized(BorrowerInfo memory borrowerInfo_) internal view returns (bool) { + return _isCollateralized(borrowerInfo_.debt, borrowerInfo_.collateral, _getLup(), _pool.poolType()); + } + + function _lpToQuoteTokens( + uint256 lp_, + uint256 index_ + ) internal view returns (uint256) { + return _poolInfo.lpToQuoteTokens(address(_pool), lp_, index_); + } + function _getKickSkipTime() internal returns (uint256) { return vm.envOr("SKIP_TIME_TO_KICK", uint256(200 days)); } @@ -347,7 +513,7 @@ abstract contract BaseHandler is Test { increaseInReserves = 0; decreaseInReserves = 0; // record reserves before each action - (previousReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + previousReserves = _getReservesInfo().reserves; // reset penalties before each action borrowerPenalty = 0; @@ -427,8 +593,7 @@ abstract contract BaseHandler is Test { // update local fenwick to pool fenwick before each action function _updateLocalFenwick() internal { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - (, , , uint256 deposits, ) = _pool.bucketInfo(bucketIndex); - fenwickDeposits[bucketIndex] = deposits; + fenwickDeposits[bucketIndex] = _getBucketInfo(bucketIndex).deposit; } } @@ -436,21 +601,22 @@ abstract contract BaseHandler is Test { /*** Auctions Helper Functions ***/ /*********************************/ - function _getKickerBond(address kicker_) internal view returns (uint256 bond_) { - (uint256 claimableBond, uint256 lockedBond) = _pool.kickerInfo(kicker_); - bond_ = claimableBond + lockedBond; - } - function _recordSettleBucket( address borrower_, uint256 borrowerCollateralBefore_, uint256 kickTimeBefore_, uint256 auctionPrice_ ) internal { - (uint256 kickTimeAfter, , , , , , , , ) = _poolInfo.auctionStatus(address(_pool), borrower_); + uint256 kickTimeAfter = _getAuctionInfo(borrower_).kickTime; // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket - if (kickTimeBefore_ != 0 && kickTimeAfter == 0 && borrowerCollateralBefore_ % 1e18 != 0) { + if ( + kickTimeBefore_ != 0 + && + kickTimeAfter == 0 + && + borrowerCollateralBefore_ % 1e18 != 0 + ) { if (auctionPrice_ < MIN_PRICE) { buckets.add(7388); lenderDepositTime[borrower_][7388] = block.timestamp; @@ -545,7 +711,11 @@ abstract contract BaseHandler is Test { uint256 bucketIndex = buckets.at(j); (uint256 lenderLps, ) = _pool.lenderInfo(bucketIndex, actors[i]); if (lenderLps != 0) { - data = string(abi.encodePacked("Lps at ", Strings.toString(bucketIndex), " = ", Strings.toString(lenderLps))); + data = string( + abi.encodePacked( + "Lps at ", Strings.toString(bucketIndex), " = ", Strings.toString(lenderLps) + ) + ); printLine(data); } } @@ -558,10 +728,11 @@ abstract contract BaseHandler is Test { for (uint256 i = 0; i < actors.length; i++) { printLine(""); printLog("Actor ", i + 1); - (uint256 debt, uint256 pledgedCollateral, ) = _poolInfo.borrowerInfo(address(_pool), actors[i]); - if (debt != 0 || pledgedCollateral != 0) { - printLog("Debt = ", debt); - printLog("Pledged collateral = ", pledgedCollateral); + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(actors[i]); + if (borrowerInfo.debt != 0 || borrowerInfo.collateral != 0) { + printLog("Debt = ", borrowerInfo.debt); + printLog("Pledged collateral = ", borrowerInfo.collateral); } } printInNextLine("======================="); diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol index 7eda8bf9d..3b9a00164 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol @@ -27,14 +27,16 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.addQuoteToken']++; - (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - // ensure actor always has amount of quote to add _ensureQuoteAmount(_actor, amount_); - try _pool.addQuoteToken(amount_, bucketIndex_, block.timestamp + 1 minutes - ) returns (uint256, uint256 addedAmount_) { + LenderInfo memory lenderInfoBeforeAdd = _getLenderInfo(bucketIndex_, _actor); + try _pool.addQuoteToken( + amount_, + bucketIndex_, + block.timestamp + 1 minutes + ) returns (uint256, uint256 addedAmount_) { // amount is rounded in pool to token scale amount_ = _roundToScale(amount_, _pool.quoteTokenScale()); @@ -43,15 +45,18 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { // **B5**: when adding quote tokens: lender deposit time = timestamp of block when deposit happened lenderDepositTime[_actor][bucketIndex_] = block.timestamp; + // **R3**: Exchange rates are unchanged by depositing quote token into a bucket exchangeRateShouldNotChange[bucketIndex_] = true; _fenwickAdd(addedAmount_, bucketIndex_); + LenderInfo memory lenderInfoAfterAdd = _getLenderInfo(bucketIndex_, _actor); // Post action condition - (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); - + require( + lenderInfoAfterAdd.lpBalance > lenderInfoBeforeAdd.lpBalance, + "LP balance should increase" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -63,12 +68,15 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.removeQuoteToken']++; - (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + // record fenwick tree state before action + fenwickDeposits[bucketIndex_] = _getBucketInfo(bucketIndex_).deposit; - ( , , , uint256 deposit, ) = _pool.bucketInfo(bucketIndex_); - fenwickDeposits[bucketIndex_] = deposit; + LenderInfo memory lenderInfoBeforeRemove = _getLenderInfo(bucketIndex_, _actor); - try _pool.removeQuoteToken(amount_, bucketIndex_) returns (uint256 removedAmount_, uint256) { + try _pool.removeQuoteToken( + amount_, + bucketIndex_ + ) returns (uint256 removedAmount_, uint256) { // **R4**: Exchange rates are unchanged by withdrawing deposit (quote token) from a bucket exchangeRateShouldNotChange[bucketIndex_] = true; @@ -77,10 +85,12 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { // rounding in favour of pool goes to reserves increaseInReserves += removedAmount_ - _roundToScale(removedAmount_, _pool.quoteTokenScale()); + LenderInfo memory lenderInfoAfterRemove = _getLenderInfo(bucketIndex_, _actor); // Post action condition - (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); - + require( + lenderInfoAfterRemove.lpBalance < lenderInfoBeforeRemove.lpBalance, + "LP balance should decrease" + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -93,8 +103,8 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.moveQuoteToken']++; - ( , , , uint256 fromDeposit, ) = _pool.bucketInfo(fromIndex_); - fenwickDeposits[fromIndex_] = fromDeposit; + // record fenwick tree state before action + fenwickDeposits[fromIndex_] = _getBucketInfo(fromIndex_).deposit; try _pool.moveQuoteToken( amount_, @@ -102,12 +112,12 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { toIndex_, block.timestamp + 1 minutes ) returns (uint256, uint256, uint256 movedAmount_) { - - (, uint256 fromBucketDepositTime) = _pool.lenderInfo(fromIndex_, _actor); - (, uint256 toBucketDepositTime) = _pool.lenderInfo(toIndex_, _actor); - // **B5**: when moving quote tokens: lender deposit time = timestamp of block when move happened - lenderDepositTime[_actor][toIndex_] = Maths.max(fromBucketDepositTime, toBucketDepositTime); + lenderDepositTime[_actor][toIndex_] = Maths.max( + _getLenderInfo(fromIndex_, _actor).depositTime, + _getLenderInfo(toIndex_, _actor).depositTime + ); + // **RE3**: Reserves increase only when moving quote tokens into a lower-priced bucket // movedAmount_ can be greater than amount_ in case when bucket gets empty by moveQuoteToken if (amount_ > movedAmount_) increaseInReserves += amount_ - movedAmount_; @@ -136,7 +146,12 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { buckets[0] = bucketIndex_; uint256[] memory amounts = new uint256[](1); amounts[0] = amount_; - _pool.increaseLPAllowance(receiver_, buckets, amounts); + + _pool.increaseLPAllowance( + receiver_, + buckets, + amounts + ); } function _transferLps( @@ -151,14 +166,16 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { changePrank(receiver_); - try _pool.transferLP(sender_, receiver_, buckets) { - - (, uint256 senderDepositTime) = _pool.lenderInfo(bucketIndex_, sender_); - (, uint256 receiverDepositTime) = _pool.lenderInfo(bucketIndex_, receiver_); - + try _pool.transferLP( + sender_, + receiver_, + buckets + ) { // **B6**: when receiving transferred LP : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time - lenderDepositTime[receiver_][bucketIndex_] = Maths.max(senderDepositTime, receiverDepositTime); - + lenderDepositTime[receiver_][bucketIndex_] = Maths.max( + _getLenderInfo(bucketIndex_, sender_).depositTime, + _getLenderInfo(bucketIndex_, receiver_).depositTime + ); } catch (bytes memory err) { _ensurePoolError(err); } @@ -166,6 +183,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _stampLoan() internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.stampLoan']++; + try _pool.stampLoan() { } catch (bytes memory err) { _ensurePoolError(err); diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol index 2b061fa8a..375eedf3b 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -12,20 +12,20 @@ import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/ import { Buckets } from 'src/libraries/internal/Buckets.sol'; import { BaseHandler } from './BaseHandler.sol'; +import '@std/Vm.sol'; abstract contract UnboundedLiquidationPoolHandler is BaseHandler { using EnumerableSet for EnumerableSet.UintSet; - struct LocalBucketTakeVars { + struct LocalTakeVars { uint256 kickerLps; uint256 takerLps; uint256 deposit; uint256 kickerBond; uint256 borrowerLps; - uint256 borrowerDebt; uint256 borrowerCollateral; - uint256 compensatedBucketCollateral; + uint256 borrowerDebt; } /*******************************/ @@ -37,21 +37,22 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.kickAuction']++; - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower_); + KickerInfo memory kickerInfoBeforeKick = _getKickerInfo(_actor); // ensure actor always has the amount to pay for bond - _ensureQuoteAmount(_actor, borrowerDebt); - - uint256 kickerBondBefore = _getKickerBond(_actor); + _ensureQuoteAmount(_actor, borrowerInfo.debt); - try _pool.kick(borrower_, 7388) { + try _pool.kick( + borrower_, + 7388 + ) { numberOfActions['kick']++; - uint256 kickerBondAfter = _getKickerBond(_actor); + KickerInfo memory kickerInfoAfterKick = _getKickerInfo(_actor); // **A7**: totalBondEscrowed should increase when auctioned kicked with the difference needed to cover the bond - increaseInBonds += kickerBondAfter - kickerBondBefore; - + increaseInBonds += kickerInfoAfterKick.totalBond - kickerInfoBeforeKick.totalBond; } catch (bytes memory err) { _ensurePoolError(err); } @@ -62,30 +63,33 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.lenderKickAuction']++; - (address maxBorrower, , ) = _pool.loansInfo(); - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), maxBorrower); - ( , , , uint256 depositBeforeAction, ) = _pool.bucketInfo(bucketIndex_); - fenwickDeposits[bucketIndex_] = depositBeforeAction; + address maxBorrower = _getLoansInfo().maxBorrower; + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(maxBorrower); + if (borrowerInfo.debt == 0) return; - uint256 kickerBondBefore = _getKickerBond(_actor); + BucketInfo memory bucketInfoBeforeKick = _getBucketInfo(bucketIndex_); + KickerInfo memory kickerInfoBeforeKick = _getKickerInfo(_actor); - if (borrowerDebt == 0) return; + // record fenwick tree state before action + fenwickDeposits[bucketIndex_] = bucketInfoBeforeKick.deposit; // ensure actor always has the amount to add for kick - _ensureQuoteAmount(_actor, borrowerDebt); + _ensureQuoteAmount(_actor, borrowerInfo.debt); - try _pool.lenderKick(bucketIndex_, 7388) { + try _pool.lenderKick( + bucketIndex_, + 7388 + ) { numberOfActions['lenderKick']++; - ( , , , uint256 depositAfterAction, ) = _pool.bucketInfo(bucketIndex_); - - uint256 kickerBondAfter = _getKickerBond(_actor); + BucketInfo memory bucketInfoAfterKick = _getBucketInfo(bucketIndex_); + KickerInfo memory kickerInfoAfterKick = _getKickerInfo(_actor); // **A7**: totalBondEscrowed should increase when auctioned kicked with the difference needed to cover the bond - increaseInBonds += kickerBondAfter - kickerBondBefore; - - _fenwickRemove(depositBeforeAction - depositAfterAction, bucketIndex_); + increaseInBonds += kickerInfoAfterKick.totalBond - kickerInfoBeforeKick.totalBond; + _fenwickRemove(bucketInfoBeforeKick.deposit - bucketInfoAfterKick.deposit, bucketIndex_); } catch (bytes memory err) { _ensurePoolError(err); } @@ -97,23 +101,24 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.withdrawBonds']++; - uint256 balanceBeforeWithdraw = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); - (uint256 claimableBondBeforeWithdraw, ) = _pool.kickerInfo(_actor); + KickerInfo memory kickerInfoBeforeWithdraw = _getKickerInfo(_actor); + uint256 poolBalanceBeforeWithdraw = _getPoolQuoteBalance(); - try _pool.withdrawBonds(kicker_, maxAmount_) { - - uint256 balanceAfterWithdraw = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); - (uint256 claimableBondAfterWithdraw, ) = _pool.kickerInfo(_actor); + try _pool.withdrawBonds( + kicker_, + maxAmount_ + ) { + KickerInfo memory kickerInfoAfterWithdraw = _getKickerInfo(_actor); + uint256 poolBalanceAfterWithdraw = _getPoolQuoteBalance(); // **A7** Claimable bonds should be available for withdrawal from pool at any time (bonds are guaranteed by the protocol). require( - claimableBondAfterWithdraw < claimableBondBeforeWithdraw, + kickerInfoAfterWithdraw.claimableBond < kickerInfoBeforeWithdraw.claimableBond, "A7: claimable bond not available to withdraw" ); // **A7**: totalBondEscrowed should decrease only when kicker bonds withdrawned - decreaseInBonds += balanceBeforeWithdraw - balanceAfterWithdraw; - + decreaseInBonds += poolBalanceBeforeWithdraw - poolBalanceAfterWithdraw; } catch (bytes memory err) { _ensurePoolError(err); } @@ -130,62 +135,84 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.takeAuction']++; - (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - - ( - uint256 borrowerDebtBeforeTake, - uint256 borrowerCollateralBeforeTake, - ) = _poolInfo.borrowerInfo(address(_pool), borrower_); - uint256 totalBondBeforeTake = _getKickerBond(kicker); - uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); + AuctionInfo memory auctionInfo = _getAuctionInfo(borrower_); + LocalTakeVars memory beforeTakeVars = _getTakeInfo( + auctionInfo.auctionPriceIndex, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); - (uint256 kickTimeBefore, , , , uint256 auctionPrice, , , , ) = _poolInfo.auctionStatus(address(_pool), borrower_); + uint256 totalBalanceBeforeTake = _getPoolQuoteBalance(); // ensure actor always has the amount to take collateral _ensureQuoteAmount(taker_, 1e45); - try _pool.take(borrower_, amount_, taker_, bytes("")) { + try _pool.take( + borrower_, + amount_, + taker_, + bytes("") + ) { numberOfActions['take']++; - (uint256 borrowerDebtAfterTake, uint256 borrowerCollateralAfterTake, ) = _poolInfo.borrowerInfo(address(_pool), borrower_); - uint256 totalBondAfterTake = _getKickerBond(kicker); - uint256 totalBalanceAfterTake = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); + uint256 totalBalanceAfterTake = _getPoolQuoteBalance(); + + LocalTakeVars memory afterTakeVars = _getTakeInfo( + auctionInfo.auctionPriceIndex, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); // **RE7**: Reserves decrease with debt covered by take. - decreaseInReserves += borrowerDebtBeforeTake - borrowerDebtAfterTake; - + decreaseInReserves += beforeTakeVars.borrowerDebt - afterTakeVars.borrowerDebt; + // **A8**: kicker reward <= Borrower penalty - borrowerPenalty = Maths.wmul(borrowerCollateralBeforeTake - borrowerCollateralAfterTake, auctionPrice) - (borrowerDebtBeforeTake - borrowerDebtAfterTake); + // Borrower penalty is difference between borrower collateral taken at auction price to amount of borrower debt reduced. + borrowerPenalty = Maths.ceilWmul(beforeTakeVars.borrowerCollateral - afterTakeVars.borrowerCollateral, auctionInfo.auctionPrice) - (beforeTakeVars.borrowerDebt - afterTakeVars.borrowerDebt); + + if (afterTakeVars.borrowerLps > beforeTakeVars.borrowerLps) { + // Borrower gets Lps at auction price against fractional collateral added to the bucket. + borrowerPenalty -= _rewardedLpToQuoteToken(afterTakeVars.borrowerLps - beforeTakeVars.borrowerLps, auctionInfo.auctionPriceIndex); + } - if (totalBondBeforeTake > totalBondAfterTake) { + if (beforeTakeVars.kickerBond > afterTakeVars.kickerBond) { // **RE7**: Reserves increase by bond penalty on take. - increaseInReserves += totalBondBeforeTake - totalBondAfterTake; + increaseInReserves += beforeTakeVars.kickerBond - afterTakeVars.kickerBond; // **A7**: Total Bond decrease by bond penalty on take. - decreaseInBonds += totalBondBeforeTake - totalBondAfterTake; + decreaseInBonds += beforeTakeVars.kickerBond - afterTakeVars.kickerBond; } else { // **RE7**: Reserves decrease by bond reward on take. - decreaseInReserves += totalBondAfterTake - totalBondBeforeTake; + decreaseInReserves += afterTakeVars.kickerBond - beforeTakeVars.kickerBond; // **A7**: Total Bond increase by bond penalty on take. - increaseInBonds += totalBondAfterTake - totalBondBeforeTake; + increaseInBonds += afterTakeVars.kickerBond - beforeTakeVars.kickerBond; // **A8**: kicker reward <= Borrower penalty - kickerReward += totalBondAfterTake - totalBondBeforeTake; + kickerReward += afterTakeVars.kickerBond - beforeTakeVars.kickerBond; } // **RE7**: Reserves increase with the quote token paid by taker. increaseInReserves += totalBalanceAfterTake - totalBalanceBeforeTake; + // **RE9**: Reserves unchanged by takes and bucket takes below TP(at the time of kick) + if (auctionInfo.auctionPrice < auctionInfo.debtToCollateral) { + increaseInReserves = 0; + decreaseInReserves = 0; + } + if (_pool.poolType() == 1) { _recordSettleBucket( borrower_, - borrowerCollateralBeforeTake, - kickTimeBefore, - auctionPrice + beforeTakeVars.borrowerCollateral, + auctionInfo.kickTime, + auctionInfo.auctionPrice ); } - } catch (bytes memory err) { _ensurePoolError(err); } @@ -199,49 +226,50 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.bucketTake']++; - (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - ( , , , , uint256 auctionPrice, , , , ) = _poolInfo.auctionStatus(address(_pool), borrower_); - uint256 auctionBucketIndex = auctionPrice < MIN_PRICE ? 7388 : (auctionPrice > MAX_PRICE ? 0 : _indexOf(auctionPrice)); - - LocalBucketTakeVars memory beforeBucketTakeVars = getBucketTakeInfo(bucketIndex_, kicker, _actor, auctionBucketIndex, borrower_); + AuctionInfo memory auctionInfo = _getAuctionInfo(borrower_); + LocalTakeVars memory beforeTakeVars = _getTakeInfo( + bucketIndex_, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); - try _pool.bucketTake(borrower_, depositTake_, bucketIndex_) { + // Record event emitted in bucketTake method call to calculate `borrowerPenalty` and `kickerReward` + vm.recordLogs(); + + try _pool.bucketTake( + borrower_, + depositTake_, + bucketIndex_ + ) { numberOfActions['bucketTake']++; - LocalBucketTakeVars memory afterBucketTakeVars = getBucketTakeInfo(bucketIndex_, kicker, _actor, auctionBucketIndex, borrower_); + LocalTakeVars memory afterTakeVars = _getTakeInfo( + bucketIndex_, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); // **B7**: when awarded bucket take LP : taker deposit time = timestamp of block when award happened - if (afterBucketTakeVars.takerLps > beforeBucketTakeVars.takerLps) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; - - // for ERC-721 pools; adjust borrower collateral by compensated collateral awarded to the bucket at the auction price - if (_pool.poolType() == 1 && beforeBucketTakeVars.borrowerLps != afterBucketTakeVars.borrowerLps) { - if (bucketIndex_ == auctionBucketIndex) revert("Cannot distinguish bucketTake collateral from compensated bucket collateral"); - afterBucketTakeVars.borrowerCollateral += afterBucketTakeVars.compensatedBucketCollateral - beforeBucketTakeVars.compensatedBucketCollateral; - } + if (afterTakeVars.takerLps > beforeTakeVars.takerLps) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; - if (afterBucketTakeVars.kickerLps > beforeBucketTakeVars.kickerLps) { + if (afterTakeVars.kickerLps > beforeTakeVars.kickerLps) { // **B7**: when awarded bucket take LP : kicker deposit time = timestamp of block when award happened - lenderDepositTime[kicker][bucketIndex_] = block.timestamp; - - // when kicker and taker are same, kicker Reward = total Reward (lps) - taker Reward (Collateral Price * difference of bucket used and auction price) - if (!depositTake_ && kicker == _actor) { - uint256 totalReward = rewardedLpToQuoteToken(afterBucketTakeVars.kickerLps - beforeBucketTakeVars.kickerLps, bucketIndex_); - uint256 takerReward = Maths.floorWmul(beforeBucketTakeVars.borrowerCollateral - afterBucketTakeVars.borrowerCollateral, _priceAt(bucketIndex_) - auctionPrice); - - // **A8**: kicker reward <= Borrower penalty - kickerReward = totalReward - takerReward; - } else { - // **A8**: kicker reward <= Borrower penalty - kickerReward = rewardedLpToQuoteToken(afterBucketTakeVars.kickerLps - beforeBucketTakeVars.kickerLps, bucketIndex_); - } + lenderDepositTime[auctionInfo.kicker][bucketIndex_] = block.timestamp; } - // **A8**: kicker reward <= Borrower penalty - if (depositTake_) { - borrowerPenalty = Maths.wmul(beforeBucketTakeVars.borrowerCollateral - afterBucketTakeVars.borrowerCollateral, _priceAt(bucketIndex_)) - (beforeBucketTakeVars.borrowerDebt - afterBucketTakeVars.borrowerDebt); - } else { - borrowerPenalty = Maths.wmul(beforeBucketTakeVars.borrowerCollateral - afterBucketTakeVars.borrowerCollateral, auctionPrice) - (beforeBucketTakeVars.borrowerDebt - afterBucketTakeVars.borrowerDebt); - } + // Get emitted events logs in bucketTake + Vm.Log[] memory entries = vm.getRecordedLogs(); + (borrowerPenalty, kickerReward) = _getBorrowerPenaltyAndKickerReward( + entries, + bucketIndex_, + beforeTakeVars.borrowerDebt - afterTakeVars.borrowerDebt, + depositTake_, + auctionInfo.auctionPrice + ); // reserves are increased by take penalty of borrower (Deposit used from bucket - Borrower debt reduced) increaseInReserves += borrowerPenalty; @@ -249,36 +277,50 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // reserves are decreased by kicker reward decreaseInReserves += kickerReward; - if (beforeBucketTakeVars.kickerBond > afterBucketTakeVars.kickerBond) { + if (beforeTakeVars.kickerBond > afterTakeVars.kickerBond) { // **RE7**: Reserves increase by bond penalty on take. - increaseInReserves += beforeBucketTakeVars.kickerBond - afterBucketTakeVars.kickerBond; + increaseInReserves += beforeTakeVars.kickerBond - afterTakeVars.kickerBond; // **A7**: Total Bond decrease by bond penalty on take. - decreaseInBonds += beforeBucketTakeVars.kickerBond - afterBucketTakeVars.kickerBond; - } else { - // **RE7**: Reserves decrease by bond reward on take. - decreaseInReserves += afterBucketTakeVars.kickerBond - beforeBucketTakeVars.kickerBond; - - // **A7**: Total Bond increase by bond penalty on take. - increaseInBonds += afterBucketTakeVars.kickerBond - beforeBucketTakeVars.kickerBond; + decreaseInBonds += beforeTakeVars.kickerBond - afterTakeVars.kickerBond; } + // **R7**: Exchange rates are unchanged under depositTakes // **R8**: Exchange rates are unchanged under arbTakes exchangeRateShouldNotChange[bucketIndex_] = true; + // Reserves can increase with roundings in deposit calculations when auction Price is very small + if (auctionInfo.auctionPrice != 0 && auctionInfo.auctionPrice < 100) { + reservesErrorMargin = (beforeTakeVars.deposit - afterTakeVars.deposit) / auctionInfo.auctionPrice; + } + + // In case of bucket take, collateral is taken at bucket price. + uint256 takePrice = _priceAt(bucketIndex_); + + // **RE9**: Reserves unchanged by takes and bucket takes below TP(at the time of kick) + if (takePrice < auctionInfo.debtToCollateral) { + increaseInReserves = 0; + decreaseInReserves = 0; + } + // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower_); - if (kickTime == 0 && _pool.poolType() == 1) { - buckets.add(auctionBucketIndex); - if (beforeBucketTakeVars.borrowerLps < afterBucketTakeVars.borrowerLps) { - lenderDepositTime[borrower_][auctionBucketIndex] = block.timestamp; + if ( + _getAuctionInfo(borrower_).kickTime == 0 + && + _pool.poolType() == 1 + ) { + buckets.add(auctionInfo.auctionPriceIndex); + if (beforeTakeVars.borrowerLps < afterTakeVars.borrowerLps) { + lenderDepositTime[borrower_][auctionInfo.auctionPriceIndex] = block.timestamp; } } // assign value to fenwick tree to mitigate rounding error that could be created in a _fenwickRemove call - fenwickDeposits[bucketIndex_] = afterBucketTakeVars.deposit; - + fenwickDeposits[bucketIndex_] = afterTakeVars.deposit; } catch (bytes memory err) { + // Reset event Logs + vm.getRecordedLogs(); + _ensurePoolError(err); } } @@ -292,25 +334,27 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { uint256 maxDepth_ ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.settleAuction']++; - ( - uint256 borrowerT0Debt, - uint256 collateral, - ) = _pool.borrowerInfo(borrower_); - (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower_); + + uint256 reservesBeforeAction = _getReservesInfo().reserves; (uint256 inflator, ) = _pool.inflatorInfo(); - try _pool.settle(borrower_, maxDepth_) { + try _pool.settle( + borrower_, + maxDepth_ + ) { numberOfActions['settle']++; // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb - while (maxDepth_ != 0 && borrowerT0Debt != 0 && collateral != 0) { + while (maxDepth_ != 0 && borrowerInfo.t0Debt != 0 && borrowerInfo.collateral != 0) { uint256 bucketIndex = fenwickIndexForSum(1); - uint256 maxSettleableDebt = Maths.floorWmul(collateral, _priceAt(bucketIndex)); + uint256 maxSettleableDebt = Maths.floorWmul(borrowerInfo.collateral, _priceAt(bucketIndex)); uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); + uint256 borrowerDebt = Maths.wmul(borrowerInfo.t0Debt, inflator); if (fenwickDeposit == 0 && maxSettleableDebt != 0) { - collateral = 0; + borrowerInfo.collateral = 0; // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 // **B5**: when settle with collateral: record min bucket where collateral added buckets.add(7388); @@ -320,23 +364,26 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // enough deposit in bucket and collateral avail to settle entire debt if (fenwickDeposit >= borrowerDebt && maxSettleableDebt >= borrowerDebt) { fenwickDeposits[bucketIndex] -= borrowerDebt; - collateral -= Maths.ceilWdiv(borrowerDebt, _priceAt(bucketIndex)); - borrowerT0Debt = 0; + + borrowerInfo.collateral -= Maths.ceilWdiv(borrowerDebt, _priceAt(bucketIndex)); + borrowerInfo.t0Debt = 0; } // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount else if (maxSettleableDebt >= fenwickDeposit) { fenwickDeposits[bucketIndex] = 0; - collateral -= Maths.ceilWdiv(fenwickDeposit, _priceAt(bucketIndex)); - borrowerT0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); + + borrowerInfo.collateral -= Maths.ceilWdiv(fenwickDeposit, _priceAt(bucketIndex)); + borrowerInfo.t0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); } // exchange all collateral with deposit else { fenwickDeposits[bucketIndex] -= maxSettleableDebt; - collateral = 0; - borrowerT0Debt -= Maths.floorWdiv(maxSettleableDebt, inflator); + + borrowerInfo.collateral = 0; + borrowerInfo.t0Debt -= Maths.floorWdiv(maxSettleableDebt, inflator); } } else { - collateral = 0; + borrowerInfo.collateral = 0; // **B5**: when settle with collateral: record min bucket where collateral added. // Lender doesn't get any LP when settle bad debt. buckets.add(7388); @@ -347,9 +394,10 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } // if collateral becomes 0 and still debt is left, settle debt by reserves and hpb making buckets bankrupt - if (borrowerT0Debt != 0 && collateral == 0) { + if (borrowerInfo.t0Debt != 0 && borrowerInfo.collateral == 0) { + + uint256 reservesAfterAction = _getReservesInfo().reserves; - (uint256 reservesAfterAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); if (reservesBeforeAction > reservesAfterAction) { // **RE12**: Reserves decrease by amount of reserve used to settle a auction decreaseInReserves = reservesBeforeAction - reservesAfterAction; @@ -357,23 +405,28 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // Reserves might increase upto 2 WAD due to rounding issue increaseInReserves = reservesAfterAction - reservesBeforeAction; } - borrowerT0Debt -= Maths.min(Maths.wdiv(decreaseInReserves, inflator), borrowerT0Debt); + borrowerInfo.t0Debt -= Maths.min( + Maths.wdiv(decreaseInReserves, inflator), + borrowerInfo.t0Debt + ); - while (maxDepth_ != 0 && borrowerT0Debt != 0) { + while (maxDepth_ != 0 && borrowerInfo.t0Debt != 0) { uint256 bucketIndex = fenwickIndexForSum(1); uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); + uint256 borrowerDebt = Maths.wmul(borrowerInfo.t0Debt, inflator); if (bucketIndex != MAX_FENWICK_INDEX) { // debt is greater than bucket deposit if (borrowerDebt > fenwickDeposit) { fenwickDeposits[bucketIndex] = 0; - borrowerT0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); + + borrowerInfo.t0Debt -= Maths.floorWdiv(fenwickDeposit, inflator); } // bucket deposit is greater than debt else { fenwickDeposits[bucketIndex] -= borrowerDebt; - borrowerT0Debt = 0; + + borrowerInfo.t0Debt = 0; } } @@ -381,8 +434,13 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } } // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket - (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower_); - if (kickTime == 0 && collateral % 1e18 != 0 && _pool.poolType() == 1) { + if ( + _getAuctionInfo(borrower_).kickTime == 0 + && + borrowerInfo.collateral % 1e18 != 0 + && + _pool.poolType() == 1 + ) { buckets.add(7388); lenderDepositTime[borrower_][7388] = block.timestamp; } @@ -391,29 +449,63 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } } - function getBucketTakeInfo(uint256 bucketIndex_, address kicker_, address taker_, uint256 auctionBucketIndex_, address borrower_) internal view returns(LocalBucketTakeVars memory bucketTakeVars) { - (bucketTakeVars.kickerLps, ) = _pool.lenderInfo(bucketIndex_, kicker_); - (bucketTakeVars.takerLps, ) = _pool.lenderInfo(bucketIndex_, taker_); - ( , , , bucketTakeVars.deposit, ) = _pool.bucketInfo(bucketIndex_); - bucketTakeVars.kickerBond = _getKickerBond(kicker_); - (bucketTakeVars.borrowerLps, ) = _pool.lenderInfo(auctionBucketIndex_, borrower_); - (bucketTakeVars.borrowerDebt, bucketTakeVars.borrowerCollateral, ) = _poolInfo.borrowerInfo(address(_pool), borrower_); - ( , bucketTakeVars.compensatedBucketCollateral, , , ) = _pool.bucketInfo(auctionBucketIndex_); + function _getTakeInfo( + uint256 bucketIndex_, + address kicker_, + address taker_, + uint256 auctionBucketIndex_, + address borrower_ + ) internal view returns(LocalTakeVars memory takeVars) { + takeVars.kickerLps = _getLenderInfo(bucketIndex_, kicker_).lpBalance; + takeVars.takerLps = _getLenderInfo(bucketIndex_, taker_).lpBalance; + takeVars.borrowerLps = _getLenderInfo(auctionBucketIndex_, borrower_).lpBalance; + + takeVars.deposit = _getBucketInfo(bucketIndex_).deposit; + takeVars.kickerBond = _getKickerInfo(kicker_).totalBond; + + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower_); + takeVars.borrowerDebt = borrowerInfo.debt; + takeVars.borrowerCollateral = borrowerInfo.collateral; + } + + // Helper function to calculate borrower penalty and kicker reward in bucket take through events emitted. + function _getBorrowerPenaltyAndKickerReward( + Vm.Log[] memory entries, + uint256 bucketIndex_, + uint256 borrowerDebtRepaid_, + bool depositTake_, + uint256 auctionPrice_ + ) internal view returns(uint256 borrowerPenalty_, uint256 kickerReward_) { + // Kicker lp reward read from `BucketTakeLPAwarded(taker, kicker, lpAwardedTaker, lpAwardedKicker)` event. + (, uint256 kickerLpAward) = abi.decode(entries[0].data, (uint256, uint256)); + kickerReward_ = _rewardedLpToQuoteToken(kickerLpAward, bucketIndex_); + + // Collateral Taken calculated from `BucketTake(borrower, index, amount, collateral, bondChange, isReward)` event. + (, , uint256 collateralTaken, ,) = abi.decode(entries[1].data, (uint256, uint256, uint256, uint256, bool)); + + if (depositTake_) { + borrowerPenalty_ = Maths.ceilWmul(collateralTaken, _priceAt(bucketIndex_)); + } else { + borrowerPenalty_ = Maths.ceilWmul(collateralTaken, auctionPrice_); + } + + borrowerPenalty_ -= borrowerDebtRepaid_; } // Helper function to calculate quote tokens from lps in a bucket irrespective of deposit available. // LP rewarded -> quote token rounded up (as LP rewarded are calculated as rewarded quote token -> LP rounded down) - function rewardedLpToQuoteToken(uint256 lps_, uint256 bucketIndex_) internal view returns(uint256 quoteTokens_) { - (uint256 bucketLP, uint256 bucketCollateral , , uint256 bucketDeposit, ) = _pool.bucketInfo(bucketIndex_); - - quoteTokens_ = Buckets.lpToQuoteTokens( - bucketCollateral, - bucketLP, - bucketDeposit, + function _rewardedLpToQuoteToken( + uint256 lps_, + uint256 bucketIndex_ + ) internal view returns(uint256 quoteTokens_) { + BucketInfo memory bucketInfo = _getBucketInfo(bucketIndex_); + quoteTokens_ = Buckets.lpToQuoteTokens( + bucketInfo.collateral, + bucketInfo.lpBalance, + bucketInfo.deposit, lps_, _priceAt(bucketIndex_), Math.Rounding.Up ); } - -} +} \ No newline at end of file diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol index 830034bd2..758fc33fa 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol @@ -17,11 +17,10 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { function _kickReserveAuction() internal updateLocalStateAndPoolInterest { numberOfCalls['UBReserveHandler.kickReserveAuction']++; - (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); + uint256 claimableReserves = _getReservesInfo().claimableReserves; if (claimableReserves == 0) return; try _pool.kickReserveAuction() { - // **RE11**: Reserves increase by claimableReserves by kickReserveAuction decreaseInReserves += claimableReserves; } catch (bytes memory err) { @@ -41,17 +40,17 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { // ensure actor always has the amount to take reserves _ensureAjnaAmount(_actor, 1e45); - (, uint256 claimableReservesBeforeAction, , , ) = _pool.reservesInfo(); - - try _pool.takeReserves(amount_) { + uint256 claimableReservesBeforeTake = _getReservesInfo().claimableReservesRemaining; - (, uint256 claimableReservesAfterAction, , ,) = _pool.reservesInfo(); + try _pool.takeReserves( + amount_ + ) { + uint256 claimableReservesAfterTake = _getReservesInfo().claimableReservesRemaining; // reserves are guaranteed by the protocol) require( - claimableReservesAfterAction < claimableReservesBeforeAction, + claimableReservesAfterTake < claimableReservesBeforeTake, "QT1: claimable reserve not avaialble to take" ); - } catch (bytes memory err) { _ensurePoolError(err); } diff --git a/tests/forge/invariants/interfaces/IBaseHandler.sol b/tests/forge/invariants/interfaces/IBaseHandler.sol index 4f8e365bb..f14fb1023 100644 --- a/tests/forge/invariants/interfaces/IBaseHandler.sol +++ b/tests/forge/invariants/interfaces/IBaseHandler.sol @@ -27,6 +27,7 @@ interface IBaseHandler { function previousReserves() external view returns(uint256); function increaseInReserves() external view returns(uint256); function decreaseInReserves() external view returns(uint256); + function reservesErrorMargin() external view returns(uint256); function borrowerPenalty() external view returns(uint256); function kickerReward() external view returns(uint256); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol index 75da8e08f..9d4da7fb4 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -565,6 +565,25 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { _liquidationERC20PoolHandler.bucketTake(437995719506453073927178171450999258981540842, 1, false, 10505745254535225521787303755428772101316447294490000685852, 1); invariant_auction(); } + + /* + Test was failing in bucket take due to kicker reward calculated 1 unit more than borrower penalty due to rounding in invariant handler. + Fixed by rounding up borrower penalty calculations. + */ + function test_regression_failure_A8_5() external { + _liquidationERC20PoolHandler.stampLoan(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.transferLps(1000000723349964869, 788851800116510692513009799204, 999999998987189766687331, 1999999999999999999996779233486571999676542926, 17578572791586479054160852518); + _liquidationERC20PoolHandler.pullCollateral(245127557453865308850344, 29075872215974757772618544502249112026637573331041, 320926102985372314407801867544337409222117895231322939819); + _liquidationERC20PoolHandler.moveQuoteToken(3251769792, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 105206693738956439744956997581001974547690524037387997795061, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.removeCollateral(2, 1503807476050804044708293765108412212021375967876200308856270774431709, 3, 2016143937953654763718116642678378945337231563); + _liquidationERC20PoolHandler.kickAuction(20039169419373110383609479377, 1724901769, 709527059171478980285358094387900703425179029953176145265918012141182596, 28813093304904246843464); + _liquidationERC20PoolHandler.stampLoan(113260412868390240841, 1); + _liquidationERC20PoolHandler.stampLoan(0, 3); + _liquidationERC20PoolHandler.addCollateral(62058676330172980020738666, 1000000000038455964, 3123151313420689462484, 311810622805541359080687896812); + _liquidationERC20PoolHandler.withdrawBonds(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC20PoolHandler.bucketTake(57735102665284417941269, 1000000000000000000000999997966378072510645569, false, 144674580308375297229175702508, 2105345777576680966242023933); + invariant_auction(); + } } contract RegressionTestLiquidationWith10BucketsERC20Pool is LiquidationERC20PoolInvariants { diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 1bc920fd0..2c0073aa9 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -1103,6 +1103,33 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_reserves(); } + /** + Test was failing in bucket take because of total reward calculated 1 unit less than taker reward due to rounding. + Fixed by calculating kicker reward only when total reward is greater than taker reward. + */ + function test_regression_bucket_take_arithmetic_over_underflow() external { + _reserveERC20PoolHandler.pullCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1644755755592, 1959341507995549485252446); + _reserveERC20PoolHandler.transferLps(978535271576710676299781512516, 16098465890038718399935149108, 1448369905976755312991, 2000000437612435879631304211889, 2334526172727163946751610); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1957944, 350546778875546746949203120839869581644186, 1909020541887755559056550203807280332183167182459, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.kickAuction(240639932869060867937218082568651310505928854172964882670536199212801389, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 91142045672818924549776487045969516692421, 2); + _reserveERC20PoolHandler.takeAuction(847775274135304201693442802951942471956040628099463426658, 15437905983783, 3021853142853682116706828163678513643058264678516883895459894928, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.moveQuoteToken(218829779082276597765702846551699258628317130832421, 280724425438786478945154682490650546683941835815462758599200198161, 111914756, 123107726091484989021093274023272769706926424743574450590, 179575236363434987440104); + _reserveERC20PoolHandler.addQuoteToken(4297946054336748243086707072598519007417628445, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 6197979715556281575155572087704038006338731777143760188196); + _reserveERC20PoolHandler.moveQuoteToken(167613418362075128105108939402493441018698866633227, 262828624041670935140279333927912328409007891154917147, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); + _reserveERC20PoolHandler.lenderKickAuction(12197725906791943334871217668864585709165849216409181, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 70725521863129038810985623968851609237); + _reserveERC20PoolHandler.withdrawBonds(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 300752541693626254472549156222621163034080128650748453137); + _reserveERC20PoolHandler.bucketTake(34340038744157012785217068122223542629705265691484769346092334880775572, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5); + _reserveERC20PoolHandler.transferLps(155227010272947024654, 3979, 14910698722141667014830239878, 32292219746171378696382686750905, 702470039723650472853373511323619201264376740059252842873620476391647153); + _reserveERC20PoolHandler.pledgeCollateral(15009631535641755367315249199883042255, 1242103542388660490644157825722210605939946175860408653463311, 138153650027265703842616); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 139465, 2365279393851601839489146993905474629987024, 254016223957851329609435735073288789372943399004393360105510319); + _reserveERC20PoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3); + _reserveERC20PoolHandler.settleAuction(2746, 14910898275941098990574623223, 250991308245266349, 391872544809136150711089553100); + _reserveERC20PoolHandler.kickReserveAuction(6316151018785827280299720335, 20172452359986642426397928979); + _reserveERC20PoolHandler.drawDebt(1058746730185409946, 688007029214871145227109798489438459916415067825650097323246830710161407, 1024414730660373408); + _reserveERC20PoolHandler.addCollateral(1690145554, 519988602335840889977616288107, 1741768576, 1000033915538988948); + _reserveERC20PoolHandler.bucketTake(3, 185058816859586233891008435707544391368868631160914389, false, 4530135247013654, 15691707183130113816464097191168520076563613870282071594338041203); + } + /** Test was failing because rewards differ by 1 gwei. Fixed by updating TakerActions to round in favor of the protocol. @@ -1400,6 +1427,21 @@ contract RegressionTestReserveWith8CollateralPrecisionERC20Pool is ReserveERC20P invariant_reserves(); } + + /* + Test failed because RE9 was incorrectly implemented that take below tp (at time of take) doesn't change reserves. + Fixed by updating RE9 implementation to `Reserves are unchanged by take below tp (at time of kick)`. + */ + function test_regression_failure_reserves_on_bucketTake() external { + _reserveERC20PoolHandler.bucketTake(21318, 17209, false, 7256, 2112895574); + _reserveERC20PoolHandler.failed(); + _reserveERC20PoolHandler.removeQuoteToken(134507693871374985842462, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 3133413913960740507); + _reserveERC20PoolHandler.bucketTake(2198, 20978, true, 9160, 2602); + _reserveERC20PoolHandler.takeAuction(19082, 7296, 22883, 1795); + _reserveERC20PoolHandler.bucketTake(3048, 3562, true, 11832, 12708); + + invariant_reserves(); + } } contract RegressionTestReservesWith18QuotePrecision4CollateralPrecisionERC20Pool is ReserveERC20PoolInvariants { @@ -1429,3 +1471,33 @@ contract RegressionTestReservesWith18QuotePrecision4CollateralPrecisionERC20Pool } } + +contract RegressionTestReservesWith8QuotePrecision12CollateralPrecisionERC20Pool is ReserveERC20PoolInvariants { + + function setUp() public override { + vm.setEnv("QUOTE_PRECISION", "8"); + vm.setEnv("COLLATERAL_PRECISION", "12"); + super.setUp(); + } + + /** + Test was failing in bucket take below tp due to Reserves increase with roundings in deposit calculations when auction Price is very small. + Fixed by adding a reserves error acceptance margin of quoteToken/auctionPrice. + */ + function test_regression_bucket_take_re9_failure() external { + _reserveERC20PoolHandler.drawDebt(24508, 6004, 12873); + _reserveERC20PoolHandler.settleAuction(1622320951432348052667116641401087769239742492942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3, 3); + _reserveERC20PoolHandler.drawDebt(24503, 7132, 8782); + _reserveERC20PoolHandler.bucketTake(202710807569127533859817400773714809979026059631061598064177268, 2385357505634384043709761419393984920, true,694050224651817452208361345174057184524500415461429528395753834136733358928, 139804506994); + _reserveERC20PoolHandler.pullCollateral(9052366115474232323230371391544137674838932567014847098128822449097, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.transferLps(1672372126, 2686, 4981, 3704, 22645); + _reserveERC20PoolHandler.moveQuoteToken(3096, 8668, 1318, 4257, 33952); + _reserveERC20PoolHandler.kickAuction(3968, 6743, 3857, 463); + _reserveERC20PoolHandler.settleAuction(12887, 8185, 10534, 7613); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 50125298687992691, 6426773705941355197739888078198041158818108804992591704360862); + _reserveERC20PoolHandler.addCollateral(12853, 17657, 499654491, 20120); + _reserveERC20PoolHandler.bucketTake(10738, 1583, false, 37901, 8140); + invariant_reserves(); + } + +} \ No newline at end of file diff --git a/tests/forge/regression/ERC20Pool/RegressionTestSettleERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestSettleERC20Pool.t.sol index 05ebdce7b..04533c8ca 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestSettleERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestSettleERC20Pool.t.sol @@ -17,7 +17,8 @@ contract RegressionTestSettleERC20Pool is SettleERC20PoolInvariants { */ function test_regression_settle_then_repay() external { _settleERC20PoolHandler.settleDebt(3113042312187095938847976769131078147978133970801631984161493412007580, 71508422573531484609164655, 55359934378837189558162829458006585270105); - _settleERC20PoolHandler.repayDebtByThirdParty(1333, 3439, 3116, 2819); + /* Commented below call as repayDebt is not allowed if borrower is kicked and removed the handler from SettleERC20PoolInvariants. */ + // _settleERC20PoolHandler.repayDebtByThirdParty(1333, 3439, 3116, 2819); invariant_quote(); } } \ No newline at end of file diff --git a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol index 6ac8afd6e..8244569b0 100644 --- a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol +++ b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol @@ -468,6 +468,23 @@ contract RegressionTestReserveERC721Pool is ReserveERC721PoolInvariants { _reserveERC721PoolHandler.lenderKickAuction(1, 2380963819538393125633366019791981, 0); } + /** + Test failed after bucket take with incorrect reserves due to incorrect borrower penalty and kicker reward calculation when borrower and kicker are same and borrower partial collateral is settled + Fixed by calculating borrower penalty and kicker reward from emitted event collateral values to separate kicker lp reward and borrower lp compensation for partial collateral. + */ + function test_regression_bucket_take_reserves_failure() external { + _reserveERC721PoolHandler.takeAuction(5614, 5711, 150000000000000001, 11889); + _reserveERC721PoolHandler.takeAuction(51, 17159, 13313, 1965); + _reserveERC721PoolHandler.failed(); + _reserveERC721PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC721PoolHandler.drawDebt(4037, 1015403262, 413376868404700886282978542123985269303539411280089634060); + _reserveERC721PoolHandler.bucketTake(14110, 4985, false, 1366, 13954); + _reserveERC721PoolHandler.lenderKickAuction(190601454519858205896974049525891537012496008839583350325368160, 2813171022655068506479644642529791249126593717492917545887718879120, 336250622645138163229276253658670571624001551213796); + _reserveERC721PoolHandler.bucketTake(59580, 11550, false, 7965, 6543); + invariant_auction(); + invariant_reserves(); + } + } contract RegressionTestReserveEvmRevertERC721Pool is ReserveERC721PoolInvariants {