diff --git a/Makefile b/Makefile index 8efb142aa..faad73388 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,8 @@ test-swap-load-erc20 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST # Regression Tests test-regression-all : test-regression-erc20 test-regression-erc721 test-regression-prototech -test-regression-erc20 :; forge t --mt test_regression --mc ERC20 --nmc "RealWorldRegression|Prototech" -test-regression-erc721 :; forge t --mt test_regression --mc ERC721 --nmc "RealWorldRegression|Prototech" +test-regression-erc20 :; forge t --mt test_regression --mc ERC20 --nmc "RealWorldRegression|Prototech|Position" +test-regression-erc721 :; forge t --mt test_regression --mc ERC721 --nmc "RealWorldRegression|Prototech|Position" test-regression-rewards :; forge t --mt test_regression --mc Rewards --nmc "RealWorldRegression|Prototech" test-regression-position :; forge t --mt test_regression --mc Position --nmc "RealWorldRegression|Prototech" test-regression-prototech :; forge t --mt test_regression --mc Prototech diff --git a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 51c90a089..5a0c5f0ee 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,35 +141,46 @@ 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); @@ -218,11 +225,12 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl 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/PanicExitERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol index a6932f66d..06990fdf1 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/PanicExitERC20PoolHandler.sol @@ -151,14 +151,12 @@ 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( diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol index 74c352e53..6afd52d6d 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -88,8 +88,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 +105,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 +122,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 +139,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 +155,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 +180,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 +199,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 +228,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 +244,30 @@ 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/PanicExitERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol index 8d16ad0cb..927e29e87 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/PanicExitERC721PoolHandler.sol @@ -149,14 +149,12 @@ 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( 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..b1fe50320 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol @@ -112,7 +112,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 @@ -134,7 +134,6 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, kickTimeBefore, auctionPrice ); - } catch (bytes memory err) { _ensurePoolError(err); } @@ -175,7 +174,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,7 +187,7 @@ 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) { @@ -214,17 +216,17 @@ 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); + _ensureQuoteAmount(_actor, borrowerInfoBeforeAction.debt + 10 * 1e18); try _erc721Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { _recordSettleBucket( _actor, - borrowerCollateralBefore, + borrowerInfoBeforeAction.collateral, kickTimeBefore, auctionPrice ); 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 65607f68b..c214980fc 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,54 @@ 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; + } + + struct AuctionInfo { + address kicker; + uint256 bondFactor; + uint256 bondSize; + uint256 kickTime; + uint256 referencePrice; + uint256 neutralPrice; + uint256 thresholdPrice; + uint256 auctionPrice; + uint256 auctionPriceIndex; + address head; + } + + struct KickerInfo { + uint256 claimableBond; + uint256 lockedBond; + uint256 totalBond; + } + + struct LenderInfo { + uint256 lpBalance; + uint256 depositTime; + } + + struct ReservesInfo { + uint256 reserves; + uint256 claimableReserves; + uint256 claimableReservesRemaining; + uint256 auctionPrice; + uint256 timeRemaining; + } + // Tokens TokenWithNDecimals internal _quote; BurnableToken internal _ajna; @@ -136,11 +185,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 { @@ -167,7 +216,7 @@ abstract contract BaseHandler is Test { (address borrower, , ) = _pool.loansInfo(); if (borrower != address(0)) { - (uint256 debt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + uint256 debt = _getBorrowerInfo(borrower).debt; try vm.startPrank(borrower) { } catch { @@ -224,7 +273,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 { @@ -244,7 +295,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) { @@ -260,6 +319,95 @@ 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_.thresholdPrice, + 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_); + } + + function _getLenderInfo( + uint256 index_, + address lender_ + ) internal view returns (LenderInfo memory lenderInfo_) { + ( + lenderInfo_.lpBalance, + lenderInfo_.depositTime + ) = _pool.lenderInfo(index_, lender_); + } + + 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)); } @@ -348,7 +496,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; @@ -428,8 +576,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; } } @@ -437,21 +584,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; @@ -546,7 +694,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); } } @@ -559,10 +711,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 802e782b3..0b9a96ac1 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -13,7 +13,6 @@ import { Buckets } from 'src/libraries/internal import { BaseHandler } from './BaseHandler.sol'; import '@std/Vm.sol'; -import "@std/console.sol"; abstract contract UnboundedLiquidationPoolHandler is BaseHandler { @@ -29,12 +28,6 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { uint256 borrowerDebt; } - struct AuctionInfo { - uint256 kickTime; - uint256 auctionPrice; - uint256 kickThresholdPrice; - } - /*******************************/ /*** Kicker Helper Functions ***/ /*******************************/ @@ -44,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); } @@ -69,30 +63,32 @@ 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, , ) = _pool.loansInfo(); + 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); } @@ -104,23 +100,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); } @@ -137,32 +134,48 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.takeAuction']++; - (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); - AuctionInfo memory auctionInfo; - (auctionInfo.kickTime, , , , auctionInfo.auctionPrice, , , auctionInfo.kickThresholdPrice, ) = _poolInfo.auctionStatus(address(_pool), borrower_); - uint256 auctionBucketIndex = auctionInfo.auctionPrice < MIN_PRICE ? 7388 : (auctionInfo.auctionPrice > MAX_PRICE ? 0 : _indexOf(auctionInfo.auctionPrice)); + AuctionInfo memory auctionInfo = _getAuctionInfo(borrower_); + LocalTakeVars memory beforeTakeVars = _getTakeInfo( + auctionInfo.auctionPriceIndex, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); - LocalTakeVars memory beforeTakeVars = getTakeInfo(auctionBucketIndex, kicker, _actor, auctionBucketIndex, 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 totalBalanceAfterTake = _quote.balanceOf(address(_pool)) * _pool.quoteTokenScale(); + uint256 totalBalanceAfterTake = _getPoolQuoteBalance(); - LocalTakeVars memory afterTakeVars = getTakeInfo(auctionBucketIndex, kicker, _actor, auctionBucketIndex, borrower_); + LocalTakeVars memory afterTakeVars = _getTakeInfo( + auctionInfo.auctionPriceIndex, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); // **RE7**: Reserves decrease with debt covered by take. decreaseInReserves += beforeTakeVars.borrowerDebt - afterTakeVars.borrowerDebt; // **A8**: kicker reward <= Borrower penalty + // 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) { - borrowerPenalty -= rewardedLpToQuoteToken(afterTakeVars.borrowerLps - beforeTakeVars.borrowerLps, auctionBucketIndex); + // Borrower gets Lps at auction price against fractional collateral added to the bucket. + borrowerPenalty -= _rewardedLpToQuoteToken(afterTakeVars.borrowerLps - beforeTakeVars.borrowerLps, auctionInfo.auctionPriceIndex); } if (beforeTakeVars.kickerBond > afterTakeVars.kickerBond) { @@ -186,7 +199,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { increaseInReserves += totalBalanceAfterTake - totalBalanceBeforeTake; // **RE9**: Reserves unchanged by takes and bucket takes below TP(at the time of kick) - if (auctionInfo.auctionPrice < auctionInfo.kickThresholdPrice) { + if (auctionInfo.auctionPrice < auctionInfo.thresholdPrice) { increaseInReserves = 0; decreaseInReserves = 0; } @@ -199,7 +212,6 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { auctionInfo.auctionPrice ); } - } catch (bytes memory err) { _ensurePoolError(err); } @@ -213,30 +225,44 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.bucketTake']++; - (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - AuctionInfo memory auctionInfo; - ( , , , , auctionInfo.auctionPrice, , , auctionInfo.kickThresholdPrice, ) = _poolInfo.auctionStatus(address(_pool), borrower_); - uint256 auctionBucketIndex = auctionInfo.auctionPrice < MIN_PRICE ? 7388 : (auctionInfo.auctionPrice > MAX_PRICE ? 0 : _indexOf(auctionInfo.auctionPrice)); - - LocalTakeVars memory beforeTakeVars = getTakeInfo(bucketIndex_, kicker, _actor, auctionBucketIndex, borrower_); + AuctionInfo memory auctionInfo = _getAuctionInfo(borrower_); + LocalTakeVars memory beforeTakeVars = _getTakeInfo( + auctionInfo.auctionPriceIndex, + auctionInfo.kicker, + _actor, + auctionInfo.auctionPriceIndex, + borrower_ + ); + // Record event emitted in bucketTake method call to calculate `borrowerPenalty` and `kickerReward` vm.recordLogs(); - try _pool.bucketTake(borrower_, depositTake_, bucketIndex_) { + try _pool.bucketTake( + borrower_, + depositTake_, + bucketIndex_ + ) { numberOfActions['bucketTake']++; - LocalTakeVars memory afterTakeVars = getTakeInfo(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 (afterTakeVars.takerLps > beforeTakeVars.takerLps) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; - Vm.Log[] memory entries = vm.getRecordedLogs(); 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; + lenderDepositTime[auctionInfo.kicker][bucketIndex_] = block.timestamp; } - (borrowerPenalty, kickerReward) = getBorrowerPenaltyAndKickerReward( + // Get emitted events logs in bucketTake + Vm.Log[] memory entries = vm.getRecordedLogs(); + (borrowerPenalty, kickerReward) = _getBorrowerPenaltyAndKickerReward( entries, bucketIndex_, beforeTakeVars.borrowerDebt - afterTakeVars.borrowerDebt, @@ -257,6 +283,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // **A7**: Total Bond decrease by bond penalty on take. decreaseInBonds += beforeTakeVars.kickerBond - afterTakeVars.kickerBond; } + // **R7**: Exchange rates are unchanged under depositTakes // **R8**: Exchange rates are unchanged under arbTakes exchangeRateShouldNotChange[bucketIndex_] = true; @@ -270,17 +297,20 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { uint256 takePrice = _priceAt(bucketIndex_); // **RE9**: Reserves unchanged by takes and bucket takes below TP(at the time of kick) - if (takePrice < auctionInfo.kickThresholdPrice) { + if (takePrice < auctionInfo.thresholdPrice) { 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 ( + _getAuctionInfo(borrower_).kickTime == 0 + && + _pool.poolType() == 1 + ) { + buckets.add(auctionInfo.auctionPriceIndex); if (beforeTakeVars.borrowerLps < afterTakeVars.borrowerLps) { - lenderDepositTime[borrower_][auctionBucketIndex] = block.timestamp; + lenderDepositTime[borrower_][auctionInfo.auctionPriceIndex] = block.timestamp; } } @@ -288,7 +318,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { fenwickDeposits[bucketIndex_] = afterTakeVars.deposit; } catch (bytes memory err) { - // Reset Logs + // Reset event Logs vm.getRecordedLogs(); _ensurePoolError(err); @@ -304,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); @@ -332,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); @@ -359,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; @@ -369,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; } } @@ -393,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; } @@ -403,35 +449,31 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } } - function getTakeInfo(uint256 bucketIndex_, address kicker_, address taker_, uint256 auctionBucketIndex_, address borrower_) internal view returns(LocalTakeVars memory takeVars) { - (takeVars.kickerLps, ) = _pool.lenderInfo(bucketIndex_, kicker_); - (takeVars.takerLps, ) = _pool.lenderInfo(bucketIndex_, taker_); - ( , , , takeVars.deposit, ) = _pool.bucketInfo(bucketIndex_); - takeVars.kickerBond = _getKickerBond(kicker_); - (takeVars.borrowerLps, ) = _pool.lenderInfo(auctionBucketIndex_, borrower_); - (takeVars.borrowerDebt, takeVars.borrowerCollateral,) = _poolInfo.borrowerInfo(address(_pool), borrower_); - } + 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; - // 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_); + takeVars.deposit = _getBucketInfo(bucketIndex_).deposit; + takeVars.kickerBond = _getKickerInfo(kicker_).totalBond; - quoteTokens_ = Buckets.lpToQuoteTokens( - bucketCollateral, - bucketLP, - bucketDeposit, - lps_, - _priceAt(bucketIndex_), - Math.Rounding.Up - ); + BorrowerInfo memory borrowerInfo = _getBorrowerInfo(borrower_); + takeVars.borrowerDebt = borrowerInfo.debt; + takeVars.borrowerCollateral = borrowerInfo.collateral; } - function getBorrowerPenaltyAndKickerReward(Vm.Log[] memory entries, uint256 bucketIndex_, uint256 borrowerDebtRepaid_, bool depositTake_, uint256 auctionPrice_) internal view returns(uint256 borrowerPenalty_, uint256 kickerReward_) { + // 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_); + 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)); @@ -444,4 +486,18 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { 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_) { + 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 c6493c439..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); }