From a8675582e7c9d39a02a8e550d98dd50e185d8a26 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:37:28 +0200 Subject: [PATCH 01/70] Update comment of toBuckets_ param in IRewardsManagerOwnerActions.moveStakedLiquidity function (#666) --- src/interfaces/rewards/IRewardsManagerOwnerActions.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/rewards/IRewardsManagerOwnerActions.sol b/src/interfaces/rewards/IRewardsManagerOwnerActions.sol index f2f314d7b..fd378b0a5 100644 --- a/src/interfaces/rewards/IRewardsManagerOwnerActions.sol +++ b/src/interfaces/rewards/IRewardsManagerOwnerActions.sol @@ -25,7 +25,7 @@ interface IRewardsManagerOwnerActions { * @dev fromBuckets and toBuckets must be the same array length. Liquidity is moved from the fromBuckets to the toBuckets in the same index. * @param tokenId_ ID of the staked LP NFT. * @param fromBuckets_ The list of bucket indexes to move liquidity from. - * @param toBuckets_ The list of bucket indexes to move liquidity from. + * @param toBuckets_ The list of bucket indexes to move liquidity to. * @param expiry_ Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. */ function moveStakedLiquidity( From 22b96f682cf3d876288e85376e69666777c6cd29 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 6 Mar 2023 20:38:50 +0200 Subject: [PATCH 02/70] Apply penalty in case deposit is moved from LUP to below LUP (#665) --- src/libraries/external/LenderActions.sol | 2 +- .../forge/ERC20Pool/ERC20PoolQuoteToken.t.sol | 24 +++++++++++-------- tests/forge/utils/DSTestPlus.sol | 4 ++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index aae4dad18..3ba0569c1 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -272,7 +272,7 @@ library LenderActions { lup_ = _lup(deposits_, poolState_.debt); // apply unutilized deposit fee if quote token is moved from above the LUP to below the LUP - if (vars.fromBucketPrice > lup_ && vars.toBucketPrice <= lup_) { + if (vars.fromBucketPrice >= lup_ && vars.toBucketPrice < lup_) { movedAmount_ = Maths.wmul(movedAmount_, Maths.WAD - _depositFeeRate(poolState_.rate)); } diff --git a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol index 597475dba..a75277d0e 100644 --- a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -1132,16 +1132,19 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { uint256 ptp = Maths.wdiv(poolDebt, 10 * 1e18); assertEq(ptp, 500.480769230769231000 * 1e18); - // lender moves some liquidity below the pool threshold price; penalty should be assessed + // lender moves some liquidity from LUP below the pool threshold price; penalty should be assessed skip(16 hours); + uint256 lupIndex = 2873; + assertEq(_lupIndex(), lupIndex); - _moveLiquidity({ + _moveLiquidityWithPenalty({ from: _lender, amount: 2_500 * 1e18, - fromIndex: 2873, + amountMoved: 2_499.657534246575342500 * 1e18, + fromIndex: lupIndex, toIndex: 2954, lpRedeemFrom: 2_499.902874075010984820 * 1e18, - lpAwardTo: 2_500 * 1e18, + lpAwardTo: 2_499.657534246575342500 * 1e18, newLup: _lup() }); @@ -1156,16 +1159,17 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { newLup: 601.252968524772188572 * 1e18 }); - // lender moves more liquidity; no penalty assessed as sufficient time has passed + // lender moves more liquidity from LUP; penalty should be assessed skip(12 hours); - _moveLiquidity({ + _moveLiquidityWithPenalty({ from: _lender, amount: 2_500 * 1e18, - fromIndex: 2873, + amountMoved: 2_499.691780821917807500 * 1e18, + fromIndex: lupIndex, toIndex: 2954, lpRedeemFrom: 2_499.815331532038893923 * 1e18, - lpAwardTo: 2_500 * 1e18, + lpAwardTo: 2_499.691780821917807500 * 1e18, newLup: _lup() }); @@ -1193,10 +1197,10 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { _removeAllLiquidity({ from: _lender, - amount: 5_000 * 1e18, + amount: 4_999.349315068493150000 * 1e18, index: 2954, newLup: 601.252968524772188572 * 1e18, - lpRedeem: 5_000 * 1e18 + lpRedeem: 4_999.349315068493150000 * 1e18 }); assertGt(_quote.balanceOf(_lender), 200_000 * 1e18); diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 02d279ae7..9331a5359 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -1300,6 +1300,10 @@ abstract contract DSTestPlus is Test, IPoolEvents { ( , , , , lup_, ) = _poolUtils.poolPricesInfo(address(_pool)); } + function _lupIndex() internal view returns (uint256 lupIndex_) { + ( , , , , , lupIndex_ ) = _poolUtils.poolPricesInfo(address(_pool)); + } + function _htp() internal view returns (uint256 htp_) { ( , , htp_, , , ) = _poolUtils.poolPricesInfo(address(_pool)); } From c43b3a44ae3c8d34c5bd0eeaf0837e63250b475b Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 6 Mar 2023 22:31:34 +0200 Subject: [PATCH 03/70] Update comments re kick NP limit index as suggested in review (#664) --- src/interfaces/pool/commons/IPoolLiquidationActions.sol | 6 +++--- src/libraries/external/Auctions.sol | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/interfaces/pool/commons/IPoolLiquidationActions.sol b/src/interfaces/pool/commons/IPoolLiquidationActions.sol index a278b1e45..f24c85a1c 100644 --- a/src/interfaces/pool/commons/IPoolLiquidationActions.sol +++ b/src/interfaces/pool/commons/IPoolLiquidationActions.sol @@ -31,8 +31,8 @@ interface IPoolLiquidationActions { /** * @notice Called by actors to initiate a liquidation. - * @param borrower Identifies the loan to liquidate. - * @param npLimitIndex Lower bound of NP tolerated when kicking the auction. + * @param borrower Identifies the loan to liquidate. + * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. */ function kick( address borrower, @@ -42,7 +42,7 @@ interface IPoolLiquidationActions { /** * @notice Called by lenders to liquidate the top loan using their deposits. * @param index The deposit index to use for kicking the top loan. - * @param npLimitIndex Lower bound of NP tolerated when kicking the auction. + * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. */ function kickWithDeposit( uint256 index, diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index 3bd10bfbc..0f4f2d270 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -335,7 +335,7 @@ library Auctions { * @notice Called to start borrower liquidation and to update the auctions queue. * @param poolState_ Current state of the pool. * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Lower bound of NP tolerated when kicking the auction. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. * @return kickResult_ The result of the kick action. */ function kick( @@ -370,7 +370,7 @@ library Auctions { * - RemoveQuoteToken * @param poolState_ Current state of the pool. * @param index_ The deposit index from where lender removes liquidity. - * @param limitIndex_ Lower bound of NP tolerated when kicking the auction. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. * @return kickResult_ The result of the kick action. */ function kickWithDeposit( @@ -790,7 +790,7 @@ library Auctions { * - Kick * @param poolState_ Current state of the pool. * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Lower bound of NP tolerated when kicking the auction. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. * @param additionalDebt_ Additional debt to be used when calculating proposed LUP. * @return kickResult_ The result of the kick action. */ @@ -824,7 +824,7 @@ library Auctions { // calculate auction params vars.neutralPrice = Maths.wmul(borrower.t0Np, poolState_.inflator); - // check if NP is still greater than the price limit provided by the kicker - done to prevent frontrunning kick auction call with a large amount of loan + // check if NP is not less than price at the limit index provided by the kicker - done to prevent frontrunning kick auction call with a large amount of loan // which will make it harder for kicker to earn a reward and more likely that the kicker is penalized _revertIfPriceDroppedBelowLimit(vars.neutralPrice, limitIndex_); From 99a5f6a6c860bfcee95ab77cfe63a861e5a406ff Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 7 Mar 2023 06:00:38 +0200 Subject: [PATCH 04/70] Pool maintanence (#663) * Consistent naming of functions and events with LPs Reorg interface functions and events based on their action * Move LP transfer logic from Pool to external library * Improve LPs events, introduce Increase/Decrease LPs and include owner address * Fix events comment * Update function comments to reflect new events naming --- src/PositionManager.sol | 2 +- src/base/Pool.sol | 222 +++++-------- src/interfaces/pool/commons/IPoolEvents.sol | 300 ++++++++++-------- .../pool/commons/IPoolLenderActions.sol | 113 ++++--- src/libraries/external/LenderActions.sol | 152 +++++++++ .../ERC20Pool/ERC20PoolTransferLPs.t.sol | 54 ++-- tests/forge/PositionManager.t.sol | 74 ++--- tests/forge/RewardsManager.t.sol | 6 +- 8 files changed, 537 insertions(+), 386 deletions(-) diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 5f665fa8c..2c31b54f9 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -367,7 +367,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R address owner = ownerOf(params_.tokenId); // approve owner to take over the LPs ownership (required for transferLPs pool call) - pool.increaseLPAllowance(owner, params_.indexes, lpAmounts); + pool.increaseLPsAllowance(owner, params_.indexes, lpAmounts); // update pool lps accounting and transfer ownership of lps from PositionManager contract pool.transferLPs(address(this), owner, params_.indexes); diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 9531ba4ee..c8063738c 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -164,128 +164,6 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _transferQuoteTokenFrom(msg.sender, quoteTokenAmountToAdd_); } - /// @inheritdoc IPoolLenderActions - function decreaseLPAllowance( - address spender_, - uint256[] calldata indexes_, - uint256[] calldata amounts_ - ) external override nonReentrant { - mapping(uint256 => uint256) storage allowances = _lpAllowances[msg.sender][spender_]; - - uint256 indexesLength = indexes_.length; - uint256 index; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances[index] -= amounts_[i]; - - unchecked { ++i; } - } - - emit SetLpAllowance( - spender_, - indexes_, - amounts_ - ); - } - - /// @inheritdoc IPoolLenderActions - function increaseLPAllowance( - address spender_, - uint256[] calldata indexes_, - uint256[] calldata amounts_ - ) external override nonReentrant { - mapping(uint256 => uint256) storage allowances = _lpAllowances[msg.sender][spender_]; - - uint256 indexesLength = indexes_.length; - uint256 index; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances[index] += amounts_[i]; - - unchecked { ++i; } - } - - emit SetLpAllowance( - spender_, - indexes_, - amounts_ - ); - } - - /// @inheritdoc IPoolLenderActions - function revokeLPAllowance( - address spender_, - uint256[] calldata indexes_ - ) external override nonReentrant { - mapping(uint256 => uint256) storage allowances = _lpAllowances[msg.sender][spender_]; - - uint256 indexesLength = indexes_.length; - uint256 index; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances[index] = 0; - - unchecked { ++i; } - } - - emit RevokeLpAllowance( - spender_, - indexes_ - ); - } - - /** - * @inheritdoc IPoolLenderActions - * @dev write state: - * - approvedTransferors mapping - */ - function approveLpTransferors( - address[] calldata transferors_ - ) external override { - mapping(address => bool) storage allowances = approvedTransferors[msg.sender]; - - uint256 transferorsLength = transferors_.length; - for (uint256 i = 0; i < transferorsLength; ) { - allowances[transferors_[i]] = true; - - unchecked { ++i; } - } - - emit ApproveLpTransferors( - msg.sender, - transferors_ - ); - } - - /** - * @inheritdoc IPoolLenderActions - * @dev write state: - * - approvedTransferors mapping - */ - function revokeLpTransferors( - address[] calldata transferors_ - ) external override { - mapping(address => bool) storage allowances = approvedTransferors[msg.sender]; - - uint256 transferorsLength = transferors_.length; - for (uint256 i = 0; i < transferorsLength; ) { - delete allowances[transferors_[i]]; - - unchecked { ++i; } - } - - emit RevokeLpTransferors( - msg.sender, - transferors_ - ); - } - /// @inheritdoc IPoolLenderActions function moveQuoteToken( uint256 maxAmountToMove_, @@ -354,22 +232,6 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _transferQuoteToken(msg.sender, removedAmount_); } - /// @inheritdoc IPoolLenderActions - function transferLPs( - address owner_, - address newOwner_, - uint256[] calldata indexes_ - ) external override nonReentrant { - LenderActions.transferLPs( - buckets, - _lpAllowances, - approvedTransferors, - owner_, - newOwner_, - indexes_ - ); - } - /// @inheritdoc IPoolLenderActions function updateInterest() external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); @@ -565,6 +427,90 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _transferQuoteToken(msg.sender, amount_); } + /******************************/ + /*** Transfer LPs Functions ***/ + /******************************/ + + /// @inheritdoc IPoolLenderActions + function increaseLPsAllowance( + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external override nonReentrant { + LenderActions.increaseLPsAllowance( + _lpAllowances[msg.sender][spender_], + spender_, + indexes_, + amounts_ + ); + } + + /// @inheritdoc IPoolLenderActions + function decreaseLPsAllowance( + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external override nonReentrant { + LenderActions.decreaseLPsAllowance( + _lpAllowances[msg.sender][spender_], + spender_, + indexes_, + amounts_ + ); + } + + /// @inheritdoc IPoolLenderActions + function revokeLPsAllowance( + address spender_, + uint256[] calldata indexes_ + ) external override nonReentrant { + LenderActions.revokeLPsAllowance( + _lpAllowances[msg.sender][spender_], + spender_, + indexes_ + ); + } + + /// @inheritdoc IPoolLenderActions + function approveLPsTransferors( + address[] calldata transferors_ + ) external override { + LenderActions.approveLPsTransferors( + approvedTransferors[msg.sender], + transferors_ + ); + } + + /** + * @inheritdoc IPoolLenderActions + * @dev write state: + * - approvedTransferors mapping + */ + function revokeLPsTransferors( + address[] calldata transferors_ + ) external override { + LenderActions.revokeLPsTransferors( + approvedTransferors[msg.sender], + transferors_ + ); + } + + /// @inheritdoc IPoolLenderActions + function transferLPs( + address owner_, + address newOwner_, + uint256[] calldata indexes_ + ) external override nonReentrant { + LenderActions.transferLPs( + buckets, + _lpAllowances, + approvedTransferors, + owner_, + newOwner_, + indexes_ + ); + } + /*****************************/ /*** Pool Helper Functions ***/ /*****************************/ diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index e4adb8631..e2f745a1a 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -6,6 +6,11 @@ pragma solidity 0.8.14; * @title Pool Events */ interface IPoolEvents { + + /*********************/ + /*** Lender events ***/ + /*********************/ + /** * @notice Emitted when lender adds quote token to the pool. * @param lender Recipient that added quote tokens. @@ -23,49 +28,89 @@ interface IPoolEvents { ); /** - * @notice Emitted when lender approves a spender owner of LPs at specified indexes with specified amounts. - * @param spender Address approved to transfer LPs. - * @param indexes Bucket indexes of LPs approved. - * @param amounts LP amounts approved (ordered by approved indexes). + * @notice Emitted when lender moves quote token from a bucket price to another. + * @param lender Recipient that moved quote tokens. + * @param from Price bucket from which quote tokens were moved. + * @param to Price bucket where quote tokens were moved. + * @param amount Amount of quote tokens moved. + * @param lpRedeemedFrom Amount of LP removed from the `from` bucket. + * @param lpAwardedTo Amount of LP credited to the `to` bucket. + * @param lup LUP calculated after removal. */ - event SetLpAllowance( - address indexed spender, - uint256[] indexes, - uint256[] amounts + event MoveQuoteToken( + address indexed lender, + uint256 indexed from, + uint256 indexed to, + uint256 amount, + uint256 lpRedeemedFrom, + uint256 lpAwardedTo, + uint256 lup ); /** - * @notice Emitted when lender whitelists addresses to accept LPs from. - * @param lender Recipient that approves new owner for LPs. - * @param transferors List of addresses that can transfer LPs to lender. + * @notice Emitted when lender removes quote token from the pool. + * @param lender Recipient that removed quote tokens. + * @param index Index at which quote tokens were removed. + * @param amount Amount of quote tokens removed from the pool. + * @param lpRedeemed Amount of LP exchanged for quote token. + * @param lup LUP calculated after removal. */ - event ApproveLpTransferors( + event RemoveQuoteToken( address indexed lender, - address[] transferors + uint256 indexed index, + uint256 amount, + uint256 lpRedeemed, + uint256 lup ); /** - * @notice Emitted when auction is completed. - * @param borrower Address of borrower that exits auction. - * @param collateral Borrower's remaining collateral when auction completed. + * @notice Emitted when lender claims collateral from a bucket. + * @param claimer Recipient that claimed collateral. + * @param index Index at which collateral was claimed. + * @param amount The amount of collateral (or number of NFT tokens) transferred to the claimer. + * @param lpRedeemed Amount of LP exchanged for quote token. */ - event AuctionSettle( + event RemoveCollateral( + address indexed claimer, + uint256 indexed index, + uint256 amount, + uint256 lpRedeemed + ); + + /***********************/ + /*** Borrower events ***/ + /***********************/ + + /** + * @notice Emitted when borrower repays quote tokens to the pool, and/or pulls collateral from the pool. + * @param borrower `msg.sender` or on behalf of sender. + * @param quoteRepaid Amount of quote tokens repaid to the pool. + * @param collateralPulled The amount of collateral (or number of NFT tokens) transferred to the claimer. + * @param lup LUP after repay. + */ + event RepayDebt( address indexed borrower, - uint256 collateral + uint256 quoteRepaid, + uint256 collateralPulled, + uint256 lup ); + /**********************/ + /*** Auction events ***/ + /**********************/ + /** - * @notice Emitted when NFT auction is completed. - * @param borrower Address of borrower that exits auction. - * @param collateral Borrower's remaining collateral when auction completed. - * @param lps Amount of LPs given to the borrower to compensate fractional collateral (if any). - * @param index Index of the bucket with LPs to compensate fractional collateral. + * @notice Emitted when a liquidation is initiated. + * @param borrower Identifies the loan being liquidated. + * @param debt Debt the liquidation will attempt to cover. + * @param collateral Amount of collateral up for liquidation. + * @param bond Bond amount locked by kicker */ - event AuctionNFTSettle( + event Kick( address indexed borrower, + uint256 debt, uint256 collateral, - uint256 lps, - uint256 index + uint256 bond ); /** @@ -80,16 +125,6 @@ interface IPoolEvents { uint256 amount ); - /** - * @notice Emitted when LPs are forfeited as a result of the bucket losing all assets. - * @param index The index of the bucket. - * @param lpForfeited Amount of LP forfeited by lenders. - */ - event BucketBankruptcy( - uint256 indexed index, - uint256 lpForfeited - ); - /** * @notice Emitted when an actor uses quote token to arb higher-priced deposit off the book. * @param borrower Identifies the loan being liquidated. @@ -123,6 +158,23 @@ interface IPoolEvents { uint256 lpAwardedKicker ); + /** + * @notice Emitted when an actor uses quote token outside of the book to purchase collateral under liquidation. + * @param borrower Identifies the loan being liquidated. + * @param amount Amount of quote token used to purchase collateral. + * @param collateral Amount of collateral purchased with quote token (ERC20 pool) or number of NFTs purchased (ERC721 pool). + * @param bondChange Impact of this take to the liquidation bond. + * @param isReward True if kicker was rewarded with `bondChange` amount, false if kicker was penalized. + * @dev amount / collateral implies the auction price. + */ + event Take( + address indexed borrower, + uint256 amount, + uint256 collateral, + uint256 bondChange, + bool isReward + ); + /** * @notice Emitted when an actor settles debt in a completed liquidation * @param borrower Identifies the loan under liquidation. @@ -135,89 +187,27 @@ interface IPoolEvents { ); /** - * @notice Emitted when a liquidation is initiated. - * @param borrower Identifies the loan being liquidated. - * @param debt Debt the liquidation will attempt to cover. - * @param collateral Amount of collateral up for liquidation. - * @param bond Bond amount locked by kicker + * @notice Emitted when auction is completed. + * @param borrower Address of borrower that exits auction. + * @param collateral Borrower's remaining collateral when auction completed. */ - event Kick( + event AuctionSettle( address indexed borrower, - uint256 debt, - uint256 collateral, - uint256 bond - ); - - /** - * @notice Emitted when a loan Neutral Price is restamped. - * @param borrower Identifies the loan to update the Neutral Price. - */ - event LoanStamped( - address indexed borrower - ); - - /** - * @notice Emitted when lender moves quote token from a bucket price to another. - * @param lender Recipient that moved quote tokens. - * @param from Price bucket from which quote tokens were moved. - * @param to Price bucket where quote tokens were moved. - * @param amount Amount of quote tokens moved. - * @param lpRedeemedFrom Amount of LP removed from the `from` bucket. - * @param lpAwardedTo Amount of LP credited to the `to` bucket. - * @param lup LUP calculated after removal. - */ - event MoveQuoteToken( - address indexed lender, - uint256 indexed from, - uint256 indexed to, - uint256 amount, - uint256 lpRedeemedFrom, - uint256 lpAwardedTo, - uint256 lup - ); - - /** - * @notice Emitted when lender claims collateral from a bucket. - * @param claimer Recipient that claimed collateral. - * @param index Index at which collateral was claimed. - * @param amount The amount of collateral (or number of NFT tokens) transferred to the claimer. - * @param lpRedeemed Amount of LP exchanged for quote token. - */ - event RemoveCollateral( - address indexed claimer, - uint256 indexed index, - uint256 amount, - uint256 lpRedeemed - ); - - /** - * @notice Emitted when lender removes quote token from the pool. - * @param lender Recipient that removed quote tokens. - * @param index Index at which quote tokens were removed. - * @param amount Amount of quote tokens removed from the pool. - * @param lpRedeemed Amount of LP exchanged for quote token. - * @param lup LUP calculated after removal. - */ - event RemoveQuoteToken( - address indexed lender, - uint256 indexed index, - uint256 amount, - uint256 lpRedeemed, - uint256 lup + uint256 collateral ); /** - * @notice Emitted when borrower repays quote tokens to the pool, and/or pulls collateral from the pool. - * @param borrower `msg.sender` or on behalf of sender. - * @param quoteRepaid Amount of quote tokens repaid to the pool. - * @param collateralPulled The amount of collateral (or number of NFT tokens) transferred to the claimer. - * @param lup LUP after repay. + * @notice Emitted when NFT auction is completed. + * @param borrower Address of borrower that exits auction. + * @param collateral Borrower's remaining collateral when auction completed. + * @param lps Amount of LPs given to the borrower to compensate fractional collateral (if any). + * @param index Index of the bucket with LPs to compensate fractional collateral. */ - event RepayDebt( + event AuctionNFTSettle( address indexed borrower, - uint256 quoteRepaid, - uint256 collateralPulled, - uint256 lup + uint256 collateral, + uint256 lps, + uint256 index ); /** @@ -232,41 +222,68 @@ interface IPoolEvents { uint256 currentBurnEpoch ); + /***************************/ + /*** LPs transfer events ***/ + /***************************/ + /** - * @notice Emitted when lender removes the allowance of a spender for their LPB. + * @notice Emitted when owner increase the LPs allowance of a spender at specified indexes with specified amounts. + * @param owner LPs owner. + * @param spender Address approved to transfer LPs. + * @param indexes Bucket indexes of LPs approved. + * @param amounts LP amounts added (ordered by indexes). + */ + event IncreaseLPsAllowance( + address indexed owner, + address indexed spender, + uint256[] indexes, + uint256[] amounts + ); + + /** + * @notice Emitted when owner decrease the LPs allowance of a spender at specified indexes with specified amounts. + * @param owner LPs owner. + * @param spender Address approved to transfer LPs. + * @param indexes Bucket indexes of LPs approved. + * @param amounts LP amounts removed (ordered by indexes). + */ + event DecreaseLPsAllowance( + address indexed owner, + address indexed spender, + uint256[] indexes, + uint256[] amounts + ); + + /** + * @notice Emitted when lender removes the allowance of a spender for their LPs. + * @param owner LPs owner. * @param spender Address that is having it's allowance revoked. * @param indexes List of bucket index to remove the allowance from. */ - event RevokeLpAllowance( + event RevokeLPsAllowance( + address indexed owner, address indexed spender, uint256[] indexes ); /** - * @notice Emitted when lender removes addresses from the LPs transferors whitelist. + * @notice Emitted when lender whitelists addresses to accept LPs from. * @param lender Recipient that approves new owner for LPs. - * @param transferors List of addresses that won't be able to transfer LPs to lender anymore. + * @param transferors List of addresses that can transfer LPs to lender. */ - event RevokeLpTransferors( + event ApproveLPsTransferors( address indexed lender, address[] transferors ); /** - * @notice Emitted when an actor uses quote token outside of the book to purchase collateral under liquidation. - * @param borrower Identifies the loan being liquidated. - * @param amount Amount of quote token used to purchase collateral. - * @param collateral Amount of collateral purchased with quote token (ERC20 pool) or number of NFTs purchased (ERC721 pool). - * @param bondChange Impact of this take to the liquidation bond. - * @param isReward True if kicker was rewarded with `bondChange` amount, false if kicker was penalized. - * @dev amount / collateral implies the auction price. + * @notice Emitted when lender removes addresses from the LPs transferors whitelist. + * @param lender Recipient that approves new owner for LPs. + * @param transferors List of addresses that won't be able to transfer LPs to lender anymore. */ - event Take( - address indexed borrower, - uint256 amount, - uint256 collateral, - uint256 bondChange, - bool isReward + event RevokeLPsTransferors( + address indexed lender, + address[] transferors ); /** @@ -284,6 +301,28 @@ interface IPoolEvents { uint256 lps ); + /**************************/ + /*** Pool common events ***/ + /**************************/ + + /** + * @notice Emitted when LPs are forfeited as a result of the bucket losing all assets. + * @param index The index of the bucket. + * @param lpForfeited Amount of LP forfeited by lenders. + */ + event BucketBankruptcy( + uint256 indexed index, + uint256 lpForfeited + ); + + /** + * @notice Emitted when a loan Neutral Price is restamped. + * @param borrower Identifies the loan to update the Neutral Price. + */ + event LoanStamped( + address indexed borrower + ); + /** * @notice Emitted when pool interest rate is updated. * @param oldRate Old pool interest rate. @@ -293,4 +332,5 @@ interface IPoolEvents { uint256 oldRate, uint256 newRate ); + } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolLenderActions.sol b/src/interfaces/pool/commons/IPoolLenderActions.sol index c2bba912a..62be6094f 100644 --- a/src/interfaces/pool/commons/IPoolLenderActions.sol +++ b/src/interfaces/pool/commons/IPoolLenderActions.sol @@ -6,6 +6,11 @@ pragma solidity 0.8.14; * @title Pool Lender Actions */ interface IPoolLenderActions { + + /*********************************************/ + /*** Quote/collateral management functions ***/ + /*********************************************/ + /** * @notice Called by lenders to add an amount of credit at a specified price bucket. * @param amount The amount of quote token to be added by a lender. @@ -19,50 +24,6 @@ interface IPoolLenderActions { uint256 expiry ) external returns (uint256 lpbChange); - /** - * @notice Called by lenders to approve transfer of an amount of LPs to a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LPs. - * @param indexes Bucket indexes from where LPs are transferred. - * @param amounts The amounts of LPs approved to transfer. - */ - function increaseLPAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts - ) external; - - /** - * @notice Called by lenders to decrease the amount of LPs that can be spend by a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LPs. - * @param indexes Bucket indexes from where LPs are transferred. - * @param amounts The amounts of LPs approved to transfer. - */ - function decreaseLPAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts - ) external; - - /** - * @notice Called by lenders to allow addresses that can transfer LPs. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are allowed to transfer LPs to lender. - */ - function approveLpTransferors( - address[] calldata transferors - ) external; - - /** - * @notice Called by lenders to revoke addresses that can transfer LPs. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are revoked to transfer LPs to lender. - */ - function revokeLpTransferors( - address[] calldata transferors - ) external; - /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket. * @param maxAmount The maximum amount of quote token to be moved by a lender. @@ -104,16 +65,73 @@ interface IPoolLenderActions { uint256 index ) external returns (uint256 quoteTokenAmount, uint256 lpAmount); + /********************************/ + /*** Interest update function ***/ + /********************************/ + + /** + * @notice Called by actors to update pool interest rate (can be updated only once in a 12 hours period of time). + */ + function updateInterest() external; + + /******************************/ + /*** LPs transfer functions ***/ + /******************************/ + + /** + * @notice Called by lenders to approve transfer of an amount of LPs to a new owner. + * @dev Intended for use by the PositionManager contract. + * @param spender The new owner of the LPs. + * @param indexes Bucket indexes from where LPs are transferred. + * @param amounts The amounts of LPs approved to transfer. + */ + function increaseLPsAllowance( + address spender, + uint256[] calldata indexes, + uint256[] calldata amounts + ) external; + + /** + * @notice Called by lenders to decrease the amount of LPs that can be spend by a new owner. + * @dev Intended for use by the PositionManager contract. + * @param spender The new owner of the LPs. + * @param indexes Bucket indexes from where LPs are transferred. + * @param amounts The amounts of LPs disapproved to transfer. + */ + function decreaseLPsAllowance( + address spender, + uint256[] calldata indexes, + uint256[] calldata amounts + ) external; + /** * @notice Called by lenders to decrease the amount of LPs that can be spend by a new owner. * @param spender Address that is having it's allowance revoked. * @param indexes List of bucket index to remove the allowance from. */ - function revokeLPAllowance( + function revokeLPsAllowance( address spender, uint256[] calldata indexes ) external; + /** + * @notice Called by lenders to allow addresses that can transfer LPs. + * @dev Intended for use by the PositionManager contract. + * @param transferors Addresses that are allowed to transfer LPs to lender. + */ + function approveLPsTransferors( + address[] calldata transferors + ) external; + + /** + * @notice Called by lenders to revoke addresses that can transfer LPs. + * @dev Intended for use by the PositionManager contract. + * @param transferors Addresses that are revoked to transfer LPs to lender. + */ + function revokeLPsTransferors( + address[] calldata transferors + ) external; + /** * @notice Called by lenders to transfers their LPs to a different address. approveLpOwnership needs to be run first * @dev Used by PositionManager.memorializePositions(). @@ -126,9 +144,4 @@ interface IPoolLenderActions { address newOwner, uint256[] calldata indexes ) external; - - /** - * @notice Called by lenders to update pool interest rate (can be updated only once in a 12 hours period of time). - */ - function updateInterest() external; } diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index 3ba0569c1..402c6cb64 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -69,6 +69,12 @@ library LenderActions { event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event MoveQuoteToken(address indexed lender, uint256 indexed from, uint256 indexed to, uint256 amount, uint256 lpRedeemedFrom, uint256 lpAwardedTo, uint256 lup); event RemoveQuoteToken(address indexed lender, uint256 indexed index, uint256 amount, uint256 lpRedeemed, uint256 lup); + + event ApproveLPsTransferors(address indexed lender, address[] transferors); + event RevokeLPsTransferors(address indexed lender, address[] transferors); + event IncreaseLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); + event DecreaseLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); + event RevokeLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes); event TransferLPs(address owner, address newOwner, uint256[] indexes, uint256 lps); /**************/ @@ -565,6 +571,152 @@ library LenderActions { } } + /******************************/ + /*** Transfer LPs Functions ***/ + /******************************/ + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - increment LPs allowances + * @dev emit events: + * - IncreaseLPsAllowance + */ + function increaseLPsAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external { + uint256 indexesLength = indexes_.length; + uint256 index; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] += amounts_[i]; + + unchecked { ++i; } + } + + emit IncreaseLPsAllowance( + msg.sender, + spender_, + indexes_, + amounts_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - decrement LPs allowances + * @dev emit events: + * - DecreaseLPsAllowance + */ + function decreaseLPsAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external { + uint256 indexesLength = indexes_.length; + uint256 index; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] -= amounts_[i]; + + unchecked { ++i; } + } + + emit DecreaseLPsAllowance( + msg.sender, + spender_, + indexes_, + amounts_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - decrement LPs allowances + * @dev emit events: + * - RevokeLPsAllowance + */ + function revokeLPsAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_ + ) external { + uint256 indexesLength = indexes_.length; + uint256 index; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] = 0; + + unchecked { ++i; } + } + + emit RevokeLPsAllowance( + msg.sender, + spender_, + indexes_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - approvedTransferors mapping + * @dev emit events: + * - ApproveLPsTransferors + */ + function approveLPsTransferors( + mapping(address => bool) storage allowances_, + address[] calldata transferors_ + ) external { + uint256 transferorsLength = transferors_.length; + for (uint256 i = 0; i < transferorsLength; ) { + allowances_[transferors_[i]] = true; + + unchecked { ++i; } + } + + emit ApproveLPsTransferors( + msg.sender, + transferors_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - approvedTransferors mapping + * @dev emit events: + * - RevokeLPsTransferors + */ + function revokeLPsTransferors( + mapping(address => bool) storage allowances_, + address[] calldata transferors_ + ) external { + uint256 transferorsLength = transferors_.length; + for (uint256 i = 0; i < transferorsLength; ) { + delete allowances_[transferors_[i]]; + + unchecked { ++i; } + } + + emit RevokeLPsTransferors( + msg.sender, + transferors_ + ); + } + /** * @notice See `IPoolLenderActions` for descriptions * @dev write state: diff --git a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol index c162f03da..5393246fa 100644 --- a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol @@ -25,7 +25,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender2); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); } /**************************/ @@ -52,7 +52,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { approveIndexes[0] = 2550; uint256[] memory amounts = new uint256[](1); amounts[0] = 1_000 * 1e18; - _pool.increaseLPAllowance(address(0), approveIndexes, amounts); + _pool.increaseLPsAllowance(address(0), approveIndexes, amounts); _assertTransferNoAllowanceRevert({ operator: _lender, @@ -74,7 +74,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 1_000 * 1e18; amounts[1] = 1_000 * 1e18; amounts[2] = 1_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); _assertTransferNoAllowanceRevert({ operator: _lender, @@ -96,7 +96,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 1_000 * 1e18; amounts[1] = 1_000 * 1e18; amounts[2] = 1_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); _assertTransferInvalidIndexRevert({ operator: _lender, @@ -126,7 +126,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 10_000 * 1e18; amounts[1] = 30_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); // only the lender's available balance should be transferred to the new owner _transferLPs({ @@ -165,10 +165,10 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender1); uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPAllowance(_lender1, indexes, amounts); + _pool.increaseLPsAllowance(_lender1, indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _assertLenderLpBalance({ lender: _lender1, @@ -253,7 +253,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 10_000 * 1e18; amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); // transfer LPs for all indexes _transferLPs({ @@ -379,7 +379,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 10_000 * 1e18; amounts[1] = 30_000 * 1e18; - _pool.increaseLPAllowance(_lender2, transferIndexes, amounts); + _pool.increaseLPsAllowance(_lender2, transferIndexes, amounts); // transfer LPs for 2 indexes _transferLPs({ @@ -523,7 +523,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 10_000 * 1e18; amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); _assertLpAllowance({ owner: _lender1, @@ -623,8 +623,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; vm.expectEmit(true, true, false, true); - emit SetLpAllowance(_lender2, indexes, amounts); - _pool.increaseLPAllowance(_lender2, indexes, amounts); + emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); assertTrue(_pool.approvedTransferors(_lender2, _lender)); @@ -633,8 +633,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { address[] memory transferors = new address[](1); transferors[0] = _lender; vm.expectEmit(true, true, false, true); - emit RevokeLpTransferors(_lender2, transferors); - _pool.revokeLpTransferors(transferors); + emit RevokeLPsTransferors(_lender2, transferors); + _pool.revokeLPsTransferors(transferors); assertFalse(_pool.approvedTransferors(_lender2, _lender)); // transfer initiated by lender should fail as it is no longer an approved transferor @@ -645,8 +645,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { // reapprove transferor changePrank(_lender2); vm.expectEmit(true, true, false, true); - emit ApproveLpTransferors(_lender2, transferors); - _pool.approveLpTransferors(transferors); + emit ApproveLPsTransferors(_lender2, transferors); + _pool.approveLPsTransferors(transferors); assertTrue(_pool.approvedTransferors(_lender2, _lender)); // transfer LPs for all indexes @@ -672,8 +672,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender1); vm.expectEmit(true, true, false, true); - emit SetLpAllowance(_lender2, indexes, amounts); - _pool.increaseLPAllowance(_lender2, indexes, amounts); + emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); // check allowance after increasing allowance _assertLpAllowance({ @@ -691,8 +691,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 5_000 * 1e18; amounts[1] = 5_000 * 1e18; vm.expectEmit(true, true, false, true); - emit SetLpAllowance(_lender2, indexes, amounts); - _pool.decreaseLPAllowance(_lender2, indexes, amounts); + emit DecreaseLPsAllowance(_lender1, _lender2, indexes, amounts); + _pool.decreaseLPsAllowance(_lender2, indexes, amounts); // check allowances after decreasing allowance _assertLpAllowance({ @@ -716,8 +716,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 0; amounts[1] = 0; vm.expectEmit(true, true, false, true); - emit RevokeLpAllowance(_lender2, indexes); - _pool.revokeLPAllowance(_lender2, indexes); + emit RevokeLPsAllowance(_lender1, _lender2, indexes); + _pool.revokeLPsAllowance(_lender2, indexes); // check allowance after revoking allowance _assertLpAllowance({ @@ -739,8 +739,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts = new uint256[](1); amounts[0] = 5_000 * 1e18; vm.expectEmit(true, true, false, true); - emit SetLpAllowance(_lender2, indexes, amounts); - _pool.increaseLPAllowance(_lender2, indexes, amounts); + emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); _assertLpAllowance({ owner: _lender1, @@ -770,17 +770,17 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 5_000 * 1e18; amounts[1] = 10_000 * 1e18; - _pool.increaseLPAllowance(_lender2, indexes, amounts); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); amounts = new uint256[](2); amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; - _pool.increaseLPAllowance(_lender, indexes, amounts); + _pool.increaseLPsAllowance(_lender, indexes, amounts); // lender 2 approves lender as transferor of LPs changePrank(_lender2); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // lender transfers allowed LPs from lender1 _transferLPs({ diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/PositionManager.t.sol index 85b7639ca..0481121fb 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/PositionManager.t.sol @@ -31,11 +31,11 @@ abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract _quote.approve(address(_pool), type(uint256).max); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); vm.prank(operator_); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); } /** @@ -168,7 +168,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -276,7 +276,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -410,7 +410,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -620,7 +620,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), transferIndexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), transferIndexes, amounts); // memorialize lender 1 quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -694,7 +694,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts = new uint256[](2); amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), transferIndexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), transferIndexes, amounts); // memorialize lender 2 quote tokens into minted NFT uint256[] memory newIndexes = new uint256[](2); @@ -880,11 +880,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 2_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1001,8 +1001,8 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract changePrank(testMinter); _pool.addQuoteToken(amounts[0], _i9_91, type(uint256).max); - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); - _pool.approveLpTransferors(transferors); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.approveLPsTransferors(transferors); memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes ); @@ -1106,12 +1106,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // allow position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1161,7 +1161,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check new owner can redeem positions changePrank(testReceiver); // allow position manager as transferor - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -1245,7 +1245,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -1303,7 +1303,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -1449,12 +1449,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // approve position manager as a transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1593,7 +1593,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[0] = mintIndex; uint256[] memory amounts = new uint256[](1); amounts[0] = 2_500 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testAddress1 IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1712,7 +1712,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testAddress2 changePrank(testAddress2); amounts[0] = 5_500 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testAddress2 memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1887,11 +1887,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -2054,11 +2054,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -2137,7 +2137,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract emit TransferLPs(address(_positionManager), testReceiver, indexes, 15_000 * 1e18); vm.expectEmit(true, true, true, true); emit RedeemPosition(testReceiver, tokenId, indexes); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _positionManager.reedemPositions(reedemParams); // check pool state @@ -2221,10 +2221,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // 3rd party minter mints NFT and memorialize lender positions uint256 tokenId = _mintNFT(minter, lender, address(_pool)); @@ -2365,7 +2365,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // 3rd party minter mints NFT and memorialize lender positions changePrank(minter); @@ -2390,7 +2390,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // minter approves position manager as a transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -2472,7 +2472,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize position IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -2548,8 +2548,8 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position changePrank(addresses[i]); - _pool.approveLpTransferors(transferors); - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.approveLPsTransferors(transferors); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -2724,12 +2724,12 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -2851,10 +2851,10 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); @@ -3001,7 +3001,7 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac // check new owner can redeem positions changePrank(testAddress2); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); _positionManager.reedemPositions(reedemParams); // check pool state diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index 3ae24e187..91d2416c8 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -257,7 +257,7 @@ contract RewardsManagerTest is ERC20HelperContract { (lpBalances[i], ) = params_.pool.lenderInfo(params_.indexes[i], params_.minter); } - params_.pool.increaseLPAllowance(address(_positionManager), params_.indexes, lpBalances); + params_.pool.increaseLPsAllowance(address(_positionManager), params_.indexes, lpBalances); // construct memorialize params struct IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -623,13 +623,13 @@ contract RewardsManagerTest is ERC20HelperContract { deal(address(_quote), _minterOne, 500_000_000 * 1e18); _quote.approve(address(_pool), type(uint256).max); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); changePrank(_minterTwo); deal(address(_quote), _minterTwo, 500_000_000 * 1e18); _quote.approve(address(_pool), type(uint256).max); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLpTransferors(transferors); + _pool.approveLPsTransferors(transferors); /*****************************/ /*** Initialize Pool State ***/ From 098029bcbf99e2c138af0b49040cc6ae0d076247 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Wed, 8 Mar 2023 06:46:05 +0200 Subject: [PATCH 05/70] Gracefully revert when collateral pull (#670) - if borrower pledged collateral is lower than encumbered collateral then revert with InsufficientCollateral --- src/libraries/external/BorrowerActions.sol | 5 ++++- .../forge/ERC20Pool/ERC20PoolCollateral.t.sol | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index e7521195e..eee38aaa4 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -382,7 +382,10 @@ library BorrowerActions { uint256 encumberedCollateral = borrower.t0Debt != 0 ? Maths.wdiv(vars.borrowerDebt, result_.newLup) : 0; - if (borrower.collateral - encumberedCollateral < collateralAmountToPull_) revert InsufficientCollateral(); + if ( + borrower.collateral < encumberedCollateral || + borrower.collateral - encumberedCollateral < collateralAmountToPull_ + ) revert InsufficientCollateral(); // stamp borrower t0Np when pull collateral action vars.stampT0Np = true; diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol index 5a8d6b1a6..962dcd2aa 100644 --- a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol @@ -3,6 +3,9 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { ERC20Pool } from 'src/ERC20Pool.sol'; + +import 'src/interfaces/pool/IPool.sol'; import 'src/PoolInfoUtils.sol'; import 'src/libraries/helpers/PoolHelper.sol'; @@ -1005,4 +1008,20 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { }); } + + function testPullBorrowerCollateralLessThanEncumberedCollateral() external { + address actor = makeAddr("actor"); + + _mintCollateralAndApproveTokens(actor, 1000000000 * 1e18); + _mintQuoteAndApproveTokens(actor, 1000000000000 * 1e18); + + changePrank(actor); + _pool.addQuoteToken(913597152782868931694946846442, 2572, block.timestamp + 100); + ERC20Pool(address(_pool)).drawDebt(actor, 456798576391434465847473423221, 7388, 170152459663184217402759609); + + vm.warp(1689742127); + // borrower is undercollateralized and pledged collateral is lower than encumbered collateral, tx should revert with InsufficientCollateral + vm.expectRevert(IPoolErrors.InsufficientCollateral.selector); + ERC20Pool(address(_pool)).repayDebt(actor, 0, 149220, actor, 7388); + } } From 51643b3187abbfab44a860e6d2c01b9a8a88f059 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Wed, 8 Mar 2023 19:48:06 +0200 Subject: [PATCH 06/70] Revert if encumbered collateral is calculated as zero for non zero debt (#673) - encumbered collateral can be calculated as zero when tiny debt and a big LUP (encumbered = debt / LUP) - in such case borrower could pull collateral leaving pool in a state where borrower has debt but not reflected in loans heap or auction queue - fix scenario by reverting when encumbered collateral is 0 but debt is greater than 0 --- src/libraries/external/BorrowerActions.sol | 4 ++-- .../forge/ERC20Pool/ERC20PoolCollateral.t.sol | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index eee38aaa4..7c31123b7 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -380,9 +380,9 @@ library BorrowerActions { _revertIfPriceDroppedBelowLimit(result_.newLup, limitIndex_); - uint256 encumberedCollateral = borrower.t0Debt != 0 ? Maths.wdiv(vars.borrowerDebt, result_.newLup) : 0; - + uint256 encumberedCollateral = Maths.wdiv(vars.borrowerDebt, result_.newLup); if ( + borrower.t0Debt != 0 && encumberedCollateral == 0 || // case when small amount of debt at a high LUP results in encumbered collateral calculated as 0 borrower.collateral < encumberedCollateral || borrower.collateral - encumberedCollateral < collateralAmountToPull_ ) revert InsufficientCollateral(); diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol index 962dcd2aa..0c5ebbc33 100644 --- a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol @@ -1024,4 +1024,22 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { vm.expectRevert(IPoolErrors.InsufficientCollateral.selector); ERC20Pool(address(_pool)).repayDebt(actor, 0, 149220, actor, 7388); } + + function testPullBorrowerWithDebtCollateralEncumberedCalculatedAsZero() external { + address actor = makeAddr("actor"); + + _mintCollateralAndApproveTokens(actor, 1000000000 * 1e18); + _mintQuoteAndApproveTokens(actor, 1000000000000 * 1e18); + + changePrank(actor); + _pool.addQuoteToken(200, 2572, block.timestamp + 100); + ERC20Pool(address(_pool)).drawDebt(actor, 100, 7388, 1); + + // actor should not be able to pull his collateral without repaying the debt + vm.expectRevert(IPoolErrors.InsufficientCollateral.selector); + ERC20Pool(address(_pool)).repayDebt(actor, 0, 1, actor, 7388); + + // borrower should be able to repay and pull collateral + ERC20Pool(address(_pool)).repayDebt(actor, 120, 1, actor, 7388); + } } From eeda748e6ac9373c94362743315f8d6f6f3ba48e Mon Sep 17 00:00:00 2001 From: mattcushman <36414299+mattcushman@users.noreply.github.com> Date: Thu, 9 Mar 2023 11:43:27 -0500 Subject: [PATCH 07/70] Added test for pledging loan with huge collateral, effect on rates and EMAs (#667) Co-authored-by: mwc --- .../ERC20PoolInterestRateAndEMAs.t.sol | 218 +++++++++++++++++- 1 file changed, 217 insertions(+), 1 deletion(-) diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index c0caa25be..a4b225213 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -642,7 +642,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { skip(10 days); - // borrower 1 borrows 500 quote from the pool + // borrower 1 borrows 10 quote from the pool _borrow({ from: _borrower, amount: 10 * 1e18, @@ -674,6 +674,222 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { depositEma: 19_999.990463256835940000 * 1e18 }); } + function testPoolLargeCollateralPostedTargetUtilization() external tearDown { + + // add initial quote to the pool + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 3_010 + }); + _addInitialLiquidity({ + from: _lender, + amount: 20_000 * 1e18, + index: 2_995 + }); + + _assertPool( + PoolParams({ + htp: 0, + lup: MAX_PRICE, + poolSize: 40_000 * 1e18, + pledgedCollateral: 0, + encumberedCollateral: 0, + poolDebt: 0, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 0, + loans: 0, + maxBorrower: address(0), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, + depositEma: 20_000 * 1e18 + }); + + // borrower 1 borrows 10000 quote from the pool + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 10_000 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); + + _assertPool( + PoolParams({ + htp: 200.192307692307692400 * 1e18, + lup: 327.188250324085203338 * 1e18, + poolSize: 40_000 * 1e18, + pledgedCollateral: 50 * 1e18, + encumberedCollateral: 30.592832642066761971 * 1e18, + poolDebt: 10009.615384615384620000 * 1e18, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 1000.961538461538462000 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, + depositEma: 20_000 * 1e18 + }); + + // borrower 2 borrows 9000 quote from the pool + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 50 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 9000 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); + + _assertPool( + PoolParams({ + htp: 200.192307692307692400 * 1e18, + lup: 327.188250324085203338 * 1e18, + poolSize: 40_000 * 1e18, + pledgedCollateral: 100 * 1e18, + encumberedCollateral: 58.126382019926847745 * 1e18, + poolDebt: 19_018.269230769230778000 * 1e18, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 950.913461538461538900 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, + depositEma: 20_000 * 1e18 + }); + + skip(10 days); + + // borrower 1 borrows 10 quote from the pool + _borrow({ + from: _borrower, + amount: 10 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); + + _assertPool( + PoolParams({ + htp: 200.941998518054562754 * 1e18, + lup: 327.188250324085203338 * 1e18, + poolSize: 40_022.159734498291800000 * 1e18, + pledgedCollateral: 100 * 1e18, + encumberedCollateral: 58.236654596124865142 * 1e18, + poolDebt: 19_054.349122034189453676 * 1e18, + actualUtilization: 0.476358955196505217 * 1e18, + targetUtilization: 0.584010402015984926 * 1e18, + minDebtAmount: 952.717456101709472684 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 3635_946.432113250913630238 * 1e18, + lupt0DebtEma: 6225_824.779082841691329479 * 1e18, + debtEma: 19_054.349122034189453676 * 1e18, + depositEma: 39_999.980926513671880000 * 1e18 + }); + + skip(10 days); + + // borrower 3 pledges enormous qty (50,000) of collateral and takes tiny debt (12 QT) + _pledgeCollateral({ + from: _borrower3, + borrower: _borrower3, + amount: 50_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 12 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); + _assertPool( + PoolParams({ + htp: 201.493279375818240993 * 1e18, + lup: 327.188250324085203338 * 1e18, + poolSize: 40_045.121523671487120000 * 1e18, + pledgedCollateral: 50_100 * 1e18, + encumberedCollateral: 58.353196900686720280 * 1e18, + poolDebt: 19_092.480394752519489737 * 1e18, + actualUtilization: 0.476094974846641824 * 1e18, + targetUtilization: 0.584010402015984926 * 1e18, + minDebtAmount: 954.624019737625974487 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 3_635_946.432113250913630238 * 1e18, + lupt0DebtEma: 6_225_824.779082841691329479 * 1e18, + debtEma: 19_054.349122034189453676 * 1e18, + depositEma: 40_022.159713346932256568 * 1e18 + }); + + skip(1 days); + // borrower 3 touches the pool again to force an interest rate update + _borrow({ + from: _borrower2, + amount: 3.14 * 1e18, + indexLimit: 3_010, + newLup: 327.188250324085203338 * 1e18 + }); + _assertPool( + PoolParams({ + htp: 201.548490576836267720 * 1e18, + lup: 327.188250324085203338 * 1e18, + poolSize: 40_047.420826509653040000 * 1e18, + pledgedCollateral: 50_100 * 1e18, + encumberedCollateral: 58.370797186283932430 * 1e18, + poolDebt: 19_098.239001402275530018 * 1e18, + actualUtilization: 0.476604459561723991 * 1e18, + targetUtilization: 0.584213148132606714 * 1e18, // big col. deposit barely affects + minDebtAmount: 954.911950070113776501 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 3_637_620.071316321624676289 * 1e18, // big col. deposit barely affects + lupt0DebtEma: 6_226_528.935447105141859984 * 1e18, + debtEma: 19_082.947576572936979769 * 1e18, + depositEma: 40_039.381071090348403568 * 1e18 + }); + } function testAccruePoolInterestHtpGtMaxPrice() external tearDown { _addLiquidityNoEventCheck({ From 55b5508aeff43024d71c68818eaa5fe328c9187f Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 9 Mar 2023 18:48:36 +0200 Subject: [PATCH 08/70] HPB bankruptcy on settle (#675) - if there's only a tiny amount of deposit (2) backed by 2 LPs in HPB then the collateralUsed to settle is calculated as 0 due to rounding (vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price)) - this results in removing deposit (2) and not adding any collateral into bucket, leaving an amount of 2 LPs that is not backed by any asset - fixed by declaring bucket bankruptcy in case settle leaves bucket with 0 collateral, 0 deposit but LPs > 0 - added check for deposit to remove to be min of available deposit in bucket / calculated deposit for settle in order to prevent any potential underflow - unit test --- src/libraries/external/Auctions.sol | 77 ++++++++++++------- .../ERC20PoolLiquidationsSettle.t.sol | 68 ++++++++++++++++ 2 files changed, 118 insertions(+), 27 deletions(-) diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index 0f4f2d270..b8d8d6488 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -105,15 +105,18 @@ library Auctions { uint256 redeemedLPs; // [WAD] LPs used by kick action } struct SettleLocalVars { - uint256 collateralUsed; // [WAD] collateral used to settle debt - uint256 debt; // [WAD] debt to settle - uint256 depositToRemove; // [WAD] deposit used by settle auction - uint256 index; // index of settling bucket - uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral - uint256 price; // [WAD] price of settling bucket - uint256 scaledDeposit; // [WAD] scaled amount of quote tokens in bucket - uint256 scale; // [WAD] scale of settling bucket - uint256 unscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket + uint256 collateralUsed; // [WAD] collateral used to settle debt + uint256 debt; // [WAD] debt to settle + uint256 depositToRemove; // [WAD] deposit used by settle auction + uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket + uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle + uint256 hpbLPs; // [WAD] amount of LPs in HPB bucket + uint256 index; // index of settling bucket + uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral + uint256 price; // [WAD] price of settling bucket + uint256 scaledDeposit; // [WAD] scaled amount of quote tokens in bucket + uint256 scale; // [WAD] scale of settling bucket + uint256 unscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket } struct TakeLocalVars { uint256 auctionPrice; // [WAD] The price of auction. @@ -217,44 +220,64 @@ library Auctions { SettleLocalVars memory vars; (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); - vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); - vars.price = _priceAt(vars.index); + vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); + vars.unscaledDeposit = vars.hpbUnscaledDeposit; + vars.price = _priceAt(vars.index); if (vars.unscaledDeposit != 0) { - vars.debt = Maths.wmul(borrower.t0Debt, params_.inflator); // current debt to be settled - vars.maxSettleableDebt = Maths.wmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral + vars.debt = Maths.wmul(borrower.t0Debt, params_.inflator); // current debt to be settled + vars.maxSettleableDebt = Maths.wmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); // enough deposit in bucket and collateral avail to settle entire debt if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { - borrower.t0Debt = 0; // no remaining debt to settle - - vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); // remove only what's needed to settle the debt + // remove only what's needed to settle the debt + vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); vars.collateralUsed = Maths.wdiv(vars.debt, vars.price); - } + // settle the entire debt + borrower.t0Debt = 0; + } // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount else if (vars.maxSettleableDebt >= vars.scaledDeposit) { - borrower.t0Debt -= Maths.wdiv(vars.scaledDeposit, params_.inflator); // subtract from debt the corresponding t0 amount of deposit - vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); - } + // subtract from debt the corresponding t0 amount of deposit + borrower.t0Debt -= Maths.wdiv(vars.scaledDeposit, params_.inflator); + } // settle constrained by collateral available else { - borrower.t0Debt -= Maths.wdiv(vars.maxSettleableDebt, params_.inflator); - vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); vars.collateralUsed = borrower.collateral; + + borrower.t0Debt -= Maths.wdiv(vars.maxSettleableDebt, params_.inflator); } - borrower.collateral -= vars.collateralUsed; // move settled collateral from loan into bucket - buckets_[vars.index].collateral += vars.collateralUsed; + // remove settled collateral from loan + borrower.collateral -= vars.collateralUsed; - Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) - } + Bucket storage hpb = buckets_[vars.index]; + vars.hpbLPs = hpb.lps; + vars.hpbCollateral = hpb.collateral + vars.collateralUsed; + + // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) + vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); + vars.hpbUnscaledDeposit -= vars.unscaledDeposit; + + // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) + Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); + + // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs + if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { + emit BucketBankruptcy(vars.index, vars.hpbLPs); + hpb.lps = 0; + hpb.bankruptcyTime = block.timestamp; + } else { + // add settled collateral into bucket + hpb.collateral = vars.hpbCollateral; + } - else { + } else { // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 Buckets.addCollateral( buckets_[vars.index], diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index 7a14502fc..7f02cf439 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; import 'src/ERC20Pool.sol'; +import 'src/interfaces/pool/commons/IPoolEvents.sol'; contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { @@ -882,5 +883,72 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { exchangeRate: 1 * 1e18 }); } +} + +contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { + + function testSettleAndBankruptcyOnHPBWithTinyDeposit() external { + address actor1 = makeAddr("actor1"); + _mintQuoteAndApproveTokens(actor1, type(uint256).max); + _mintCollateralAndApproveTokens(actor1, type(uint256).max); + + address actor2 = makeAddr("actor2"); + _mintQuoteAndApproveTokens(actor2, type(uint256).max); + _mintCollateralAndApproveTokens(actor2, type(uint256).max); + + address actor3 = makeAddr("actor1"); + _mintQuoteAndApproveTokens(actor3, type(uint256).max); + _mintCollateralAndApproveTokens(actor3, type(uint256).max); + address actor6 = makeAddr("actor6"); + _mintQuoteAndApproveTokens(actor6, type(uint256).max); + _mintCollateralAndApproveTokens(actor6, type(uint256).max); + + address actor8 = makeAddr("actor8"); + _mintQuoteAndApproveTokens(actor8, type(uint256).max); + _mintCollateralAndApproveTokens(actor8, type(uint256).max); + + changePrank(actor6); + _pool.addQuoteToken(2_000_000 * 1e18, 2572, block.timestamp + 100); + skip(100 days); + ERC20Pool(address(_pool)).drawDebt(actor6, 1000000 * 1e18, 7388, 372.489032271806320214 * 1e18); + skip(100 days); + + changePrank(actor1); + _pool.updateInterest(); + _pool.kick(actor6, 7388); + skip(100 hours); + ERC20Pool(address(_pool)).drawDebt(actor1, 1000000 * 1e18, 7388, 10066231386838.450530455239517417 * 1e18); + skip(100 days); + + changePrank(actor2); + _pool.updateInterest(); + _pool.kick(actor1, 7388); + skip(10 days); + + changePrank(actor3); + _pool.addQuoteToken(2, 2571, block.timestamp + 100); + + (uint256 bucketLps, uint256 collateral, , uint256 deposit, ) = _pool.bucketInfo(2571); + assertEq(bucketLps, 2); + assertEq(collateral, 0); + assertEq(deposit, 2); + (uint256 borrowerDebt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(actor1); + assertEq(borrowerDebt, 987909.179343464530923023 * 1e18); + assertEq(borrowerCollateral, 10066231386838.450530455239517417 * 1e18); + + changePrank(actor8); + _pool.updateInterest(); + vm.expectEmit(true, true, false, true); + emit BucketBankruptcy(2571, 2); + ERC20Pool(address(_pool)).settle(actor1, 1); + + (bucketLps, collateral, , deposit, ) = _pool.bucketInfo(2571); + assertEq(bucketLps, 0); // entire LPs removed from bucket 2571 + assertEq(collateral, 0); // no collateral added in bucket 2571 + assertEq(deposit, 0); // entire deposit from bucket 2571 used to settle + (borrowerDebt, borrowerCollateral, ) = _pool.borrowerInfo(actor1); + assertEq(borrowerDebt, 987909.179343464530923021 * 1e18); // decreased with 2 + assertEq(borrowerCollateral, 10066231386838.450530455239517417 * 1e18); // same as before settle + } } From 9dbe69c5abaf6ecdfe8a205650f8df5c7b5a68ac Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Thu, 9 Mar 2023 15:18:36 -0500 Subject: [PATCH 09/70] remove ptp, change how borrow amount calculated, fix LP scaling bugs (#668) --- tests/brownie/conftest.py | 32 ++++++++++-------------- tests/brownie/test_stable_volatile.py | 35 ++++++++++----------------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/tests/brownie/conftest.py b/tests/brownie/conftest.py index 112b5c7c1..2fca5777d 100644 --- a/tests/brownie/conftest.py +++ b/tests/brownie/conftest.py @@ -108,6 +108,11 @@ def __init__(self, ajna_protocol: AjnaProtocol, pool): # TODO: Move this functionality into SDK to insulate consumer from implementation logic. + def availableLiquidity(self): + quoteBalance = self.quoteToken().balanceOf(self.pool.address) + reserves = quoteBalance + self.debt() - self.pool.depositSize() + return quoteBalance - reserves; + def borrowerInfo(self, borrower_address): # returns (debt, collateral, mompFactor) return self.pool_info_utils.borrowerInfo(self.pool.address, borrower_address) @@ -126,6 +131,10 @@ def debt(self): def hpb(self): (hpb, hpbIndex, htp, htpIndex, lup, lupIndex) = self.pool_info_utils.poolPricesInfo(self.pool.address) return hpb + + def hpbIndex(self): + (hpb, hpbIndex, htp, htpIndex, lup, lupIndex) = self.pool_info_utils.poolPricesInfo(self.pool.address) + return hpbIndex def htp(self): (hpb, hpbIndex, htp, htpIndex, lup, lupIndex) = self.pool_info_utils.poolPricesInfo(self.pool.address) @@ -391,22 +400,16 @@ def j(text): return str.rjust(text, w) def nw(wad): return wad/1e18 - def ny(ray): - return ray/1e27 def fw(wad): return f"{nw(wad):>{w}.3f}" - def fy(ray): - return f"{ny(ray):>{w}.3f}" lup_index = pool_helper.lupIndex() htp_index = pool_helper.price_to_index_safe(pool_helper.htp()) pledged_collateral = pool.pledgedCollateral() - ptp_index = pool_helper.price_to_index_safe(int(pool_helper.debt() * 1e18 / pledged_collateral)) \ - if pledged_collateral > 0 else 0 min_bucket_index = max(0, pool_helper.priceToIndex(pool_helper.hpb()) - 3) # HPB - max_bucket_index = min(7388, max(lup_index, htp_index) + 3) if htp_index < 7388 else min(7388, lup_index + 3) + max_bucket_index = min(7388, max(lup_index, htp_index) + 3) if htp_index < 7388 else max(7388, lup_index + 42) assert min_bucket_index < max_bucket_index lines = [] @@ -423,8 +426,6 @@ def fy(ray): pointer += "LUP" if i == htp_index: pointer += "HTP" - if i == ptp_index: - pointer += "PTP" try: ( _, @@ -439,10 +440,10 @@ def fy(ray): continue if csv: lines.append(','.join([j(str(i)), nw(price), pointer, nw(bucket_quote), nw(bucket_collateral), - ny(bucket_lpAccumulator), nw(bucket_scale)])) + nw(bucket_lpAccumulator), nw(bucket_scale)])) else: lines.append(''.join([j(str(i)), fw(price), j(pointer), fw(bucket_quote), fw(bucket_collateral), - fy(bucket_lpAccumulator), f"{nw(bucket_scale):>{w}.9f}"])) + fw(bucket_lpAccumulator), f"{nw(bucket_scale):>{w}.9f}"])) return '\n'.join(lines) @staticmethod @@ -462,11 +463,6 @@ def summarize_pool(pool_helper): reserves = contract_quote_balance + poolDebt - pool.depositSize() pledged_collateral = pool.pledgedCollateral() (interest_rate, _) = pool.interestRateInfo() - if pledged_collateral > 0: - ptp = poolDebt * 10 ** 18 / pledged_collateral - ptp_index = pool_helper.priceToIndex(ptp) - else: - ptp = 0 print(f"contract q bal: {contract_quote_balance/1e18:>12.1f} " f"deposit: {pool.depositSize()/1e18:>12.1f} " f"reserves: {reserves/1e18:>12.1f} " @@ -476,10 +472,8 @@ def summarize_pool(pool_helper): lup = pool_helper.lup() htp = pool_helper.htp() poolCollateral = pool.pledgedCollateral() - ptp = int(poolDebt * 1e18 / poolCollateral) if poolCollateral else 0 print(f"lup: {lup/1e18:>12.3f} " - f"htp: {htp/1e18:>12.3f} " - f"ptp: {ptp/1e18:>12.3f}") + f"htp: {htp/1e18:>12.3f}") @pytest.fixture diff --git a/tests/brownie/test_stable_volatile.py b/tests/brownie/test_stable_volatile.py index 49f6c1762..559d16f78 100644 --- a/tests/brownie/test_stable_volatile.py +++ b/tests/brownie/test_stable_volatile.py @@ -143,21 +143,6 @@ def ensure_pool_is_funded(pool, quote_token_amount: int, action: str) -> bool: return True -def get_cumulative_bucket_deposit(pool_helper, bucket_depth) -> int: # WAD - # Iterates through number of buckets passed as parameter, adding deposit to determine what loan size will be - # required to utilize the buckets. - index = pool_helper.lupIndex() - (_, quote, _, _, _, _) = pool_helper.bucketInfo(index) - cumulative_deposit = quote - while bucket_depth > 0 and index > MIN_BUCKET: - index += 1 - # TODO: This ignores partially-utilized buckets; difficult to calculate in v10 - (_, quote, _, _, _, _) = pool_helper.bucketInfo(index) - cumulative_deposit += quote - bucket_depth -= 1 - return cumulative_deposit - - def get_time_between_interactions(actor_index): # Distribution function throttles time between interactions based upon user_index return 333 * math.exp(actor_index/10) + 3600 @@ -207,9 +192,12 @@ def pledge_and_borrow(pool_helper, borrower, borrower_index, collateral_to_depos collateral_balance = pool_helper.collateralToken().balanceOf(borrower) if collateral_balance < collateral_to_deposit: log(f" WARN: borrower {borrower_index} only has {collateral_balance/1e18:.1f} collateral " - f"and cannot deposit {collateral_to_deposit/1e18:.1f} to draw debt") + f"and cannot deposit {collateral_to_deposit/1e18:.1f} to draw debt") + return + if collateral_to_deposit < 0.001 * 10**18: + log(f" WARN: borrower {borrower_index} should not draw {borrow_amount/1e18:.1f} " + f"with {collateral_to_deposit/1e18:.1f} collateral") return - assert collateral_to_deposit > 0.001 * 10**18 # draw debt pledged += collateral_to_deposit @@ -274,8 +262,8 @@ def draw_and_bid(lenders, borrowers, start_from, pool_helper, chain, test_utils, def draw_debt(borrower, borrower_index, pool_helper, test_utils, collateralization=1.1): - # Draw debt based on added liquidity - borrow_amount = get_cumulative_bucket_deposit(pool_helper, (borrower_index % 4) + 1) + # Draw debt based on available liquidity + borrow_amount = pool_helper.availableLiquidity() * 1 / (4*((borrower_index%5)+1)) pool_quote_on_deposit = pool_helper.pool.depositSize() - pool_helper.debt() borrow_amount = min(pool_quote_on_deposit / 2, borrow_amount) collateral_to_deposit = borrow_amount / pool_helper.lup() * collateralization * 10**18 @@ -316,9 +304,9 @@ def remove_quote_token(lender, lender_index, price, pool_helper) -> bool: (lp_balance, _) = pool_helper.lenderInfo(price_index, lender) if lp_balance > 0: (_, _, _, _, _, exchange_rate) = pool_helper.bucketInfo(price_index) - claimable_quote = lp_balance * exchange_rate / 10**36 + claimable_quote = lp_balance * exchange_rate / 10**18 log(f" lender {lender_index:>4} removing {claimable_quote / 10**18:.1f} quote" - f" from bucket {price_index} ({price / 10**18:.1f}); exchange rate is {exchange_rate/1e27:.8f}") + f" from bucket {price_index} ({price / 10**18:.1f}); exchange rate is {exchange_rate/1e18:.8f}") if not ensure_pool_is_funded(pool_helper.pool, claimable_quote * 2, "withdraw"): return False try: @@ -391,7 +379,10 @@ def test_stable_volatile_one(pool_helper, lenders, borrowers, test_utils, chain) with test_utils.GasWatcher(['addQuoteToken', 'drawDebt', 'removeQuoteToken', 'repayDebt']): while chain.time() < end_time: # hit the pool an hour at a time, calculating interest and then sending transactions - actor_id = draw_and_bid(lenders, borrowers, actor_id, pool_helper, chain, test_utils) + try: + actor_id = draw_and_bid(lenders, borrowers, actor_id, pool_helper, chain, test_utils) + except VirtualMachineError as ex: + log(f"WARN: {ex.message}") test_utils.summarize_pool(pool_helper) print(f"days remaining: {(end_time - chain.time()) / 3600 / 24:.3f}\n") From 19d4e2bda795359516c7ba6107369daed69f4e73 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Fri, 10 Mar 2023 11:21:28 +0530 Subject: [PATCH 10/70] Update lender deposit time only when non-zero lps are added (#674) * Fix: Deposit time updates only when non zero lps added * Add unit test (#677) - kicker does not get any LP as reward but deposit time increase * Fix test * Fix after review - proper comment * Format test --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: grandizzy --- src/libraries/internal/Buckets.sol | 12 +-- .../ERC20PoolLiquidationsDepositTake.t.sol | 83 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/libraries/internal/Buckets.sol b/src/libraries/internal/Buckets.sol index 8075f9307..044a88c68 100644 --- a/src/libraries/internal/Buckets.sol +++ b/src/libraries/internal/Buckets.sol @@ -65,7 +65,7 @@ library Buckets { /** * @notice Add amount of LPs for a given lender in a given bucket. - * @dev Increments bucket.collateral and bucket.lps accumulator state. + * @dev Increments lender lps accumulator and updates the deposit time. * @param bucket_ Bucket to record lender LPs. * @param bankruptcyTime_ Time when bucket become insolvent. * @param lender_ Lender address to add LPs for in the given bucket. @@ -77,12 +77,14 @@ library Buckets { address lender_, uint256 lpsAmount_ ) internal { - Lender storage lender = bucket_.lenders[lender_]; + if (lpsAmount_ != 0) { + Lender storage lender = bucket_.lenders[lender_]; - if (bankruptcyTime_ >= lender.depositTime) lender.lps = lpsAmount_; - else lender.lps += lpsAmount_; + if (bankruptcyTime_ >= lender.depositTime) lender.lps = lpsAmount_; + else lender.lps += lpsAmount_; - lender.depositTime = block.timestamp; + lender.depositTime = block.timestamp; + } } /**********************/ diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 246d3fcb4..22c78b5b1 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import 'src/ERC20Pool.sol'; import 'src/libraries/helpers/PoolHelper.sol'; contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { @@ -707,3 +708,85 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { }); } } + +contract ERC20PoolLiquidationsDepositTakeRegressionTest is ERC20HelperContract { + + function testDepositTakeOnAuctionPriceZero() external { + // initialize kicker to be rewarded after bucket take + address actor1 = makeAddr("actor1"); + _mintQuoteAndApproveTokens(actor1, type(uint256).max); + _mintCollateralAndApproveTokens(actor1, type(uint256).max); + + // initialize borrower to be kicked and take + address actor2 = makeAddr("actor2"); + _mintQuoteAndApproveTokens(actor2, type(uint256).max); + _mintCollateralAndApproveTokens(actor2, type(uint256).max); + + // initialize taker + address actor4 = makeAddr("actor4"); + _mintQuoteAndApproveTokens(actor4, type(uint256).max); + _mintCollateralAndApproveTokens(actor4, type(uint256).max); + + _addInitialLiquidity({ + from: actor2, + amount: 1_791_670_358_647.909977170293982862 * 1e18, + index: 2572 + }); + _pool.updateInterest(); + _drawDebtNoLupCheck({ + from: actor2, + borrower: actor2, + amountToBorrow: 895_835_179_323.954988585146991431 * 1e18, + limitIndex: 7388, + collateralToPledge: 333_688_779.021420071719646593 * 1e18 + }); + // skip to make loan undercollateralized + skip(100 days); + + // kicker kicks undercollateralized loan + changePrank(actor1); + _pool.updateInterest(); + _pool.kick(actor2, 7388); + skip(100 days); + + changePrank(actor4); + _pool.updateInterest(); + + // assert auction before bucket take, enough time passed so auction price is zero + _assertAuction( + AuctionParams({ + borrower: actor2, + active: true, + kicker: actor1, + bondSize: 9_090_645_929.673616432967467261 * 1e18, + bondFactor: 0.01 * 1e18, + kickTime: _startTime + 100 days, + kickMomp: 2_697.999235705754194133 * 1e18, + totalBondEscrowed: 9_090_645_929.673616432967467261 * 1e18, + auctionPrice: 0, + debtInAuction: 930_695_454_793.486423224879367583 * 1e18, + thresholdPrice: 2_789.112230632554291586 * 1e18, + neutralPrice: 2_860.503207254858101199 * 1e18 + }) + ); + + // assert kicker balances in bucket before bucket take auction with auction price zero + _assertLenderLpBalance({ + lender: actor1, + index: 2572, + lpBalance: 0, + depositTime: 0 + }); + + ERC20Pool(address(_pool)).bucketTake(actor2, false, 2572); + + // assert kicker balances in bucket after take + // deposit time should remain the same since auction price was zero / kicker reward is zero + _assertLenderLpBalance({ + lender: actor1, + index: 2572, + lpBalance: 0, + depositTime: 0 + }); + } +} \ No newline at end of file From 22db2835f04d7394dd64e142adec182b2356b530 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 10 Mar 2023 08:17:28 +0200 Subject: [PATCH 11/70] Bucket take with tiny deposit (#676) * Bucket take with tiny deposit - if deposit in bucket cannot cover at least 1 unit of collateral (that is calculated amount to take is zero) then taker reward is zero and bucket is left with no collateral, no deposit and unbacked LPs - fix by reverting if bucket take on a bucket with not enough deposit to cover at least one unit of collateral - unit test * Changes after review: cosmetic test changes --- src/libraries/external/Auctions.sol | 6 +- .../ERC20PoolLiquidationsDepositTake.t.sol | 78 ++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index b8d8d6488..3ac261c76 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -998,7 +998,8 @@ library Auctions { vars_.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); - if (vars_.unscaledDeposit == 0) revert InsufficientLiquidity(); // revert if no quote tokens in arbed bucket + // revert if no quote tokens in arbed bucket + if (vars_.unscaledDeposit == 0) revert InsufficientLiquidity(); vars_.bucketPrice = _priceAt(params_.index); @@ -1017,6 +1018,9 @@ library Auctions { vars_ ); + // revert if bucket deposit cannot cover at least one unit of collateral + if (vars_.collateralAmount == 0) revert InsufficientLiquidity(); + _rewardBucketTake( auctions_, deposits_, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 22c78b5b1..67bbf5ef0 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; import 'src/ERC20Pool.sol'; +import 'src/interfaces/pool/commons/IPoolErrors.sol'; import 'src/libraries/helpers/PoolHelper.sol'; contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { @@ -657,8 +658,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower, index: _i9_91 - } -); + }); skip(2.5 hours); @@ -789,4 +789,78 @@ contract ERC20PoolLiquidationsDepositTakeRegressionTest is ERC20HelperContract { depositTime: 0 }); } + + function testDepositTakeRevertOnCollateralCalculatedAsZero() external { + // initialize borrower to be kicked and take + address actor0 = makeAddr("actor0"); + _mintQuoteAndApproveTokens(actor0, type(uint256).max); + _mintCollateralAndApproveTokens(actor0, type(uint256).max); + + // initialize kicker to be rewarded after bucket take + address actor2 = makeAddr("actor2"); + _mintQuoteAndApproveTokens(actor2, type(uint256).max); + _mintCollateralAndApproveTokens(actor2, type(uint256).max); + + // initialize taker + address actor3 = makeAddr("actor3"); + _mintQuoteAndApproveTokens(actor3, type(uint256).max); + _mintCollateralAndApproveTokens(actor3, type(uint256).max); + + changePrank(actor0); + _addInitialLiquidity({ + from: actor0, + amount: 1_927_834_830_600.755456044194881800 * 1e18, + index: 2572 + }); + _pool.updateInterest(); + _drawDebtNoLupCheck({ + from: actor0, + borrower: actor0, + amountToBorrow: 963_917_415_300.377728022097440900 * 1e18, + limitIndex: 7388, + collateralToPledge: 359_048_665.215178534787974447 * 1e18 + }); + // skip to make loan undercollateralized + skip(100 days); + + // kicker kicks undercollateralized loan + changePrank(actor2); + _pool.updateInterest(); + _pool.kick(actor0, 7388); + skip(5 days); + + changePrank(actor3); + _pool.updateInterest(); + // taker adds a tiny amount of quote token in bucket to take + _addLiquidityNoEventCheck({ + from: actor3, + amount: 3, + index: 2571 + }); + + // assert bucket before take + _assertBucket({ + index: 2571, + lpBalance: 3, + collateral: 0, + deposit: 3, // tiny deposit that cannot cover one unit of collateral, collateral to take will be calculated as 0 + exchangeRate: 1 * 1e18 + }); + + changePrank(actor2); + _pool.updateInterest(); + + // bucket take with bucket 2571 should revert as deposit of 3 cannot cover at least one unit of collateral + vm.expectRevert(IPoolErrors.InsufficientLiquidity.selector); + _pool.bucketTake(actor0, true, 2571); + + // assert bucket after take + _assertBucket({ + index: 2571, + lpBalance: 3, + collateral: 0, + deposit: 3, + exchangeRate: 1 * 1e18 + }); + } } \ No newline at end of file From 828aa178b946ca626576ce25415a47a99757a415 Mon Sep 17 00:00:00 2001 From: Mike Hathaway Date: Fri, 10 Mar 2023 07:52:49 -0500 Subject: [PATCH 12/70] pr feedback (#671) Co-authored-by: Mike --- src/RewardsManager.sol | 2 +- tests/forge/RewardsManager.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index a76c71850..7e3941e20 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -170,7 +170,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { positionManager.moveLiquidity(moveLiquidityParams); // update bucket state - toBucket.lpsAtStakeTime = uint128(fromBucket.lpsAtStakeTime); + toBucket.lpsAtStakeTime = uint128(positionManager.getLPs(tokenId_, toIndex)); toBucket.rateAtStakeTime = uint128(IPool(ajnaPool).bucketExchangeRate(toIndex)); delete stakeInfo.snapshot[fromIndex]; diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index 91d2416c8..c160c7fdb 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -1270,7 +1270,7 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_minterOne); assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464439745 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.676741657300861210 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730457738037587731 * 1e18); _rewardsManager.claimRewards(tokenId, currentBurnEpoch); } From 1d91617b4530fce34dcdd2f54bc51826422f0c17 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Fri, 10 Mar 2023 12:04:12 -0500 Subject: [PATCH 13/70] EMA testing (#678) * test setup, started impl for first test * implemented testEMAAdjustmentTime * implemented testDepositShockResistance * use _assertEMAs --- tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol | 377 ++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol new file mode 100644 index 000000000..189cc796e --- /dev/null +++ b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; + +import 'src/libraries/helpers/PoolHelper.sol'; + +contract ERC721PoolEMAsTest is ERC721HelperContract { + + address internal _attacker; + address internal _borrower; + address internal _lender; + + function setUp() external { + _lender = makeAddr("lender"); + _borrower = makeAddr("borrower"); + _attacker = makeAddr("attacker"); + + // deploy subset pool + _pool = _deployCollectionPool(); + + _mintAndApproveQuoteTokens(_lender, 25_000 * 1e18); + _mintAndApproveQuoteTokens(_borrower, 2_000 * 1e18); + _mintAndApproveCollateralTokens(_borrower, 6); + _mintAndApproveQuoteTokens(_attacker, 300_000_000 * 1e18); + + // add meaningful liquidity; EMA should initialize + _addInitialLiquidity({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26 + }); + _assertEMAs({ + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, + depositEma: 10_000 * 1e18 + }); + + // add unmeaningful liquidity in same block; EMA should not update + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: 7000 + }); + // deposit accumulator updated to 15_000, but the EMA remains unchanged because no time passed + _assertEMAs({ + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, + depositEma: 10_000 * 1e18 + }); + + skip(8 hours); + + // borrower pledges 6 nfts and draws debt to maintain 130% collateralization ratio + uint256[] memory tokenIdsToAdd = new uint256[](6); + uint256 borrowAmount = Maths.wmul(6 * _p1505_26, 0.76923 * 1e18); + assertEq(borrowAmount, 6_947.364107101568112756 * 1e18); + for (uint i=0; i<6; ++i) { + tokenIdsToAdd[i] = i + 1; + } + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: borrowAmount, + limitIndex: _i1505_26, + tokenIds: tokenIdsToAdd, + newLup: _p1505_26 + }); + + _assertPool( + PoolParams({ + htp: 1_159.007377482809680884 * 1e18, // 7000 / 6 = 1166.66 + lup: _p1505_26, + poolSize: 15_000 * 1e18, + pledgedCollateral: 6 * 1e18, + encumberedCollateral: 4.620028820788372636 * 1e18, // 6 / 1.3 = 4.62 + poolDebt: 6_954.361808414458420694 * 1e18, + actualUtilization: 0.586829404159407881 * 1e18, // moving -> 6_947 / 10_000 (meaningful) = 0.7 + targetUtilization: 0.769969644230769231 * 1e18, + minDebtAmount: 695.436180841445842069 * 1e18, // debt / 10; only one loan, so not enforced + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 8_059_788.606357480557372857 * 1e18, // 6_954^2 / 6 ~= 8_059_686 + lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // 1_505.26 * 6_954.04 ~= 10_467_638.25 + debtEma: 6_954.044264896858085302 * 1e18, // current debt with origination fee + // previous accumulator had updated to 15_000 before debt was drawn, but now 5_000 is no longer meaningful... + depositEma: 11_850.197375262816985000 * 1e18 // ...so it is moving down toward 10_000 + }); + } + + function testEMAAdjustmentTime() external tearDown { + skip(3 hours); // 11 hours passed since liquidity added + + // since pool was not touched since debt was drawn, debt EMAs should remain unchanged + // debtColEma / lupt0DebtEma ~= 8_059_788.6 / 10_467_670.6 ~= 0.77 expected target utilization + _assertPool( + PoolParams({ + htp: 1_159.007377482809680884 * 1e18, + lup: _p1505_26, + poolSize: 15_000 * 1e18, + pledgedCollateral: 6 * 1e18, + encumberedCollateral: 4.620107931548236591 * 1e18, // small increase due to pending interest + poolDebt: 6_954.480890971813258160 * 1e18, // small increase due to pending interest + actualUtilization: 0.586829404159407881 * 1e18, + targetUtilization: 0.769969644230769231 * 1e18, // debtColEma / lupt0DebtEma + minDebtAmount: 695.448089097181325816 * 1e18, // small increase due to pending interest + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 8_059_788.606357480557372857 * 1e18, // unchanged from setup + lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // unchanged from setup + debtEma: 6_954.044264896858085302 * 1e18, // unchanged from setup + depositEma: 11_850.197375262816985000 * 1e18 // unchanged from setup + }); + + // touch the pool, triggering an interest accrual - EMAs should update + _pool.updateInterest(); + _assertPool( + PoolParams({ + htp: 1_159.152924076894437152 * 1e18, + lup: _p1505_26, + poolSize: 15_000.38784582038918 * 1e18, // first interest accrual + pledgedCollateral: 6 * 1e18, + encumberedCollateral: 4.620107931548236591 * 1e18, + poolDebt: 6_954.480890971813258160 * 1e18, // pending interest now equals current interest + actualUtilization: 0.601778294656389596 * 1e18, + targetUtilization: 0.769969644230769231 * 1e18, + minDebtAmount: 695.448089097181325816 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + _assertEMAs({ + debtColEma: 8_059_788.606357480557372857 * 1e18, // accumulator updated, no EMA change + lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // accumulator updated, no EMA change + debtEma: 6_954.044264896858085302 * 1e18, // accumulator updated, no EMA change + depositEma: 11_555.824340370334487364 * 1e18 // still moving toward 10_000 + }); + (uint256 interestRate, ) = _pool.interestRateInfo(); + assertEq(interestRate, 0.05 * 1e18); + + skip(9 hours); // 12 hours since debt was drawn + _pool.updateInterest(); + _assertEMAs({ + debtColEma: 8_059_824.827133087800583978 * 1e18, // updated for interest accrual + lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // updated for interest accrual + debtEma: 6_954.221271554347056671 * 1e18, // updated for interest accrual + depositEma: 10_925.255918947232279645 * 1e18 // still moving toward 10_000 + }); + (interestRate, ) = _pool.interestRateInfo(); + assertEq(interestRate, 0.05 * 1e18); + + skip(6 hours); + + // double the meaningful deposit + _addLiquidityNoEventCheck({ + from: _lender, + amount: 10_000 * 1e18, + index: _i1505_26 + }); + + _skipAndAccrue({ + time: 4 hours, + mau: 0.552709628959737370 * 1e18, // dropping from 60% to 35% + tu: 0.769980648855063767 * 1e18, // still at 77% + rate: 0.05 * 1e18 + }); + (, , , uint256 depositEma) = _pool.emasInfo(); + assertEq(depositEma, 12_582.630574533185450994 * 1e18); // now moving toward 20_000 + + _skipAndAccrue({ + time: 20 hours, // 24 hours since liquidity was added + mau: 0.393730664447534870 * 1e18, // still dropping toward 35% + tu: 0.769999034610545182 * 1e18, // still at 77% + rate: 0.045 * 1e18 // first interest rate drop + }); + (, , , depositEma) = _pool.emasInfo(); + assertEq(depositEma, 17_664.401438069534341122 * 1e18); // still moving toward 20_000 + + _skipAndAccrue({ + time: 2 days, // 3 days since liquidity was added + mau: 0.350326278385275701 * 1e18, // reached 35% + tu: 0.770061298755197770 * 1e18, // still at 77% + rate: 0.0405 * 1e18 // second interest rate drop + }); + (, , , depositEma) = _pool.emasInfo(); + assertEq(depositEma, 19_855.678232854988936290 * 1e18); // reached (sort of) 20_000 + _assertPool( + PoolParams({ + htp: 1_160.241132852523151748 * 1e18, + lup: _p1505_26, + poolSize: 25_003.260972741349848786 * 1e18, // reflects additional 10_000 deposit + pledgedCollateral: 6 * 1e18, + encumberedCollateral: 4.622276093514343199 * 1e18, + poolDebt: 6_957.744546536839716358 * 1e18, + actualUtilization: 0.350326278385275701 * 1e18, // dropped to 35% as expected + targetUtilization: 0.770061298755197770 * 1e18, + minDebtAmount: 695.774454653683971636 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.0405 * 1e18, // dropped twice + interestRateUpdate: _startTime + 98 hours + }) + ); + + // draw additional debt + vm.stopPrank(); + _mintAndApproveCollateralTokens(_borrower, 6); + uint256[] memory tokenIdsToAdd = new uint256[](6); + for (uint i=0; i<6; ++i) { + tokenIdsToAdd[i] = i + 7; + } + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 11_000 * 1e18, // total ~18_000 principal / 20_0000 meaningful liquidity + limitIndex: _i1505_26, + tokenIds: tokenIdsToAdd, + newLup: _p1505_26 + }); + + _skipAndAccrue({ + time: 3 hours, + mau: 0.438034189478303511 * 1e18, // rising from 35% to 90% + tu: 0.783712586574747919 * 1e18, // increases as collateralization decreases + rate: 0.0405 * 1e18 + }); + (, , uint256 debtEma, ) = _pool.emasInfo(); + assertEq(debtEma, 8_707.751377089437009807 * 1e18); // increasing from 7_000 to 18_000 + + _skipAndAccrue({ + time: 9 hours, + mau: 0.625264252786034774 * 1e18, // still rising to 90% + tu: 0.817638199962595844 * 1e18, + rate: 0.0405 * 1e18 + }); + (, , debtEma, ) = _pool.emasInfo(); + assertEq(debtEma, 12_461.239878735709484526 * 1e18); // increasing from 7_000 to 18_000 + + _skipAndAccrue({ + time: 4 days, + mau: 0.897117712497350667 * 1e18, // reached 90% + tu: 0.947031347885781555 * 1e18, + rate: 0.0405 * 1e18 + }); + (, , debtEma, ) = _pool.emasInfo(); + assertEq(debtEma, 17_945.800561906185304271 * 1e18); // reached 18_000 + _assertPool( + PoolParams({ + htp: 1_499.486002669294859183 * 1e18, + lup: _p1505_26, + poolSize: 25_011.246566016078933954 * 1e18, + pledgedCollateral: 12 * 1e18, // 6 additional NFTs deposited + encumberedCollateral: 11.941618338706780744 * 1e18, // all 12 NFTs are encumbered + poolDebt: 17_975.284944476369220528 * 1e18, // includes new debt + actualUtilization: 0.897117712497350667 * 1e18, + targetUtilization: 0.947031347885781555 * 1e18, + minDebtAmount: 1_797.528494447636922053 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.0405 * 1e18, + interestRateUpdate: _startTime + 98 hours + }) + ); + } + + function testDepositShockResistance() external tearDown { + // add some debt to bring MAU closer to TU (77%) + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 700 * 1e18, // total 7_647 principal + limitIndex: _i1505_26, + tokenIds: new uint256[](0), + newLup: _p1505_26 + }); + _skipAndAccrue({ + time: 40 hours, // 2 days after liquidity was added + mau: 0.744857048522821157 * 1e18, // 7_647 / 10_000 ~= 76% + tu: 0.793326272355691526 * 1e18, // starting at 77% + rate: 0.05 * 1e18 + }); + _assertEMAs({ + debtColEma: 8_539_491.492000790693673965 * 1e18, // reflects newly drawn debt + lupt0DebtEma: 10_764_160.711133073306706753 * 1e18, // unchanged from setup + debtEma: 7_585.487807318324588356 * 1e18, // increasing toward 7_647 + depositEma: 10_183.816911394801817581 * 1e18 // decreasing toward 10_000 + }); + + // bad actor comes along and deposits large amount for 5 minutes, and then withdraws + _addLiquidityNoEventCheck({ + from: _attacker, + amount: 150000 * 1e18, + index: _i1505_26 + }); + skip(5 minutes); + _pool.updateInterest(); // not really needed, since removing liquidity will trigger rate update + _removeAllLiquidity({ + from: _attacker, + amount: 150_000.003089440923020314 * 1e18, + index: _i1505_26, + newLup: _p1505_26, + lpRedeem: 149_972.484368509876101687 * 1e18 + }); + + _skipAndAccrue({ + time: 12, // skip a single block + mau: 0.695753471133465072 * 1e18, // impacted, but not enough to cause rate change + tu: 0.793367939903626038 * 1e18, + rate: 0.05 * 1e18 // rate unchanged + }); + _assertEMAs({ + debtColEma: 8_540_370.017841347311996670 * 1e18, + lupt0DebtEma: 10_764_702.716470726509705193 * 1e18, + debtEma: 7_585.843823429738778980 * 1e18, + depositEma: 10_903.062849361711217820 * 1e18 // still noticably impacted + }); + + _skipAndAccrue({ + time: 12 hours, + mau: 0.729141586574051708 * 1e18, // moving back toward 75% + tu: 0.798822457321421405 * 1e18, + rate: 0.05 * 1e18 + }); + _assertEMAs({ + debtColEma: 8_656_142.490618553816291562 * 1e18, + lupt0DebtEma: 10_836_128.117434222666947215 * 1e18, + debtEma: 7_621.315210378439120928 * 1e18, + depositEma: 10_452.448949164988301441 * 1e18 // moving down back to 10_000 + }); + _assertPool( + PoolParams({ + htp: 1_276.656276295498190871 * 1e18, + lup: _p1505_26, + poolSize: 15_002.306593887595240000 * 1e18, + pledgedCollateral: 6 * 1e18, + encumberedCollateral: 5.087022896986824909 * 1e18, + poolDebt: 7_657.311052725908840244 * 1e18, // 7_647 principal plus some interest + actualUtilization: 0.729141586574051708 * 1e18, + targetUtilization: 0.798822457321421405 * 1e18, + minDebtAmount: 765.731105272590884024 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + } + + function _skipAndAccrue( + uint256 time, // amount of time to skip + uint256 mau, // expected meaningful actual utilization + uint256 tu, // expected target utilization + uint256 rate // interest rate + ) internal { + skip(time); + _pool.updateInterest(); + (, , uint256 mauActual, uint256 tuActual) = _poolUtils.poolUtilizationInfo(address(_pool)); + assertEq(mauActual, mau); + assertEq(tuActual, tu); + (uint256 rateActual, ) = _pool.interestRateInfo(); + assertEq(rateActual, rate); + } +} \ No newline at end of file From 94b27743d64ec58d02714c8dbf1865134738972a Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Mon, 13 Mar 2023 06:25:31 -0400 Subject: [PATCH 14/70] Documented Net Interest Margin calculation (#683) * documented net interest margin calculation * improve comment --- src/libraries/external/PoolCommons.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 75a72b522..183f0c4fd 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -294,12 +294,22 @@ library PoolCommons { function _lenderInterestMargin( uint256 mau_ ) internal pure returns (uint256) { + // Net Interest Margin = ((1 - MAU1)^(1/3) * 0.15) + // Where MAU1 is MAU capped at 100% (min(MAU,1)) + // Lender Interest Margin = 1 - Net Interest Margin + + // PRBMath library forbids raising a number < 1e18 to a power. Using the product and quotient rules of + // exponents, rewrite the equation with a coefficient s which provides sufficient precision: + // Net Interest Margin = ((s - MAU1) * s)^(1/3) / s^(1/3) * 0.15 + uint256 base = 1_000_000 * 1e18 - Maths.wmul(Maths.min(mau_, 1e18), 1_000_000 * 1e18); + // If unutilized deposit is infinitessimal, lenders get 100% of interest. if (base < 1e18) { return 1e18; } else { // cubic root of the percentage of meaningful unutilized deposit uint256 crpud = PRBMathUD60x18.pow(base, ONE_THIRD); + // finish calculating Net Interest Margin, and then convert to Lender Interest Margin return 1e18 - Maths.wmul(Maths.wdiv(crpud, CUBIC_ROOT_1000000), 0.15 * 1e18); } } From 600500891b601402d4cbac5d9eaceeb37276e8c1 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 13 Mar 2023 19:55:13 +0200 Subject: [PATCH 15/70] Remove unused load fromBucket storage (#684) --- src/RewardsManager.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 7e3941e20..683e9ef50 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -156,9 +156,6 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { fromIndex = fromBuckets_[i]; toIndex = toBuckets_[i]; - BucketState storage fromBucket = stakeInfo.snapshot[fromIndex]; - BucketState storage toBucket = stakeInfo.snapshot[toIndex]; - // call out to position manager to move liquidity between buckets IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = IPositionManagerOwnerActions.MoveLiquidityParams( tokenId_, @@ -169,8 +166,9 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { ); positionManager.moveLiquidity(moveLiquidityParams); - // update bucket state - toBucket.lpsAtStakeTime = uint128(positionManager.getLPs(tokenId_, toIndex)); + // update to bucket state + BucketState storage toBucket = stakeInfo.snapshot[toIndex]; + toBucket.lpsAtStakeTime = uint128(positionManager.getLPs(tokenId_, toIndex)); toBucket.rateAtStakeTime = uint128(IPool(ajnaPool).bucketExchangeRate(toIndex)); delete stakeInfo.snapshot[fromIndex]; From 2f00a000bc9d087d777a59de1517a48a5635f4ff Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 13 Mar 2023 20:27:45 +0200 Subject: [PATCH 16/70] (ToB) PoolUtilInfo htp calculation fix (#685) * PoolUtilInfo htp fix: - in PoolUtilsInfo.poolPricesInfo and htp functions do not multiply again the maxThresholdPrice returned from pool.loansInfo (as it's already multiplied) - consistent naming of inflatorRate instead inflatorSnapshot * Changes after review, consistent inflator naming --- src/PoolInfoUtils.sol | 26 +++++++------------ src/interfaces/pool/commons/IPoolState.sol | 8 +++--- src/libraries/external/PoolCommons.sol | 10 +++---- tests/forge/ERC20Pool/ERC20DSTestPlus.sol | 6 ++--- tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol | 10 +++---- .../forge/ERC20Pool/ERC20PoolCollateral.t.sol | 4 +-- tests/forge/ERC20Pool/ERC20PoolFactory.t.sol | 24 ++++++++--------- .../ERC20PoolInterestRateAndEMAs.t.sol | 18 ++++++------- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 2 +- .../ERC20Pool/ERC20PoolLiquidationsMisc.t.sol | 6 ++--- .../ERC20PoolLiquidationsSettle.t.sol | 2 +- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 20 +++++++------- tests/forge/ERC721Pool/ERC721DSTestPlus.sol | 6 ++--- tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol | 2 +- tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol | 8 +++--- .../forge/ERC721Pool/ERC721PoolFactory.t.sol | 12 ++++----- .../ERC721PoolLiquidationsDepositTake.t.sol | 2 +- .../ERC721PoolLiquidationsKick.t.sol | 2 +- .../ERC721PoolLiquidationsTake.t.sol | 10 +++---- tests/forge/PoolCommonsTest.t.sol | 14 +++++----- tests/forge/utils/DSTestPlus.sol | 6 ++--- 21 files changed, 96 insertions(+), 102 deletions(-) diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index 99bfd8ac2..01e1134c4 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -43,13 +43,13 @@ contract PoolInfoUtils { IPool pool = IPool(ajnaPool_); ( - uint256 poolInflatorSnapshot, - uint256 lastInflatorSnapshotUpdate + uint256 inflator, + uint256 lastInflatorUpdate ) = pool.inflatorInfo(); (uint256 interestRate,) = pool.interestRateInfo(); - uint256 pendingInflator = PoolCommons.pendingInflator(poolInflatorSnapshot, lastInflatorSnapshotUpdate, interestRate); + uint256 pendingInflator = PoolCommons.pendingInflator(inflator, lastInflatorUpdate, interestRate); uint256 t0Debt; (t0Debt, collateral_, t0Np_) = pool.borrowerInfo(borrower_); @@ -112,14 +112,14 @@ contract PoolInfoUtils { (maxBorrower_, , loansCount_) = pool.loansInfo(); ( - uint256 inflatorSnapshot, - uint256 lastInflatorSnapshotUpdate + uint256 inflator, + uint256 inflatorUpdate ) = pool.inflatorInfo(); (uint256 interestRate, ) = pool.interestRateInfo(); - pendingInflator_ = PoolCommons.pendingInflator(inflatorSnapshot, lastInflatorSnapshotUpdate, interestRate); - pendingInterestFactor_ = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - lastInflatorSnapshotUpdate); + pendingInflator_ = PoolCommons.pendingInflator(inflator, inflatorUpdate, interestRate); + pendingInterestFactor_ = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - inflatorUpdate); } /** @@ -151,9 +151,8 @@ contract PoolInfoUtils { hpb_ = _priceAt(hpbIndex_); (, uint256 maxThresholdPrice,) = pool.loansInfo(); - (uint256 inflatorSnapshot,) = pool.inflatorInfo(); - htp_ = Maths.wmul(maxThresholdPrice, inflatorSnapshot); + htp_ = maxThresholdPrice; htpIndex_ = htp_ >= MIN_PRICE ? _indexOf(htp_) : MAX_FENWICK_INDEX; lupIndex_ = pool.depositIndex(debt); lup_ = _priceAt(lupIndex_); @@ -310,13 +309,8 @@ contract PoolInfoUtils { function htp( address ajnaPool_ - ) external view returns (uint256) { - IPool pool = IPool(ajnaPool_); - - (, uint256 maxThresholdPrice, ) = pool.loansInfo(); - (uint256 inflatorSnapshot, ) = pool.inflatorInfo(); - - return Maths.wmul(maxThresholdPrice, inflatorSnapshot); + ) external view returns (uint256 htp_) { + (, htp_, ) = IPool(ajnaPool_).loansInfo(); } function momp( diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index 35c8c66be..63600e4f9 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -51,7 +51,7 @@ interface IPoolState { * @param borrower Address of the borrower. * @return t0Debt Amount of debt borrower would have had if their loan was the first debt drawn from the pool * @return collateral Amount of collateral that the borrower has deposited, in collateral token. - * @return t0Np Np / borrowerInflatorSnapshot + * @return t0Np t0 Neutral Price */ function borrowerInfo(address borrower) external @@ -119,14 +119,14 @@ interface IPoolState { /** * @notice Returns information about pool inflator. - * @return inflatorSnapshot A snapshot of the last inflator value. - * @return lastUpdate The timestamp of the last `inflatorSnapshot` update. + * @return inflator Pool inflator value. + * @return lastUpdate The timestamp of the last `inflator` update. */ function inflatorInfo() external view returns ( - uint256 inflatorSnapshot, + uint256 inflator, uint256 lastUpdate ); diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 183f0c4fd..8069cec73 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -349,18 +349,18 @@ library PoolCommons { /** * @notice Calculates pool pending inflator given the current inflator, time of last update and current interest rate. - * @param inflatorSnapshot_ Current pool interest rate. - * @param inflatorUpdate Timestamp when inflator was updated. - * @param interestRate_ The interest rate of the pool. + * @param inflator_ Current pool inflator. + * @param inflatorUpdate Timestamp when inflator was updated. + * @param interestRate_ The interest rate of the pool. * @return The pending value of pool inflator. */ function pendingInflator( - uint256 inflatorSnapshot_, + uint256 inflator_, uint256 inflatorUpdate, uint256 interestRate_ ) external view returns (uint256) { return Maths.wmul( - inflatorSnapshot_, + inflator_, PRBMathUD60x18.exp((interestRate_ * (block.timestamp - inflatorUpdate)) / 365 days) ); } diff --git a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol index d3d19879b..3d06804df 100644 --- a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol @@ -39,13 +39,13 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { (borrowerT0debt, borrowerCollateral, ) = _pool.borrowerInfo(borrower); // calculate current pool Inflator - (uint256 poolInflatorSnapshot, uint256 lastInflatorSnapshotUpdate) = _pool.inflatorInfo(); + (uint256 poolInflator, uint256 lastInflatorUpdate) = _pool.inflatorInfo(); - uint256 elapsed = block.timestamp - lastInflatorSnapshotUpdate; + uint256 elapsed = block.timestamp - lastInflatorUpdate; (uint256 interestRate, ) = _pool.interestRateInfo(); uint256 factor = PoolCommons.pendingInterestFactor(interestRate, elapsed); - uint256 currentPoolInflator = Maths.wmul(poolInflatorSnapshot, factor); + uint256 currentPoolInflator = Maths.wmul(poolInflator, factor); // Calculate current debt of borrower, rounding up to token precision uint256 currentDebt = Maths.wmul(currentPoolInflator, borrowerT0debt); diff --git a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol index 76eda391b..582eb91d5 100644 --- a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol @@ -368,7 +368,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 352.454532537342231182 * 1e18, + htp: 351.393939751686889789 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 50_055.509494601600700000 * 1e18, pledgedCollateral: 60 * 1e18, @@ -406,7 +406,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 424.349858731660857846 * 1e18, + htp: 422.372244265211513602 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 50_086.111097974181150000 * 1e18, pledgedCollateral: 50 * 1e18, @@ -441,7 +441,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 425.900107294311861922 * 1e18, + htp: 423.143052860217066081 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 50_119.833720983122550000 * 1e18, pledgedCollateral: 50 * 1e18, @@ -473,7 +473,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 427.611922756860156608 * 1e18, + htp: 423.992567137945688846 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 50_157.001040562732750000 * 1e18, pledgedCollateral: 50 * 1e18, @@ -503,7 +503,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 427.611922756860156608 * 1e18, + htp: 423.992567137945688846 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 50_157.001040562732750000 * 1e18, pledgedCollateral: 50 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol index 0c5ebbc33..1d763ce64 100644 --- a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol @@ -125,7 +125,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 421.557216751451801727 * 1e18, + htp: 420.980136462780058369 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 30_024.492338129690940000 * 1e18, pledgedCollateral: 50 * 1e18, @@ -161,7 +161,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 2_985.093792841086761332 * 1e18, + htp: 2_981.007422784467321393 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 30_024.492338129690940000 * 1e18, pledgedCollateral: 7.061038044473493202 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol index a9e8d58f9..b34a709b3 100644 --- a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol @@ -120,8 +120,8 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(interestRate, 0.0543 * 10**18); assertEq(interestRateUpdate, _startTime + 333); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); + (uint256 poolInflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + assertEq(poolInflator, 10**18); assertEq(lastInflatorUpdate, _startTime + 333); // check tracking of deployed pools @@ -151,8 +151,8 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(interestRate, 0.0543 * 10**18); assertEq(interestRateUpdate, _startTime + 333); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); + (uint256 poolInflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + assertEq(poolInflator, 10**18); assertEq(lastInflatorUpdate, _startTime + 333); } @@ -176,8 +176,8 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(interestRate, 0.0543 * 10**18); assertEq(interestRateUpdate, _startTime + 333); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); + (uint256 poolInflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + assertEq(poolInflator, 10**18); assertEq(lastInflatorUpdate, _startTime + 333); } @@ -201,9 +201,9 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(interestRate, 0.0543 * 10**18); assertEq(interestRateUpdate, _startTime + 333); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); - assertEq(lastInflatorUpdate, _startTime + 333); + (uint256 poolInflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + assertEq(poolInflator, 10**18); + assertEq(lastInflatorUpdate, _startTime + 333); } function testDeployERC20CompUsdcPool() external { @@ -226,9 +226,9 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { assertEq(interestRate, 0.0543 * 10**18); assertEq(interestRateUpdate, _startTime + 333); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); - assertEq(lastInflatorUpdate, _startTime + 333); + (uint256 poolInflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + assertEq(poolInflator, 10**18); + assertEq(lastInflatorUpdate, _startTime + 333); } function testPoolAlreadyInitialized() external { diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index a4b225213..3628d1b5b 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -127,7 +127,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 461.913231889174987773 * 1e18, + htp: 461.177183352194672960 * 1e18, lup: 2_981.007422784467321543 * 1e18, poolSize: 110_064.287293030035100000 * 1e18, pledgedCollateral: 100 * 1e18, @@ -235,7 +235,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 0.664069695689831259 * 1e18, + htp: 0.664020422940018855 * 1e18, lup: 100.332368143282009890 * 1e18, poolSize: 1_000.062818094677887000 * 1e18, pledgedCollateral: 1_500 * 1e18, @@ -307,7 +307,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 500.549332936289892272 * 1e18, + htp: 500.515049909493508659 * 1e18, lup: 601.252968524772188572 * 1e18, poolSize: 11_000.291385769156360000 * 1e18, pledgedCollateral: 10 * 1e18, @@ -373,7 +373,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { // show the rate bottoms out at 10 bps _assertPool( PoolParams({ - htp: 1.003660230043452158 * 1e18, + htp: 1.002309975983935259 * 1e18, lup: _p1505_26, poolSize: 10_000.000000011461690000 * 1e18, pledgedCollateral: 0.00001 * 1e18, @@ -439,7 +439,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { // show the rate maxed out at 50000% _assertPool( PoolParams({ - htp: 2_081.663642755844195984 * 1e18, + htp: 0.001443490644739886 * 1e18, lup: _p1505_26, poolSize: 10_012.269795822391370000 * 1e18, pledgedCollateral: 10_000.0 * 1e18, @@ -652,7 +652,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 10.237543320969223878 * 1e18, + htp: 10.223528890139451939 * 1e18, lup: 327.188250324085203338 * 1e18, poolSize: 20_001.166301815699560000 * 1e18, pledgedCollateral: 100 * 1e18, @@ -798,7 +798,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 200.941998518054562754 * 1e18, + htp: 200.666923956635192629 * 1e18, lup: 327.188250324085203338 * 1e18, poolSize: 40_022.159734498291800000 * 1e18, pledgedCollateral: 100 * 1e18, @@ -836,7 +836,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }); _assertPool( PoolParams({ - htp: 201.493279375818240993 * 1e18, + htp: 200.941998518054562716 * 1e18, lup: 327.188250324085203338 * 1e18, poolSize: 40_045.121523671487120000 * 1e18, pledgedCollateral: 50_100 * 1e18, @@ -868,7 +868,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }); _assertPool( PoolParams({ - htp: 201.548490576836267720 * 1e18, + htp: 200.969526704670605713 * 1e18, lup: 327.188250324085203338 * 1e18, poolSize: 40_047.420826509653040000 * 1e18, pledgedCollateral: 50_100 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index d9d67006c..6b8604254 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -177,7 +177,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 8.209538814158655264 * 1e18, + htp: 8.097846143253778448 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 73_093.873009488594553000 * 1e18, pledgedCollateral: 1_002 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol index 85f239501..1a1f25584 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol @@ -315,7 +315,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { }); _assertPool( PoolParams({ - htp: 7.991488192808991114 * 1e18, + htp: 7.989580407145861718 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 63_008.830844290339406730 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, @@ -388,7 +388,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 7.991488192808991114 * 1e18, + htp: 7.989580407145861718 * 1e18, lup: 9.529276179422528643 * 1e18, poolSize: 8_000.423065889459267791 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, @@ -416,7 +416,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 7.993335753787741967 * 1e18, + htp: 7.990503913730158191 * 1e18, lup: 9.529276179422528643 * 1e18, poolSize: 8_001.214422208222843222 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index 7f02cf439..cbfc7a87c 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -391,7 +391,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { }); _assertPool( PoolParams({ - htp: 9.910303333009215085 * 1e18, + htp: 9.771304290202671377 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 63_807.241482463513273501 * 1e18, pledgedCollateral: 2 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 99e9dae47..b705891df 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -308,7 +308,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.280695967198888513 * 1e18, + htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 83_219.674636105806620000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, @@ -386,7 +386,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.281940892899682703 * 1e18, + htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 83_220.780619576281748675 * 1e18, pledgedCollateral: 1_002.0 * 1e18, @@ -436,7 +436,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.281940892899682703 * 1e18, + htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 83_220.780619576281748675 * 1e18, pledgedCollateral: 2_002.0 * 1e18, @@ -585,7 +585,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.280695967198888513 * 1e18, + htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 83_219.674636105806620000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, @@ -754,7 +754,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { ); _assertBorrower({ borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerDebt: 9_822.951211365485636462 * 1e18, borrowerCollateral: 1_000 * 1e18, borrowert0Np: 1_575.326150647652569911 * 1e18, borrowerCollateralization: 0.989651241857326201 * 1e18 @@ -793,7 +793,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.280695967198888513 * 1e18, + htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 83_219.674636105806620000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, @@ -955,7 +955,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.901856025849255254 * 1e18, + htp: 9.767138988573636287 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 73_113.822894306622584000 * 1e18, pledgedCollateral: 1_002 * 1e18, @@ -1007,7 +1007,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.902059490734692431 * 1e18, + htp: 9.767239336407496599 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 73_113.913540853182354328 * 1e18, pledgedCollateral: 992.0 * 1e18, @@ -1142,7 +1142,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { ); _assertBorrower({ borrower: _borrower2, - borrowerDebt: 9_822.951211365485636462* 1e18, + borrowerDebt: 9_822.951211365485636462 * 1e18, borrowerCollateral: 1_000 * 1e18, borrowert0Np: 1_575.326150647652569911 * 1e18, borrowerCollateralization: 0.989651241857326201 * 1e18 @@ -1181,7 +1181,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _assertPool( PoolParams({ - htp: 9.280695967198888513 * 1e18, + htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, poolSize: 83_219.674636105806620000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol index 1b5a865de..7a222ae60 100644 --- a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol @@ -45,13 +45,13 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { (borrowerT0debt, borrowerCollateral, ) = _pool.borrowerInfo(borrower); // calculate current pool Inflator - (uint256 poolInflatorSnapshot, uint256 lastInflatorSnapshotUpdate) = _pool.inflatorInfo(); + (uint256 poolInflator, uint256 lastInflatorUpdate) = _pool.inflatorInfo(); (uint256 interestRate, ) = _pool.interestRateInfo(); - uint256 factor = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - lastInflatorSnapshotUpdate); + uint256 factor = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - lastInflatorUpdate); // Calculate current debt of borrower (currentPoolInflator * borrowerT0Debt) - uint256 currentDebt = Maths.wmul(Maths.wmul(poolInflatorSnapshot, factor), borrowerT0debt); + uint256 currentDebt = Maths.wmul(Maths.wmul(poolInflator, factor), borrowerT0debt); // mint quote tokens to borrower address equivalent to the current debt deal(_pool.quoteTokenAddress(), borrower, currentDebt); diff --git a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol index 02fb1d46d..5712ea996 100644 --- a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol @@ -272,7 +272,7 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { // check pool state after partial repay _assertPool( PoolParams({ - htp: 503.022258079721182348 * 1e18, + htp: 502.333658244714424687 * 1e18, lup: _priceAt(2550), poolSize: 30_003.498905447098710000 * 1e18, pledgedCollateral: Maths.wad(3), diff --git a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol index 189cc796e..079483d25 100644 --- a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol @@ -128,7 +128,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _pool.updateInterest(); _assertPool( PoolParams({ - htp: 1_159.152924076894437152 * 1e18, + htp: 1_159.080148495302209694 * 1e18, lup: _p1505_26, poolSize: 15_000.38784582038918 * 1e18, // first interest accrual pledgedCollateral: 6 * 1e18, @@ -200,7 +200,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { assertEq(depositEma, 19_855.678232854988936290 * 1e18); // reached (sort of) 20_000 _assertPool( PoolParams({ - htp: 1_160.241132852523151748 * 1e18, + htp: 1_159.624091089473286060 * 1e18, lup: _p1505_26, poolSize: 25_003.260972741349848786 * 1e18, // reflects additional 10_000 deposit pledgedCollateral: 6 * 1e18, @@ -260,7 +260,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { assertEq(debtEma, 17_945.800561906185304271 * 1e18); // reached 18_000 _assertPool( PoolParams({ - htp: 1_499.486002669294859183 * 1e18, + htp: 1_497.940412039697435044 * 1e18, lup: _p1505_26, poolSize: 25_011.246566016078933954 * 1e18, pledgedCollateral: 12 * 1e18, // 6 additional NFTs deposited @@ -343,7 +343,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }); _assertPool( PoolParams({ - htp: 1_276.656276295498190871 * 1e18, + htp: 1_276.218508787651473374 * 1e18, lup: _p1505_26, poolSize: 15_002.306593887595240000 * 1e18, pledgedCollateral: 6 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol index b8b5cc45e..942e59ef4 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol @@ -197,9 +197,9 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { assertEq(interestRate, 0.05 * 10**18); assertEq(interestRateUpdate, _startTime); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = _NFTCollectionPool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); - assertEq(lastInflatorUpdate, _startTime); + (uint256 poolInflator, uint256 lastInflatorUpdate) = _NFTCollectionPool.inflatorInfo(); + assertEq(poolInflator, 10**18); + assertEq(lastInflatorUpdate, _startTime); } /**************************************/ @@ -307,9 +307,9 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { assertEq(interestRate, 0.05 * 10**18); assertEq(interestRateUpdate, _startTime); - (uint256 poolInflatorSnapshot, uint256 lastInflatorUpdate) = _NFTSubsetOnePool.inflatorInfo(); - assertEq(poolInflatorSnapshot, 10**18); - assertEq(lastInflatorUpdate, _startTime); + (uint256 poolInflator, uint256 lastInflatorUpdate) = _NFTSubsetOnePool.inflatorInfo(); + assertEq(poolInflator, 10**18); + assertEq(lastInflatorUpdate, _startTime); assertTrue(_NFTSubsetOnePool.tokenIdsAllowed(1)); assertTrue(_NFTSubsetOnePool.tokenIdsAllowed(5)); diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol index f1d744521..744df44af 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol @@ -157,7 +157,7 @@ contract ERC721PoolLiquidationsDepositTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582216822103492762 * 1e18, + htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000 * 1e18, pledgedCollateral: 5 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol index 02ab236d5..540385b18 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol @@ -177,7 +177,7 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582216822103492762 * 1e18, + htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000 * 1e18, pledgedCollateral: 5 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index fbea45f27..1f2fc03b0 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -183,7 +183,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582216822103492762 * 1e18, + htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000 * 1e18, pledgedCollateral: 5 * 1e18, @@ -290,7 +290,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 8.887140410855539624 * 1e18, + htp: 7.749209044755361552 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000.000966222327608000 * 1e18, pledgedCollateral: 4 * 1e18, @@ -362,7 +362,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582588772946404613 * 1e18, + htp: 5.739737879567360457 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000.000966222327608000 * 1e18, pledgedCollateral: 3.0 * 1e18, @@ -461,7 +461,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582216822103492762 * 1e18, + htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000 * 1e18, pledgedCollateral: 5 * 1e18, @@ -571,7 +571,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { _assertPool( PoolParams({ - htp: 6.582893111996772890 * 1e18, + htp: 5.739870563397816903 * 1e18, lup: 9.917184843435912074 * 1e18, poolSize: 73_000.001756788173660000 * 1e18, pledgedCollateral: 3 * 1e18, diff --git a/tests/forge/PoolCommonsTest.t.sol b/tests/forge/PoolCommonsTest.t.sol index dc39b3421..ee3ddbb92 100644 --- a/tests/forge/PoolCommonsTest.t.sol +++ b/tests/forge/PoolCommonsTest.t.sol @@ -11,15 +11,15 @@ contract PoolCommonsTest is DSTestPlus { * @notice Tests pending inflator calculation for varying parameters */ function testPendingInflator() external { - uint256 inflatorSnapshot = 0.01 * 1e18; + uint256 inflator = 0.01 * 1e18; skip(730 days); - uint256 lastInflatorSnapshotUpdate = block.timestamp - 365 days; + uint256 lastInflatorUpdate = block.timestamp - 365 days; uint256 interestRate = 0.1 * 1e18; - assertEq(PoolCommons.pendingInflator(inflatorSnapshot, lastInflatorSnapshotUpdate, interestRate), Maths.wmul(inflatorSnapshot,PRBMathUD60x18.exp(interestRate))); - assertEq(PoolCommons.pendingInflator(inflatorSnapshot, block.timestamp - 1 hours, interestRate), 0.010000114155902715 * 1e18); - assertEq(PoolCommons.pendingInflator(inflatorSnapshot, block.timestamp - 10 hours, interestRate), 0.010001141617671002 * 1e18); - assertEq(PoolCommons.pendingInflator(inflatorSnapshot, block.timestamp - 1 days, interestRate), 0.010002740101366609 * 1e18); - assertEq(PoolCommons.pendingInflator(inflatorSnapshot, block.timestamp - 5 hours, 0.042 * 1e18 ), 0.010000239728900849 * 1e18); + assertEq(PoolCommons.pendingInflator(inflator, lastInflatorUpdate, interestRate), Maths.wmul(inflator, PRBMathUD60x18.exp(interestRate))); + assertEq(PoolCommons.pendingInflator(inflator, block.timestamp - 1 hours, interestRate), 0.010000114155902715 * 1e18); + assertEq(PoolCommons.pendingInflator(inflator, block.timestamp - 10 hours, interestRate), 0.010001141617671002 * 1e18); + assertEq(PoolCommons.pendingInflator(inflator, block.timestamp - 1 days, interestRate), 0.010002740101366609 * 1e18); + assertEq(PoolCommons.pendingInflator(inflator, block.timestamp - 5 hours, 0.042 * 1e18 ), 0.010000239728900849 * 1e18); } /** diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 9331a5359..0187edb90 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -506,9 +506,9 @@ abstract contract DSTestPlus is Test, IPoolEvents { assertEq(loansCount, state_.loans); assertEq(maxBorrower, state_.maxBorrower); - (uint256 poolInflatorSnapshot, ) = _pool.inflatorInfo(); - assertGe(poolInflatorSnapshot, 1e18); - assertGe(pendingInflator, poolInflatorSnapshot); + (uint256 poolInflator, ) = _pool.inflatorInfo(); + assertGe(poolInflator, 1e18); + assertGe(pendingInflator, poolInflator); (uint256 interestRate, uint256 interestRateUpdate) = _pool.interestRateInfo(); assertEq(interestRate, state_.interestRate); From c5ea6b0c5f82c7692846f40f765013b184bb2466 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:13:37 +0200 Subject: [PATCH 17/70] Protocol invariants tests (#672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * Sherlock reviewed fixes - to be merged in master only after Sherlock review (#594) * Develop (#565) * reworked the RNG to use a deterministic seed (#523) * reworked the RNG to use a deterministic seed * reordered params such that they are in the same order as "bound" is called * Nonfuzzy fill (#529) * work in progress * reproduced fenwick tree failures at last index * proposed changes in light that max index is special case for prefix suim (#533) * proposed changes in light that max index is special case for prefix suim * removed comment Co-authored-by: mwc Co-authored-by: Ed Noepel * removed two temp tests * ported Matt's changes to the fuzzed implementation Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> Co-authored-by: mwc Co-authored-by: Ed Noepel Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> Co-authored-by: mwc Co-authored-by: Ed Noepel * Docs (#535) * Diagrams * Add components * Updates * Center components * add pool architectire diagrams (#536) Co-authored-by: prateek105 Co-authored-by: Prateek Gupta Co-authored-by: prateek105 * Test Coverage and Tests Style improvements (#541) * - Ajna balance less than rewards tests * NFT take tests * Loan heap test coverage * PoolCommons 100% test coverage - no interest earned when htp > max price * Test coverage move dusty amount * Changes after review: - comments - common style on touched tests * Common style across all unit tests * PositionManager and RewardsManager deployment (#547) * replaced bash script with forge script, update for PositionManager and RewardsManager * intentionally broke example addresses so no one tries to use them for anything * CI updates (#549) * testing set-output for size-check * update to cachev3 * make it look like an env var * use it like an env var * revert to old usage syntax * deleted redundant forge test, since code coverage already runs the tests * Made Token limitations more robust (#557) # Description of change ## High level * Altered `README.MD` to reflect a more robust definition of incompatible tokens --------- Co-authored-by: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> Co-authored-by: mwc Co-authored-by: Ed Noepel Co-authored-by: Prateek Gupta Co-authored-by: prateek105 Co-authored-by: Ian Harvey * Consistent naming, replace lpTokens with LPs (#543) * Consistent naming, replace lpTokens with LPs * Changes after review: replace _lpTokenAllowances with _lpAllowances * Apply test style * gas improvements (#542) * Gas improvements: - LenderActions.removeQuoteToken: reuse depositTime instead loading again from storage * RewardsManager: calculateAndClaimRewards, increment and then use next epoch * Remove duped calculation of toPrice from moveQuoteToken function * Take underflows when full pool debt repaid (#551) Take underflows when full pool debt repaid Scenario is exposed in testTakeAuctionRepaidAmountGreaterThanPoolDebt test: - when auction debt is fully repaid by take action then accrued pool debt value is less than repaid amount with 1 unit of WAD. When the repaid amount is subtracted from pool debt value (normally leaving no debt in pool) the operation underflows. - this happens because repaid debt is calculated using t0 value (t0 repaid debt * inflator) and then subtracted from already calculated pool debt - fix is to calculate pool debt as (t0 pool debt - t0 repaid debt) * inflator, hence preventing rounding issues The scope of this PR is extended to uniformize the t0 / accrued debt values calculation across all contracts by using pattern below: - when values relative to t0 are changed then t0 is updated first and then transformed in actual value (by multiplying it with inflator value) - all accumulations of pool or borrower debt are done on spot and not deferred to Pool contracts (which only save states) * Sherlock 103.md: Remaining collateral used by ERC721Pool is missed in Auctions take and bucketTake return structures (#566) * Nft pledge accumulator (#567) * Add test to expose pledgeCollateral inconsistency when settle NFT auction * Fix pledgedCollateral accumulator * Fix settle auction on bucket take * Add test for auction settle and compensate borrower when settle pool debt. Check pool collateral conistency across buckets * Fix for pledged collateral accumulator when settle auction with regular take * Reorg test, add _assertCollateralInvariants helper * Sherlock 162.md: ERC721Pool taker callback misreports quote funds whenever there was collateral amount rounding (#568) * Flashloan non18 decimals (#569) Fix flashloan, reserve auction and settle auction for non standard collateral / quote token precision Always use token precision when transferring during flashloans (and do not scale amounts to pool precision). For pool reserves use scaled pool token balance (WAD) so the reserves are accurately calculated. * Fix flashloan and reserve auction for non standard collateral / quote token precision * Changes after review: rename _getScaledPoolQuoteTokenBalance to _getNormalizedPoolQuoteTokenBalance * remove redundant code (#558) Co-authored-by: prateek105 * Reduce flashloans logic, introduce _isFlashloanSupported function (default returns true for quote token address, overriden in ERC20 pool to allow both quote and collateral token). Reorg the flashloan reverts conditions * Safety measure: (#588) - setting the remaining collateral to the current by default in drawDebt/repayDebt/_takeLoan functions - update alongside with borrower.colalteral when more collateral pledged Note: the remainingCollateral is used by NFT pool and only in the case of auction settlement. This commit doesn't change any logic but it's a safety measure for future developments that could alter this logic * Sherlock 104.md: Settled collateral of a borrower aren't available for lenders until borrower's debt is fully cleared (#570) - in ERC721Pool settle rebalance token ids if there's collateral settled - remove unused return param from Auction.settle - fix failing test * Sherlock 105.md: ERC721Pool's mergeOrRemoveCollateral allows to remove collateral while auction is clearable (#571) - check _revertIfAuctionClearable in mergeOrRemoveCollateral function - update tests: add _assertMergeRemoveCollateralAuctionNotClearedRevert and assert in ERC721 mergeOrRemove unit tests * Sherlock 101.md: Flashloan end result isn't controlled (#572) - record pool balance before transfer flash loaned ERC20 tokens - compare pool balance after flashloan with the initial / recorded balance - intorduce FlashloanIncorrectBalance error to revert if initial and current pool balances are different - update tests, introduce strategy that transfers tokens to pool, hence increasing pool balance, and expect FlashloanIncorrectBalance revert * Sherlock 183.md: RewardsManager doesn't delete old bucket snapshot info on unstaking (#573) - add getBucketStateStakeInfo view function to return info of recorded bucket at stake time - when unstake loop through positions and delete BucketState structs - in test helper _unstakeToken make sure there's no bucket state info remaining after token unstaked * Sherlock 151.md: Permanent freezing of unclaimed yield (#574) * Sherlock 151.md: Permanent freezing of unclaimed yield - new EpochNotAvailable custom error added - in claimRewards revert if epoch to claim is greater than latest burn epoch - add test * Changes after review: freeze typo * Sherlock 134.md: (#575) reported as Transferring funds to yourself increases your balance actually: Transferring funds to yourself reset balance to 0 (effect is that owner lose all their LPs as it is removed as bucket lender at LenderActions#L550) - added new custom error TransferToSameOwner, revert in Pool.transferLPs if new owner same as owner - added revert test on transfer to same address as owner address - tests style format * Sherlock 075.md: If borrower or kicker got blacklisted by asset contract their collateral or bond funds can be permanently frozen with the pool (#578) - Add recipient arg to withdrawBonds and repayDebt functions - add tests TBD: should we check and revert if recipient is 0x address * Sherlock 139.md (#579) * Sherlock 139.md: scaledQuoteTokenAmount isn't updated to be collateral sell value in the quote token constraint case of _calculateTakeFlowsAndBondChange - setting the `scaledQuoteTokenAmount` to the `C * p` as a final step of quote token amount constraint case - update tests * Added an example to show that the new exchange rate is invariant * Add tearDown to test, remove return --------- Co-authored-by: mwc * Sherlock 096.md: Interest rate for pool is bounded wrongly (#580) - allow creation of pools with min / max rate values - add tests * Sherlock 068.md (#581) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * Sherlock 068.md: ERC721Pool's take will proceed with truncated collateral amount and full debt when borrower's collateral is fractional - Early revert take action when borrower's collateral is less than `1` if the pool is ERC721 - ensure collateral, quote tokens and bond calculations are alwyas performed for the number of NFTs that will be taken: instead passing Maths.min(borrower_.collateral, params_.takeCollateral) to _calculateTakeFlowsAndBondChange function calculate the rounded down collateral that can be taken from borrower Example: if borrower has 1.2 worth of colalteral and taker pass an amount of 2 collateral to take then calculations happens for Min(rounded(1.2), 2) = 1 NFT token If borrower debt can be covered with less than 1 NFT then _calculateTakeFlowsAndBondChange will return a fractioned NFT (0.5 for example) and the difference between 1 NFT taken and 0.5 will be paid with excess quote tokens --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey * fix sherlock issues 120 and 121 (#576) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * fix sherlock issue 121 precision loss by multiplying before dividing * add _transferAjnaRewards method --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike * Sherlock 163,140, 34 & 31: Remove support for non standard NFTs (#585) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * Remove support for non standard NFTs (will have to be wrapped) * Sherlock 151: Changes for initial fix: (#596) * Sherlock 151: Changes for initial fix: - pass isManualEpoch_ param to _claimRewards function and do the epoch validation only if true (no need to validate for unstake which doesn't receive epoch as a param) - load stake struct from storage only once (and pass as a param to _claimRewards function) minimize calls to load ajna pool from stake storage * Changes after review: rename isManualEpoch to validateEpoch param * Sherlock 134.md changes after review: move check in LenderActions external library to keep logic in same place (#597) * Sherlock 98.md: (#577) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * add reentrancy guard to PositionManager.mint(); update mint natspec --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike * Sherlock 145: Take adjustments: `msg.sender` provides QT instead of `_callee` (#589) # Description of change ## High level * inside of `take` for ERC721 and ERC20 pools changed the argument passed in `_transferQuoteTokenFrom()` from `_callee` to `msg.sender`. * This update now makes the `_take` call match other implementations such as Maker * NOTE: 10-30K spike in gas cost because of change. # Description of bug or vulnerability and solution [ERC20Pool](https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC20Pool.sol#L403) and [ERC721Pool](https://github.com/sherlock-audit/2023-01-ajna/blob/main/contracts/src/ERC721Pool.sol#L405) implement the take functions, which buy collateral from auction in exchange for quote tokens. The address to pull quote tokens from is specified in the `_callee` argument, which allows anyone to call the functions and pass an address that has previously approved spending of the quote token to the pool. As a result, such an address will pay for the liquidation and will receive the collateral. The solution makes it so the `msg.sender` provides the QT when calling the `take()` method. # Contract size ## Pre Change ``` ============ Deployment Bytecode Sizes ============ ERC721Pool - 22,267B (90.60%) ERC20Pool - 20,972B (85.33%) ``` ## Post Change ``` ============ Deployment Bytecode Sizes ============ ERC721Pool - 22,267B (90.60%) ERC20Pool - 20,972B (85.33%) ``` # Gas usage ## Pre Change ``` | src/ERC20Pool.sol:ERC20Pool contract | | | | | | |--------------------------------------|-----------------|--------|--------|--------|---------| | take | 8550 | 158087 | 165977 | 520357 | 32 | src/ERC721Pool.sol:ERC721Pool contract | | | | | | |----------------------------------------|-----------------|--------|--------|--------|---------| | take | 11246 | 292029 | 375721 | 632601 | 11 | ``` ## Post Change ``` | src/ERC20Pool.sol:ERC20Pool contract | | | | | | |--------------------------------------|-----------------|--------|--------|--------|---------| | take | 8550 | 164634 | 184194 | 520356 | 33 | | src/ERC721Pool.sol:ERC721Pool contract | | | | | | |----------------------------------------|-----------------|--------|--------|----------|---------| | take | 11246 | 320810 | 387200 | 637400 | 12 | ``` * Sherlock 70: user can drawDebt that is below dust amount (#598) * ensure borrower debt exceeds dust amount regardless of loan count * Revert "ensure borrower debt exceeds dust amount regardless of loan count" This reverts commit c0a64f97c93e29b28533e9c1fc99fdf8f1b90cee. * re-applied changes lost when rebasing * Sherlock 68: (#599) - review update: add suggested revert to be excessively safe (for the case if current or future version of _calculateTakeFlowsAndBondChange() malfunctions producing collateral bigger than its collateral argument) * Sherlock 92: startClaimableReserveAuction using old inflator (#587) * Sherlock 92: startClaimableReserveAuction using old inflator * Add test to show interest accrued when start reserve auction * Revert "Sherlock 92: startClaimableReserveAuction using old inflator (#587)" This reverts commit 1414435c26f384ef13987bef1869b4b72326df46. * change safeTransferFrom to transferFrom to support Oasis (#592) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * change safeTransferFrom to transferFrom --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike * Sherlock 148: Use pool debt when calculating MOMP in Loans.update (#586) * Update comments: (#590) - ERC721.addCollateral changes bucketTokenIds - ERC721.repayDebt changes borrowerTokenIds and bucketTokenIds (when auction settled) - fix comment in Auctions._calculateTakeFlowsAndBondChange for take below NP * fix safeTransferFromWithPermit (#593) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * fix safeTransferFromWithPermit --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike * LPS and Exchange Rate as WADs - Sherlock 133 & 83 (#606) * LPs as WADs * - record lender LPs in remove collateral and quote token only if bucket doesn't go bankrupt - for remove colalteral use the same way to calculate LPs as when rewarding, to avoid precision issues - tearDown should consider that bucket can go bankrupt, hence lender LPs should be checked = 0 taking this into account (lenderInfo function is doing this) - all tests passing but testDepositTwoActorSameBucket fails in tearDown - that's because there are quote tokens remaining without being backed by LPs (were previously redeemed in tearDown for collateral) * Use consistent way to calculate LPs reported to scaled amounts, remove unscaled rate exchange, fix tests Couple of take tests fail in tearDown leaving small dust deposit * Cleanup Maths library, update comments * Fix remaining tests * Fix sequence to empty buckets, one single settle test remaining failing in tearDown * Add bucket clean out sequence, make consistent for removeCollateral too (per Sherlock 133) * Add bankruptcy check for move quote token from bucket and unit test * Guard against underflows in removeQuoteToken (due to roundings when transforming from scaled to unscaled amount) * Changes after review: - calculate amount using collateralAmount_ rather than bucketCollateral - remove dead code in remove quote token * Sherlock 39: expiration timestamp and slippage control (#600) * ignore forge script tx broadcasts * provide LUP limit when pulling collateral * expiration checks for adding to buckets * unit tests for addQuote and addCollateral expiration * expiration checks for moveQuote * ensure borrower debt exceeds dust amount regardless of loan count * Revert "ensure borrower debt exceeds dust amount regardless of loan count" This reverts commit c0a64f97c93e29b28533e9c1fc99fdf8f1b90cee. * feedback from PR #600 (#604) * removed comment * Address review comments, update comment for _revertIfLupDroppedBelowL… (#607) * Address review comments, update comment for _revertIfLupDroppedBelowLimit, changed LimitIndexReached to LimitIndexExceeded * Remove lup Id variable, calculate LUP price directly * Remove unused struct member --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> * Fix test compile * Protocol invariants testing (#609) * Invariants * Exclude invariants from code coverage and GH action * Fix coverage GH target * Changes after review * Disallow auctioned borrowers to draw more debt or pull collateral if auction is not settled. (#611) This can happen in a scenario where user becomes recollateralized to the new LUP. Without these checks, and since borrow and pull doesn't settle auctions, borrower is not removed from auction queue but added in loan queue as well, which violate invariant - **A3**: number of borrowers with debt = number of loans + number of auctioned borrowers * Sherlock 73 (#591) * Create standardized PR templates for contracts (#539) This PR creates three github PR templates that should be followed: * `logic_src_changes.md` * `cosmetic_src_changes.md` * `testing_or_misc_nonsrc_changes.md` To learn more about how GH templates work -> https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository Co-authored-by: Ian Harvey * Single template (#546) * merged all of the templates into one so the text will auto fill when a PR is created. * was running into this issue documented here with auto fill -> https://stackoverflow.com/questions/52139192/github-pull-requests-template-not-showing * update getNFTSubsetHash to uniquely sort array of tokenIds * add natspec and slight gas optimization * change encodePacked to encode to add byte padding * pool deployer gas optimization * check sort order instead of sorting on chain * pr feedback to check for duplicate tokenIds --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike --------- Co-authored-by: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> Co-authored-by: mwc Co-authored-by: Ed Noepel Co-authored-by: Prateek Gupta Co-authored-by: prateek105 Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Mike Hathaway Co-authored-by: Mike * ERC20Pool.removeCollateral should revert with InsufficientLPs error if the redeemed collateral amount is 0. * Add test to expose swap scenario with small amounts * Mul before div when calculating amount of collateral to remove * precalculate exchange rate logic added * return moved amount from moveQuoteToken * update reserve invariants, update liquidation handlers, fix local fenwick bugs, add new regression tests * Add unit test for failing regression sequence test_fenwick_bug_1 * change to repay debt with zero params from local fenwick for reserve and exchange rate precalculation * Brownie invariant tests sample (#548) * remove regression tests from make test command * add fenwick tree invariants F1, F2, F3, F4, auction invariant A6, bucket invariant B4, B5, Interest rate invariant I2 * Add settleAuction, kickWithDeposit, withdrawBonds, transferLps and approveLps handlers * Add useTimestamps modifier for handlers and useCurrentTimestamp modifier for invariant test to fix overflow/underflow issue * Fix: Local fenwick accure interest calculations, Add: Failing invariant sequences in regression test * Adapt to new develop branch * Use newly updateInterestRate function, catch InvalidAmounts, fix invariant_collateralBalance_CT1_CT7 (collateral amount is the 2nd in bucketInfo returned tuple) * Resolve 2 regression tests, new one to investigate * Use LPs before and after bucket take to check if kicker rewarded * Fix local fenwick accure interest bug by removing resetFenwickDepositUpdate method which is no longer needed * Fix: Deposit time updates only when non zero lps added * Added regression test for PR #674 * HPB bankruptcy on settle - if there's only a tiny amount of deposit (2) backed by 2 LPs in HPB then the collateralUsed to settle is calculated as 0 due to rounding (vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price)) - this results in removing deposit (2) and not adding any collateral into bucket, leaving an amount of 2 LPs that is not backed by any asset - fixed by declaring bucket bankruptcy in case settle leaves bucket with 0 collateral, 0 deposit but LPs > 0 - added check for deposit to remove to be min of available deposit in bucket / calculated deposit for settle in order to prevent any potential underflow - unit test * Update addQuoteToken and moveQuoteToken handler to add depositfee if deposit is added/moved below lup * Bucket take with tiny deposit - if deposit in bucket cannot cover at least 1 unit of collateral (that is calculated amount to take is zero) then taker reward is zero and bucket is left with no collateral, no deposit and unbacked LPs - fix by reverting if bucket take on a bucket with not enough deposit to cover at least one unit of collateral - unit test * Invariants code style * Invariants code restructure (#679) * Invariants code restructure * Consistent Invariants naming * Introduce _auctionSettleStateReset to set alreadyTaken on false if auction settled * rename TestBase to InvariantsTestBase and InvariantsTestBase to InvariantsTestHelpers --------- Co-authored-by: prateek105 * Only update bucket taker deposit time when LPs reward not 0 * Charge fee also if depositing at lup * Fix: moveQuotetoken handler to account for deposit fee * Update: Lup precalculation in moveQuoteToken handler * Fix invariant test move quote token amounts / fee calculation * Fix move quote token handler, use returned value from pool function which already contains the amount with applied fee (if case) * Catch specific errors * Add helper to ensure pool error * Use _borrowFeeRate, _depositFeeRate functions, reserves should increase if add and move quote token below the LUP * Invariants take2 cleanup (#682) * Code reorg, fix kick, add utility functions * Remove storing current exchange rate and reserves, compare directly with pool values * Do not bound in unbounded handlers, accrue fenwick interest and record reserves and exchange rate in modifier * Remove should exchange rate, add mapping bucketIndex -> exchangeShouldNotChange. In rate invariant check buckets with exchangeShouldNotChange flag set on true and compare previousExchangeRate with currentExchangeRate Move more bounds from unbounded handler * Add more invariants, code comment * Fix tests, intorduce pre action phases and reuse them in tests * Comments * Use global bucket index, do not fuzzed in _pre functions * Call preDrawDebt before drawDebt in kickAuction * Workaround reserve invariant rounding of 1 unit of WAD * Accrue local fenwick interest in at the end of the test * Accrue local fenwick interest before updating pool state * Changes after review: - rename invariant_Bucket_deposit_time_B5 to invariant_Bucket_deposit_time_B5_B6_B7 - rename modifier resetAllPreviousLocalState to updateLocalStateAndPoolInterest - rename function _recordReservesAndExchangeRate to _resetAndRecordReservesAndExchangeRate * Remove amount used for kick with deposit from local Fenwick * Remove amount of quote tokens needed for bucket take * Add reserve invariant RE11 * Add reserve invariant RE12 * Fix: reserves calculation in _settleAuction handler * Update: Reserve change calculation in _settleAuction handler --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey Co-authored-by: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> Co-authored-by: mwc Co-authored-by: Ed Noepel Co-authored-by: Prateek Gupta Co-authored-by: prateek105 Co-authored-by: Mike Hathaway Co-authored-by: Mike --- Makefile | 5 +- foundry.toml | 6 +- tests/INVARIANTS.md | 29 +- tests/README.md | 4 + tests/brownie/test_invariants.py | 640 ++++++++++++++++++ .../ERC20PoolLiquidationsDepositTake.t.sol | 2 +- .../invariants/BasicInvariants.t.sol | 283 ++++++-- ...iant.t.sol => LiquidationInvariants.t.sol} | 118 ++-- .../invariants/ReserveInvariants.t.sol | 94 ++- .../ERC20Pool/invariants/base/BaseHandler.sol | 409 +++++++++++ .../InvariantsTestBase.sol} | 25 +- .../InvariantsTestHelpers.sol} | 2 +- .../base/UnboundedBasicPoolHandler.sol | 279 ++++++++ .../base/UnboundedLiquidationPoolHandler.sol | 256 +++++++ .../base/UnboundedReservePoolHandler.sol | 41 ++ .../invariants/handlers/BaseHandler.sol | 153 ----- .../invariants/handlers/BasicPoolHandler.sol | 514 +++++++------- .../invariants/handlers/IBaseHandler.sol | 22 - .../handlers/LiquidationPoolHandler.sol | 228 ++++--- .../handlers/ReservePoolHandler.sol | 72 +- .../invariants/interfaces/IBaseHandler.sol | 30 + .../invariants/interfaces/ITestBase.sol | 11 + .../regression/RegressionTestBasic.t.sol | 320 ++++++--- .../RegressionTestLiquidation.t.sol | 50 +- .../regression/RegressionTestReserves.t.sol | 164 ++++- tests/forge/PositionManager.t.sol | 1 - 26 files changed, 2895 insertions(+), 863 deletions(-) create mode 100644 tests/brownie/test_invariants.py rename tests/forge/ERC20Pool/invariants/{LiquidationInvariant.t.sol => LiquidationInvariants.t.sol} (66%) create mode 100644 tests/forge/ERC20Pool/invariants/base/BaseHandler.sol rename tests/forge/ERC20Pool/invariants/{TestBase.sol => base/InvariantsTestBase.sol} (60%) rename tests/forge/ERC20Pool/invariants/{InvariantTest.sol => base/InvariantsTestHelpers.sol} (98%) create mode 100644 tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol create mode 100644 tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol create mode 100644 tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol delete mode 100644 tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol delete mode 100644 tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol create mode 100644 tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol create mode 100644 tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol diff --git a/Makefile b/Makefile index 5a0c59780..326902781 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,11 @@ install :; git submodule update --init --recursive build :; forge clean && forge build # Tests -test :; forge test --no-match-test "testLoad|invariant" # --ffi # enable if you need the `ffi` cheat code on HEVM +test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report -test-invariant :; forge t --mt invariant +test-invariant :; forge t --mt invariant --nmc RegressionTest +test-regression :; forge t --mt test_regression coverage :; forge coverage --no-match-test "testLoad|invariant" # Generate Gas Snapshots diff --git a/foundry.toml b/foundry.toml index c1b3e82d2..a58a30960 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,7 +12,7 @@ remappings = [ '@base64-sol=lib/base64/', 'src/=src/' ] -verbosity = 3 +verbosity = 2 block_timestamp = 1_672_372_127 block_number = 16_295_000 fork_block_number = 16_295_000 @@ -26,7 +26,7 @@ optimizer_runs = 200 runs = 300 [invariant] -runs = 10 # The number of calls to make in the invariant tests -depth = 100 # The number of times to run the invariant tests +runs = 1000 # The number of calls to make in the invariant tests +depth = 10 # The number of times to run the invariant tests call_override = false # Override calls fail_on_revert = false # Fail the test if the contract reverts \ No newline at end of file diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 8799e40a3..75de10952 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -34,7 +34,9 @@ - **B2**: bucket LPs accumulator (`Bucket.lps`) = 0 if no deposit / collateral in bucket - **B3**: if no collateral or deposit in bucket then the bucket exchange rate is `1e27` - **B4**: bankrupt bucket LPs accumulator = 0; lender LPs for deposits before bankruptcy time = 0 -- **B5**: when adding quote tokens: lender deposit time (`Lender.depositTime`) = timestamp of block when deposit happened (`block.timestamp`) +- **B5**: when adding / moving quote tokens or adding collateral : lender deposit time (`Lender.depositTime`) = timestamp of block when deposit happened (`block.timestamp`) +- **B6**: when receiving transferred LPs : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time +- **B7**: when awarded bucket take LPs : taker/kicker deposit time (`Lender.depositTime`) = timestamp of block when award happened (`block.timestamp`) ## Interest - **I1**: interest rate (`InterestState.interestRate`) cannot be updated more than once in a 12 hours period of time (`InterestState.interestRateUpdate`) @@ -45,11 +47,11 @@ - **F1**: Value represented at index `i` (`Deposits.valueAt(i)`) is equal to the accumulation of scaled values incremented or decremented from index `i` - **F2**: For any index `i`, the prefix sum up to and including `i` is the sum of values stored in indices `j<=i` - **F3**: For any index `i < MAX_FENWICK_INDEX`, `findIndexOfSum(prefixSum(i)) > i` -- **F4**: For any index `i`, there is zero deposit above `i` and below `findIndexOfSum(prefixSum(i))`: `prefixSum(findIndexOfSum(prefixSum(i))-1 == prefixSum(i)' +- **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j))`, where j is the next index from i with deposits != 0 ## Exchange rate invariants ## - **R1**: Exchange rates are unchanged by pledging collateral -- **R2**: Exchange rates are unchanged by removing collateral +- **R2**: Exchange rates are unchanged by pulling collateral - **R3**: Exchange rates are unchanged by depositing quote token into a bucket - **R4**: Exchange rates are unchanged by withdrawing deposit (quote token) from a bucket - **R5**: Exchange rates are unchanged by adding collateral token into a bucket @@ -58,12 +60,15 @@ - **R8**: Exchange rates are unchanged under arbTakes ## Reserves ## -- **RE1**: Reserves are unchanged by pledging collateral -- **RE2**: Reserves are unchanged by removing collateral -- **RE3**: Reserves are unchanged by depositing quote token into a bucket -- **RE4**: Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired -- **RE5**: Reserves are unchanged by adding collateral token into a bucket -- **RE6**: Reserves are unchanged by removing collateral token from a bucket -- **RE7**: Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) -- **RE8**: Reserves are unchanged under takes/depositTakes/arbTakes after the first take -- **RE9**: Reserves increase by .25% of the debt when a loan is kicked +- **RE1**: Reserves are unchanged by pledging collateral +- **RE2**: Reserves are unchanged by removing collateral +- **RE3**: Reserves increase only when depositing quote token into a bucket below LUP. Reserves increase only when moving quote tokens into a bucket below LUP. +- **RE4**: Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired +- **RE5**: Reserves are unchanged by adding collateral token into a bucket +- **RE6**: Reserves are unchanged by removing collateral token from a bucket +- **RE7**: Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) and increase/decrease by bond penalty/reward on take. +- **RE8**: Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. +- **RE9**: Reserves increase by 3 months of interest when a loan is kicked +- **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt +- **RE11**: Reserves decrease by claimableReserves by startClaimableReserveAuction +- **RE12**: Reserves decrease by amount of reserve used to settle a auction diff --git a/tests/README.md b/tests/README.md index 7cccef612..2c5668b17 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,6 +27,10 @@ make coverage brownie test ``` - to view `stdout` on long-running tests, use `brownie test -s`. +- run invariant tests (experimental, doesn't have full coverage): +```bash +brownie test --stateful true +``` #### Debugging Brownie integration tests - to drop into the console upon test failure: diff --git a/tests/brownie/test_invariants.py b/tests/brownie/test_invariants.py new file mode 100644 index 000000000..8605cb273 --- /dev/null +++ b/tests/brownie/test_invariants.py @@ -0,0 +1,640 @@ +import brownie +import pytest +from brownie.test import strategy +from brownie import Contract, chain +import hypothesis.strategies as st +from hypothesis.stateful import invariant +from hypothesis._settings import HealthCheck +from conftest import PoolHelper, MAX_PRICE + +############## Constants ############## + +MAX_BUCKET = 2690 +MIN_BUCKET = 2700 +NUM_LENDERS = 10 +NUM_BORROWERS = 15 +NUM_BIDDERS = 5 +NUM_KICKERS = 5 +NUM_TAKERS = 5 +MAX_LEND_AMOUNT = 10000*1e18 +MIN_LEND_AMOUNT = 1*1e18 +MAX_BORROW_AMOUNT = 1000*1e18 +MIN_BORROW_AMOUNT = 1*1e18 +MAX_BID_AMOUNT = 10*1e18 +MIN_BID_AMOUNT = 1*1e18 +MIN_TAKE_AMOUNT = 5*1e16 +MAX_TAKE_AMOUNT = 1*1e18 +MAX_NUMBER_OF_RULES = 1000 +MAX_NUMBER_OF_RUNS = 50 + +MAX_UINT256 = 2**256-1 + + +class _BasePoolStateMachine: + + """ + This base state machine class contains initialization and invariant + methods that are shared across multiple stateful tests. + """ + + ############## Strategies ############## + + st_sleep = st.integers(min_value=0, max_value=12 * 360) + st_index = st.integers(min_value=MAX_BUCKET, max_value=MIN_BUCKET) + st_lender = st.integers(min_value=0, max_value=NUM_LENDERS-1) + st_borrower = st.integers(min_value=0, max_value=NUM_BORROWERS-1) + st_bidder = st.integers(min_value=0, max_value=NUM_BIDDERS-1) + st_kicker = st.integers(min_value=0, max_value=NUM_KICKERS-1) + st_taker = st.integers(min_value=0, max_value=NUM_TAKERS-1) + + st_lend_amount = strategy("uint256", min_value=MIN_LEND_AMOUNT, max_value=MAX_LEND_AMOUNT) + st_borrow_amount = strategy("uint256", min_value=MIN_BORROW_AMOUNT, max_value=MAX_BORROW_AMOUNT) + st_bid_amount = strategy("uint256", min_value=MIN_BID_AMOUNT, max_value=MAX_BID_AMOUNT) + st_take_amount = strategy("uint256", min_value=MIN_TAKE_AMOUNT, max_value=MAX_TAKE_AMOUNT) + + + ############## Initialization ############## + + def __init__(self, ajna_protocol, scaled_pool, lenders, borrowers, bidders, kickers, takers): + self.pool = scaled_pool + self.lenders = lenders + self.borrowers = borrowers + self.bidders = bidders + self.kickers = kickers + self.takers = takers + self.pool_helper = PoolHelper(ajna_protocol, scaled_pool) + + + ############## Invariants ############## + + @invariant() + def pool_collateral_balance(self): + + # collateral inflows: + # - pledge collateral (borrower) + # - added collateral in bucket (bidder swap) + # collateral outflows: + # - pull collateral (borrower) + # - remove collateral from buckets (any actor with LPs in bucket: lender, borrower, kicker, taker) + # - auction take + + buckets_collateral = 0 + + for index in range(MAX_BUCKET, MIN_BUCKET + 1): + (_, bucket_collateral, _, _, _) = self.pool.bucketInfo(index) + buckets_collateral += bucket_collateral + + # Invariant 1: Pool collateral token balance = sum of collateral across all borrowers + sum of claimable collateral across all buckets + assert self.pool_helper.collateralToken().balanceOf(self.pool) == self.pool.pledgedCollateral() + buckets_collateral + + borrowers_collateral = 0 + + for borrower in self.borrowers: + (_, borrower_collateral, _) = self.pool.borrowerInfo(borrower) + borrowers_collateral += borrower_collateral + + # Invariant 2: total pledged collateral in pool = sum of collateral pledged across all borrowers + assert borrowers_collateral == self.pool.pledgedCollateral() + + @invariant() + def pool_quote_balance(self): + + # quote inflows: + # - add quote tokens (lender) + # - repay debt (borrower) + # - kick loan (kicker, lender) + # quote outflows: + # - draw debt (borrower) + # - remove quote tokens from bucket (any actor with LPs in bucket: lender, borrower, kicker, taker) + # - claim bonds (kicker, lender) + # - reward reserves auction + + liquidation_bonds = 0 + for kicker in self.kickers: + (claimable, locked) = self.pool.kickerInfo(kicker) + liquidation_bonds += claimable + locked + + # Invariant 3: Pool quote token balance (with penalties) >= liquidation bonds (locked + claimable) + pool deposit size - pool debt + assert self.pool_helper.quoteToken().balanceOf(self.pool) >= liquidation_bonds + self.pool_helper.pool.depositSize() - self.pool_helper.debt() + + @invariant() + def pool_global_debt(self): + borrowers_debt = 0 + + for borrower in self.borrowers: + (borrower_debt, _, _) = self.pool.borrowerInfo(borrower) + borrowers_debt += borrower_debt + + # Invariant 4: Global Debt Accumulator = sum of debt across all borrowers + assert self.pool.totalT0Debt() == borrowers_debt + + @invariant() + def pool_buckets(self): + for index in range(MAX_BUCKET, MIN_BUCKET + 1): + total_lenders_lps = 0 + + for lender in self.lenders: + (lender_lps, _) = self.pool.lenderInfo(index, lender) + total_lenders_lps += lender_lps + + for borrower in self.borrowers: + (borrower_lps, _) = self.pool.lenderInfo(index, borrower) + total_lenders_lps += borrower_lps + + for bidder in self.bidders: + (bidder_lps, _) = self.pool.lenderInfo(index, bidder) + total_lenders_lps += bidder_lps + + for kicker in self.kickers: + (kicker_lps, _) = self.pool.lenderInfo(index, kicker) + total_lenders_lps += kicker_lps + + for taker in self.takers: + (taker_lps, _) = self.pool.lenderInfo(index, taker) + total_lenders_lps += taker_lps + + (bucket_lps, bucket_collateral, _, bucket_deposit, _) = self.pool.bucketInfo(index) + # Invariant 5: sum of actors lps in bucket = bucket lps accumulator + assert bucket_lps == total_lenders_lps + + # Invariant 6: if no deposit / collateral in bucket then bucket LPs should be 0 + if bucket_collateral == 0 and bucket_deposit == 0: + assert bucket_lps == 0 + + + @invariant() + def pool_debt_in_auction(self): + auctioned_borrowers_debt = 0 + for borrower in self.borrowers: + (_, _, _, kick_time, _, _, _, _, _, _) = self.pool.auctionInfo(borrower) + if kick_time != 0: + (borrower_debt, _, _) = self.pool.borrowerInfo(borrower) + auctioned_borrowers_debt += borrower_debt + + # Invariant 7: debt in auction accumulator = sum of debt across all auctioned borrowers + assert self.pool.totalT0DebtInAuction() == auctioned_borrowers_debt + + @invariant() + def pool_auction_bonds(self): + (total_bond_escrowed, _, _, _) = self.pool.reservesInfo() + + kicker_bonds_locked = 0 + for kicker in self.kickers: + (_, locked) = self.pool.kickerInfo(kicker) + kicker_bonds_locked += locked + + auction_bonds_locked = 0 + for borrower in self.borrowers: + (_, _, bond_size, _, _, _, _, _, _, _) = self.pool.auctionInfo(borrower) + auction_bonds_locked += bond_size + + # Invariant 8: sum of bonds across all auctions = sum of locked balances across all kickers = total bond escrowed accumulator + assert total_bond_escrowed == kicker_bonds_locked == auction_bonds_locked + + @invariant() + def pool_loans_and_auctions(self): + (_, _, number_of_loans) = self.pool.loansInfo() + + number_of_auctions = 0 + borrowers_with_debt = 0 + for borrower in self.borrowers: + (borrower_debt, _, _) = self.pool.borrowerInfo(borrower) + if borrower_debt != 0: + borrowers_with_debt += 1 + + (_, _, _, kick_time, _, _, _, _, _, _) = self.pool.auctionInfo(borrower) + if kick_time != 0: + number_of_auctions += 1 + + # Invariant 9: number of borrowers with debt = number of loans + number of auctioned borrowers + assert borrowers_with_debt == number_of_loans + number_of_auctions + + + ############## Teardown ############## + + def teardown(self): + # TODO: verify pool invariants / health at the end of each run + print('Tear down') + + + ############## Utilities ############## + + @staticmethod + def _print_rule_result(message, success): + status = "succeeded" if success else "failed" + print(f"{message} {status}") + + +@pytest.fixture +def BasePoolStateMachine(): + yield _BasePoolStateMachine + + +@pytest.fixture +def borrowers(ajna_protocol, scaled_pool): + collateral_client = ajna_protocol.get_token(scaled_pool.collateralAddress()) + quote_client = ajna_protocol.get_token(scaled_pool.quoteTokenAddress()) + amount = int(150_000 * 10**18 / NUM_BORROWERS) + borrowers = [] + print("Initializing borrowers") + for _ in range(NUM_BORROWERS): + borrower = ajna_protocol.add_borrower() + collateral_client.top_up(borrower, amount) + collateral_client.approve_max(scaled_pool, borrower) + quote_client.top_up(borrower, 100_000 * 10**18) # for repayment of interest + quote_client.approve_max(scaled_pool, borrower) + assert collateral_client.get_contract().balanceOf(borrower) >= amount + borrowers.append(borrower) + return borrowers + + +@pytest.fixture +def lenders(ajna_protocol, scaled_pool): + quote_client = ajna_protocol.get_token(scaled_pool.quoteTokenAddress()) + amount = int(3_000_000_000 * 10**18 / NUM_LENDERS) + lenders = [] + print("Initializing lenders") + for _ in range(NUM_LENDERS): + lender = ajna_protocol.add_lender() + quote_client.top_up(lender, amount) + quote_client.approve_max(scaled_pool, lender) + lenders.append(lender) + return lenders + + +@pytest.fixture +def bidders(ajna_protocol, scaled_pool): + collateral_client = ajna_protocol.get_token(scaled_pool.collateralAddress()) + amount = int(100 * 10**18 / NUM_BIDDERS) + bidders = [] + print("Initializing bidders") + for _ in range(NUM_BIDDERS): + bidder = ajna_protocol.add_borrower() + collateral_client.top_up(bidder, amount) + collateral_client.approve_max(scaled_pool, bidder) + assert collateral_client.get_contract().balanceOf(bidder) >= amount + bidders.append(bidder) + return bidders + + +@pytest.fixture +def kickers(ajna_protocol, scaled_pool): + quote_client = ajna_protocol.get_token(scaled_pool.quoteTokenAddress()) + amount = int(3_000_000_000 * 10**18 / NUM_KICKERS) + kickers = [] + print("Initializing kickers") + for _ in range(NUM_KICKERS): + kicker = ajna_protocol.add_lender() + quote_client.top_up(kicker, amount) + quote_client.approve_max(scaled_pool, kicker) + kickers.append(kicker) + return kickers + + +@pytest.fixture +def takers(ajna_protocol, scaled_pool): + quote_client = ajna_protocol.get_token(scaled_pool.quoteTokenAddress()) + amount = int(3_000_000_000 * 10**18 / NUM_TAKERS) + takers = [] + print("Initializing takers") + for _ in range(NUM_TAKERS): + taker = ajna_protocol.add_lender() + quote_client.top_up(taker, amount) + quote_client.approve_max(scaled_pool, taker) + takers.append(taker) + return takers + +############## Tests ############## + + +def test_stateful_borrow_repay( + BasePoolStateMachine, + state_machine, + ajna_protocol, + scaled_pool, + lenders, + borrowers, + bidders, + kickers, + takers + ): + + """ + Stateful test that verifies draw debt / repay behavior + """ + + class PoolStateMachine(BasePoolStateMachine): + + + def setup(self): + # add some initial liquidity in the pool + self.pool.addQuoteToken(MAX_LEND_AMOUNT, MAX_BUCKET, chain.time() + 30, {"from": lenders[0]}) + + + ############## Lender rules ############## + + def rule_add_quote_token(self, st_lend_amount, st_index, st_lender, st_sleep): + # lend an arbitrary amount + lender = lenders[st_lender] + + lender_balance = self.pool_helper.quoteToken().balanceOf(lender) + + lend_amount = min(lender_balance, st_lend_amount) + + success = True + + try: + self.pool.addQuoteToken(lend_amount, st_index, chain.time() + 30, {"from": lenders[st_lender]}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"lender{st_lender}: add quote token {lend_amount} at index {st_index}", + success + ) + + + def rule_swap_quote_for_collateral(self, st_lend_amount, st_bid_amount, st_index, st_lender, st_bidder, st_sleep): + + success = True + + try: + (_, bucket_collateral, _, _, _) = self.pool.bucketInfo(st_index) + if bucket_collateral < st_bid_amount: + self.pool.addCollateral(st_bid_amount, st_index, chain.time() + 30, {"from": bidders[st_bidder]}) + + self.pool.addQuoteToken(st_lend_amount, st_index, chain.time() + 30, {"from": lenders[st_lender]}) + self.pool.removeCollateral(st_bid_amount, st_index, {"from": lenders[st_lender]}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"lender{st_lender}: swap quote {st_lend_amount} for collateral {st_bid_amount} from index {st_index}", + success + ) + + + ############## Borrower rules ############## + + def rule_draw_debt(self, st_borrow_amount, st_lender, st_borrower, st_sleep): + # borrow an arbitrary amount + + success = True + + try: + (min_debt, _, _, _) = self.pool_helper.utilizationInfo() + st_borrow_amount = max(st_borrow_amount, min_debt + 100*1e18) # borrow at least the min debt amount from pool + + pool_quote_on_deposit = self.pool_helper.pool.depositSize() - self.pool_helper.debt() + if pool_quote_on_deposit < st_borrow_amount: + self.pool.addQuoteToken(st_borrow_amount + 100*1e18, MAX_BUCKET, chain.time() + 30, {"from": lenders[st_lender]}) + + pool_price = self.pool_helper.lup() + if pool_price == MAX_PRICE: # if there is no LUP, + pool_price = self.pool_helper.hpb() # use the highest-priced bucket with deposit + + collateral_to_deposit = st_borrow_amount / pool_price * 2 * 10**18 + + self.pool.drawDebt(borrowers[st_borrower], st_borrow_amount, 7000, collateral_to_deposit, {"from": borrowers[st_borrower]}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"borrower{st_borrower}: draw debt {st_borrow_amount} and pledge {collateral_to_deposit}", + success + ) + + + def rule_repay_debt(self, st_borrow_amount, st_borrower, st_sleep): + # repay an arbitrary amount + borrower = borrowers[st_borrower] + + (debt, _, _) = self.pool_helper.borrowerInfo(borrower) + repay_amount = min(debt, st_borrow_amount) + + borrower_balance = self.pool_helper.quoteToken().balanceOf(borrower) + repay_amount = min(repay_amount, borrower_balance) + + success = True + + try: + self.pool.repayDebt(borrower, repay_amount, 0, borrower, 7000, {"from": borrower}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"borrower{st_borrower}: repay debt {repay_amount}", + success + ) + + + ############## Bidder rules ############## + + def rule_swap_collateral_for_quote(self, st_bid_amount, st_lend_amount, st_index, st_lender, st_bidder, st_sleep): + # bid an arbitrary amount + + success = True + + try: + (_, _, _, bucket_deposit, _) = self.pool.bucketInfo(st_index) + if bucket_deposit < st_lend_amount: + self.pool.addQuoteToken(st_lend_amount, st_index, chain.time() + 30, {"from": lenders[st_lender]}) + + self.pool.addCollateral(st_bid_amount, st_index, chain.time() + 30, {"from": bidders[st_bidder]}) + self.pool.removeQuoteToken(st_lend_amount, st_index, {"from": bidders[st_bidder]}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"bidder{st_bidder}: swap collateral {st_bid_amount} for quote {st_lend_amount} at index {st_index}", + success + ) + + + settings = {"stateful_step_count": MAX_NUMBER_OF_RULES, "max_examples": MAX_NUMBER_OF_RUNS} + state_machine( + PoolStateMachine, + ajna_protocol, + scaled_pool, + lenders, + borrowers, + bidders, + kickers, + takers, + settings=settings + ) + + +@pytest.mark.skip +def test_stateful_auctions( + BasePoolStateMachine, + state_machine, + ajna_protocol, + scaled_pool, + lenders, + borrowers, + bidders, + kickers, + takers + ): + + """ + Stateful test that verifies auctions behavior + """ + + class PoolStateMachine(BasePoolStateMachine): + + + def setup(self): + # add some initial liquidity in the pool + self.pool.addQuoteToken(MAX_BORROW_AMOUNT, MAX_BUCKET, chain.time() + 30, {"from": lenders[0]}) + self.pool.addQuoteToken(MAX_BORROW_AMOUNT, MIN_BUCKET, chain.time() + 30, {"from": lenders[0]}) + + + ############## Borrower rules ############## + + def rule_draw_debt(self, st_borrow_amount, st_lender, st_borrower, st_sleep): + # borrow an arbitrary amount + + success = True + + # make sure borrower doesn't have any debt or collateral pledged (to make sure kick and take actions succeed) + (debt, collateral_deposited, _) = self.pool_helper.borrowerInfo(borrowers[st_borrower]) + if debt != 0: + debt = MAX_UINT256 # make sure the entire debt is repaid + + self.pool.repayDebt(borrowers[st_borrower], debt, collateral_deposited, borrowers[st_borrower], 7000, {"from": borrowers[st_borrower]}) + + try: + (min_debt, _, _, _) = self.pool_helper.utilizationInfo() + st_borrow_amount = max(st_borrow_amount, min_debt + 100*1e18) # borrow at least the min debt amount from pool + + pool_quote_on_deposit = self.pool_helper.pool.depositSize() - self.pool_helper.debt() + if pool_quote_on_deposit < st_borrow_amount: + self.pool.addQuoteToken(st_borrow_amount + 100*1e18, MAX_BUCKET, chain.time() + 30, {"from": lenders[st_lender]}) + + pool_price = self.pool_helper.lup() + if pool_price == MAX_PRICE: # if there is no LUP, + pool_price = self.pool_helper.hpb() # use the highest-priced bucket with deposit + + collateral_to_deposit = st_borrow_amount / pool_price * 1.01 * 10**18 + + self.pool.drawDebt(borrowers[st_borrower], st_borrow_amount, 7000, collateral_to_deposit, {"from": borrowers[st_borrower]}) + + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"borrower{st_borrower}: draw debt {st_borrow_amount} and pledge {collateral_to_deposit}", + success + ) + + + def rule_repay_debt(self, st_borrow_amount, st_borrower, st_sleep): + # repay an arbitrary amount + borrower = borrowers[st_borrower] + + (debt, _, _) = self.pool_helper.borrowerInfo(borrower) + repay_amount = min(debt, st_borrow_amount) + + borrower_balance = self.pool_helper.quoteToken().balanceOf(borrower) + repay_amount = min(repay_amount, borrower_balance) + + success = True + + try: + self.pool.repayDebt(borrower, repay_amount, 0, borrower, 7000, {"from": borrower}) + + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"borrower{st_borrower}: repay debt {repay_amount}", + success + ) + + + ############## Kicker rules ############## + + def rule_kick_auction(self, st_borrow_amount, st_lender, st_borrower, st_kicker, st_sleep): + + # do not kick if already active + (_, _, _, kick_time, _, _, _, _, _, _) = self.pool.auctionInfo(borrowers[st_borrower]) + if kick_time != 0: + return + + success = True + + try: + # execute borrow rule to ensure loan + self.rule_draw_debt(st_borrow_amount, st_lender, st_borrower, st_sleep) + + # do not kick if borrower does not have debt + (debt, _, _) = self.pool_helper.borrowerInfo(borrowers[st_borrower]) + if debt == 0: + return + + # skip to make loan kickable + chain.sleep(86400 * 200) + chain.mine(2) + + # kick borrower + self.pool.kick(borrowers[st_borrower], 7_388, {"from": kickers[st_kicker]}) + + chain.sleep(st_sleep) + chain.mine(2) + except: + success = False + + self._print_rule_result( + f"kicker{st_kicker}: kick borrower borrower{st_borrower}", + success + ) + + + ############## Taker rules ############## + + def rule_take_auction(self, st_borrow_amount, st_take_amount, st_lender, st_borrower, st_kicker, st_taker, st_sleep): + + success = True + + # kick if auction not kicked already + (_, _, _, kick_time, _, _, _, _, _, _) = self.pool.auctionInfo(borrowers[st_borrower]) + if kick_time == 0: + self.rule_kick_auction(st_borrow_amount, st_lender, st_borrower, st_kicker, st_sleep) + + try: + # skip to take from auction + chain.sleep(3600 * 3) + chain.mine(2) + + self.pool.take(borrowers[st_borrower], st_take_amount, takers[st_taker], bytes(), {"from": takers[st_taker]}) + chain.sleep(st_sleep) + except: + success = False + + self._print_rule_result( + f"taker{st_taker}: take collateral {st_take_amount} from borrower{st_borrower}", + success + ) + + + settings = {"stateful_step_count": MAX_NUMBER_OF_RULES, "max_examples": MAX_NUMBER_OF_RUNS} + state_machine( + PoolStateMachine, + ajna_protocol, + scaled_pool, + lenders, + borrowers, + bidders, + kickers, + takers, + settings=settings + ) \ No newline at end of file diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 67bbf5ef0..31ad2a943 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -863,4 +863,4 @@ contract ERC20PoolLiquidationsDepositTakeRegressionTest is ERC20HelperContract { exchangeRate: 1 * 1e18 }); } -} \ No newline at end of file +} diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 6227d2486..96c3ed260 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -1,21 +1,23 @@ - // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import '@std/Test.sol'; import "@std/console.sol"; -import { TestBase } from './TestBase.sol'; - import { Maths } from 'src/libraries/internal/Maths.sol'; -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, BasicPoolHandler } from './handlers/BasicPoolHandler.sol'; +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX, + BasicPoolHandler +} from './handlers/BasicPoolHandler.sol'; -import { IBaseHandler } from './handlers/IBaseHandler.sol'; +import { InvariantsTestBase } from './base/InvariantsTestBase.sol'; +import { IBaseHandler } from './interfaces/IBaseHandler.sol'; // contains invariants for the test -contract BasicInvariants is TestBase { +contract BasicInvariants is InvariantsTestBase { /**************************************************************************************************************************************/ /*** Invariant Tests ***/ @@ -24,6 +26,10 @@ contract BasicInvariants is TestBase { * B1: totalBucketLPs === totalLenderLps * B2: bucketLps == 0 (if bucket quote and collateral is 0) * B3: exchangeRate == 0 (if bucket quote and collateral is 0) + * B4: bankrupt bucket LPs accumulator = 0; lender LPs for deposits before bankruptcy time = 0 + * B5: block.timestamp == lenderDepositTime (if lps are added to lender lp balance) + * B6: block.timestamp == max(sender's depositTime, receiver's depositTime), when receiving transferred LPs + * B7: lenderDepositTime == block.timestamp (timestamp of block when taker is rewarded by bucketTake) * Quote Token * QT1: poolQtBal + poolDebt >= totalBondEscrowed + poolDepositSize * QT2: pool t0 debt = sum of all borrower's t0 debt @@ -39,7 +45,14 @@ contract BasicInvariants is TestBase { * Interest Rate * I1: Interest rate should only update once in 12 hours + * I2: ReserveAuctionState.totalInterestEarned accrues only once per block and equals to 1e18 if pool debt = 0 * I3: Inflator should only update once per block + + * Fenwick tree + * F1: Value represented at index i (Deposits.valueAt(i)) is equal to the accumulation of scaled values incremented or decremented from index i + * F2: For any index i, the prefix sum up to and including i is the sum of values stored in indices j<=i + * F3: For any index i < MAX_FENWICK_INDEX, findIndexOfSum(prefixSum(i)) > i + * F4: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)), where j is the next index from i with deposits != 0 ****************************************************************************************************************************************/ uint256 internal constant NUM_ACTORS = 10; @@ -49,18 +62,28 @@ contract BasicInvariants is TestBase { // bucket exchange rate tracking mapping(uint256 => uint256) internal previousBucketExchangeRate; - uint256 previousInterestRateUpdate; - uint256 previousInflator; - uint256 previousInflatorUpdate; + uint256 previousInterestRateUpdate; + uint256 previousTotalInterestEarned; + uint256 previousTotalInterestEarnedUpdate; + function setUp() public override virtual{ super.setUp(); - _basicPoolHandler = new BasicPoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); + _basicPoolHandler = new BasicPoolHandler( + address(_pool), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + _handler = address(_basicPoolHandler); + excludeContract(address(_collateral)); excludeContract(address(_quote)); excludeContract(address(_poolFactory)); @@ -80,25 +103,37 @@ contract BasicInvariants is TestBase { } // checks pool lps are equal to sum of all lender lps in a bucket - function invariant_Lps_B1() public { + function invariant_Lps_B1_B4() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { uint256 totalLps; + for (uint256 i = 0; i < actorCount; i++) { address lender = IBaseHandler(_handler).actors(i); (uint256 lps, ) = _pool.lenderInfo(bucketIndex, lender); + totalLps += lps; } + (uint256 bucketLps, , , , ) = _pool.bucketInfo(bucketIndex); + assertEq(bucketLps, totalLps, "Incorrect Bucket/lender lps"); } } // checks bucket lps are equal to 0 if bucket quote and collateral are 0 // checks exchange rate is 1e27 if bucket quote and collateral are 0 - function invariant_Buckets_B2_B3() public view { + function invariant_Buckets_B2_B3() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - ( ,uint256 deposit, uint256 collateral, uint256 bucketLps, ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); + ( + , + uint256 deposit, + uint256 collateral, + uint256 bucketLps, + , + uint256 exchangeRate + ) = _poolInfo.bucketInfo(address(_pool), bucketIndex); if (collateral == 0 && deposit == 0) { require(bucketLps == 0, "Incorrect bucket lps"); @@ -107,24 +142,51 @@ contract BasicInvariants is TestBase { } } - // checks pool quote token balance is greater than equals total deposits in pool - function invariant_quoteTokenBalance_QT1() public { - uint256 poolBalance = _quote.balanceOf(address(_pool)); - uint256 t0debt = _pool.totalT0Debt(); - (uint256 inflator, ) = _pool.inflatorInfo(); - uint256 poolDebt = Maths.wmul(t0debt, inflator); - (uint256 totalBondEscrowed, uint256 unClaimed, , ) = _pool.reservesInfo(); + // checks if lender deposit timestamp is updated when lps are added into lender lp balance + function invariant_Bucket_deposit_time_B5_B6_B7() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + for (uint256 i = 0; i < actorCount; i++) { + address lender = IBaseHandler(_handler).actors(i); + + (, uint256 depositTime) = _pool.lenderInfo(bucketIndex, lender); + + require( + depositTime == IBaseHandler(_handler).lenderDepositTime(lender, bucketIndex), + "Incorrect deposit Time" + ); + } + } + } - assertGe(poolBalance + poolDebt, totalBondEscrowed + _pool.depositSize() + unClaimed, "Incorrect pool quote token"); + // checks pool quote token balance is greater than equals total deposits in pool + function invariant_quoteTokenBalance_QT1() public useCurrentTimestamp { + uint256 poolBalance = _quote.balanceOf(address(_pool)); + (uint256 poolDebt, , ) = _pool.debtInfo(); + + ( + uint256 totalBondEscrowed, + uint256 unClaimed, + , + ) = _pool.reservesInfo(); + + assertGe( + poolBalance + poolDebt, totalBondEscrowed + _pool.depositSize() + unClaimed, + "Incorrect pool quote token" + ); } // checks pools collateral Balance to be equal to collateral pledged - function invariant_collateralBalance_CT1_CT7() public { + function invariant_collateralBalance_CT1_CT7() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 totalCollateralPledged; - for(uint256 i = 0; i < actorCount; i++) { + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); + ( , uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); + totalCollateralPledged += borrowerCollateral; } @@ -134,7 +196,8 @@ contract BasicInvariants is TestBase { uint256 bucketCollateral; for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - (, , uint256 collateral , , ) = _pool.bucketInfo(bucketIndex); + (, uint256 collateral, , , ) = _pool.bucketInfo(bucketIndex); + bucketCollateral += collateral; } @@ -142,12 +205,14 @@ contract BasicInvariants is TestBase { } // checks pool debt is equal to sum of all borrowers debt - function invariant_pooldebt_QT2() public view { + function invariant_pooldebt_QT2() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); uint256 totalDebt; - for(uint256 i = 0; i < actorCount; i++) { + + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); (uint256 debt, , ) = _pool.borrowerInfo(borrower); + totalDebt += debt; } @@ -156,36 +221,44 @@ contract BasicInvariants is TestBase { require(poolDebt == totalDebt, "Incorrect pool debt"); } - function _invariant_exchangeRate_RE1_RE2_R3_R4_R5_R6() public { + function invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); - if (!IBaseHandler(_handler).shouldExchangeRateChange()) { + uint256 currentExchangeRate = _pool.bucketExchangeRate(bucketIndex); + + if (IBaseHandler(_handler).exchangeRateShouldNotChange(bucketIndex)) { + uint256 previousExchangeRate = IBaseHandler(_handler).previousExchangeRate(bucketIndex); + console.log("======================================"); - console.log("Bucket Index -->", bucketIndex); - console.log("Previous exchange Rate -->", previousBucketExchangeRate[bucketIndex]); - console.log("Current exchange Rate -->", exchangeRate); - requireWithinDiff(exchangeRate, previousBucketExchangeRate[bucketIndex], 1e12, "Incorrect exchange Rate changed"); + console.log("Bucket Index -->", bucketIndex); + console.log("Previous exchange Rate -->", previousExchangeRate); + console.log("Current exchange Rate -->", currentExchangeRate); console.log("======================================"); + + requireWithinDiff( + currentExchangeRate, + previousExchangeRate, + 1e17, // TODO: check why changing so much + "Incorrect exchange Rate changed" + ); } - previousBucketExchangeRate[bucketIndex] = exchangeRate; } } - function invariant_loan_L1_L2_L3() public view { + function invariant_loan_L1_L2_L3() public useCurrentTimestamp { (address borrower, uint256 tp) = _pool.loanInfo(0); // first loan in loan heap should be 0 require(borrower == address(0), "Incorrect borrower"); - require(tp == 0, "Incorrect threshold price"); + require(tp == 0, "Incorrect threshold price"); ( , , uint256 totalLoans) = _pool.loansInfo(); - for(uint256 loanId = 1; loanId < totalLoans; loanId++) { + for (uint256 loanId = 1; loanId < totalLoans; loanId++) { (borrower, tp) = _pool.loanInfo(loanId); // borrower address and threshold price should not 0 require(borrower != address(0), "Incorrect borrower"); - require(tp != 0, "Incorrect threshold price"); + require(tp != 0, "Incorrect threshold price"); // tp of a loan at index 'i' in loan array should be greater than equals to loans at index '2i' and '2i+1' (, uint256 tp1) = _pool.loanInfo(2 * loanId); @@ -197,27 +270,143 @@ contract BasicInvariants is TestBase { } // interest should only update once in 12 hours - function invariant_interest_rate_I1() public { + function invariant_interest_rate_I1() public useCurrentTimestamp { (, uint256 currentInterestRateUpdate) = _pool.interestRateInfo(); if (currentInterestRateUpdate != previousInterestRateUpdate) { - require(currentInterestRateUpdate - previousInterestRateUpdate >= 12 hours, "Incorrect interest rate update"); + require( + currentInterestRateUpdate - previousInterestRateUpdate >= 12 hours, + "Incorrect interest rate update" + ); } + previousInterestRateUpdate = currentInterestRateUpdate; } + // reserve.totalInterestEarned should only update once per block + function invariant_total_interest_earned_I2() public useCurrentTimestamp { + (, , , uint256 totalInterestEarned) = _pool.reservesInfo(); + + if (previousTotalInterestEarnedUpdate == block.number) { + require( + totalInterestEarned == previousTotalInterestEarned, + "Incorrect total interest earned" + ); + } + + previousTotalInterestEarnedUpdate = block.number; + previousTotalInterestEarned = totalInterestEarned; + } + // inflator should only update once per block - function invariant_inflator_I3() public { + function invariant_inflator_I3() public useCurrentTimestamp { (uint256 currentInflator, uint256 currentInflatorUpdate) = _pool.inflatorInfo(); - if(currentInflatorUpdate == previousInflatorUpdate) { + + if (currentInflatorUpdate == previousInflatorUpdate) { require(currentInflator == previousInflator, "Incorrect inflator update"); } - previousInflator = currentInflator; + + uint256 poolT0Debt = _pool.totalT0Debt(); + if(poolT0Debt == 0) require(currentInflator == 1e18, "Incorrect inflator update"); + + previousInflator = currentInflator; previousInflatorUpdate = currentInflatorUpdate; } - function invariant_call_summary() external view virtual { + // deposits at index i (Deposits.valueAt(i)) is equal to the accumulation of scaled values incremented or decremented from index i + function invariant_fenwick_depositAtIndex_F1() public useCurrentTimestamp { + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); + + console.log("===================Bucket Index : ", bucketIndex, " ==================="); + console.log("Deposit From Pool -->", depositAtIndex); + console.log("Deposit From local fenwick tree -->", IBaseHandler(_handler).fenwickSumAtIndex(bucketIndex)); + console.log("========================================="); + + requireWithinDiff( + depositAtIndex, + IBaseHandler(_handler).fenwickSumAtIndex(bucketIndex), + 1e16, + "Incorrect deposits in bucket" + ); + } + } + + // For any index i, the prefix sum up to and including i is the sum of values stored in indices j<=i + function invariant_fenwick_depositsTillIndex_F2() public useCurrentTimestamp { + uint256 depositTillIndex; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); + + depositTillIndex += depositAtIndex; + + console.log("===================Bucket Index : ", bucketIndex, " ==================="); + console.log("Deposit From Pool -->", depositTillIndex); + console.log("Deposit From local fenwick tree -->", IBaseHandler(_handler).fenwickSumTillIndex(bucketIndex)); + console.log("========================================="); + + requireWithinDiff( + depositTillIndex, + IBaseHandler(_handler).fenwickSumTillIndex(bucketIndex), + 1e16, + "Incorrect deposits prefix sum" + ); + } + } + + // For any index i < MAX_FENWICK_INDEX, findIndexOfSum(prefixSum(i)) > i + function invariant_fenwick_bucket_index_F3() public useCurrentTimestamp { + uint256 prefixSum; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); + + if (depositAtIndex != 0) { + prefixSum += depositAtIndex; + uint256 bucketIndexFromDeposit = _pool.depositIndex(prefixSum); + + console.log("===================Bucket Index : ", bucketIndex, " ==================="); + console.log("Bucket Index from deposit -->", bucketIndexFromDeposit); + console.log("========================================="); + + require(bucketIndexFromDeposit >= bucketIndex, "Incorrect bucket index"); + } + } + } + + // For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)) where j is the next index from i with deposits != 0 + function invariant_fenwick_prefixSumIndex_F4() public useCurrentTimestamp { + uint256 prefixSum; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); + + if (depositAtIndex != 0) { + prefixSum += depositAtIndex; + uint256 nextBucketIndexWithDeposit = _pool.depositIndex(prefixSum + 1); + (, , , uint256 depositAtNextBucket, ) = _pool.bucketInfo(nextBucketIndexWithDeposit); + uint256 prefixSumTillNextBucket = prefixSum + depositAtNextBucket; + + console.log("============"); + console.log("Deposit Index of presum -->", _pool.depositIndex(prefixSum)); + console.log("Presum -->", prefixSum); + console.log("depositAtNextBucket ===>", depositAtNextBucket); + console.log("prefixSumTillNextBucket -->", prefixSumTillNextBucket); + console.log("nextBucketIndexWithDeposit -->", nextBucketIndexWithDeposit); + console.log("BucketIndex -->", bucketIndex); + console.log("Pool deposit Index -->", _pool.depositIndex(prefixSumTillNextBucket - depositAtNextBucket)); + + require( + bucketIndex == _pool.depositIndex(prefixSumTillNextBucket - depositAtNextBucket), + "Incorrect buckets with 0 deposit" + ); + } + } + } + + function invariant_call_summary() external virtual useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); console.log("BBasicHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); @@ -231,8 +420,8 @@ contract BasicInvariants is TestBase { console.log("--Borrower--------"); console.log("BBasicHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.drawDebt")); console.log("UBBasicHandler.drawDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.drawDebt")); - console.log("BBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt")); - console.log("UBBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.repayDebt")); + console.log("BBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt")); + console.log("UBBasicHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.repayDebt")); console.log("------------------"); console.log( "Sum", diff --git a/tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol b/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol similarity index 66% rename from tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol rename to tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol index 10cfbffe1..e1e9d91f9 100644 --- a/tests/forge/ERC20Pool/invariants/LiquidationInvariant.t.sol +++ b/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol @@ -2,28 +2,30 @@ pragma solidity 0.8.14; -import '@std/Test.sol'; import "@std/console.sol"; -import { TestBase } from './TestBase.sol'; - -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX } from './handlers/BasicPoolHandler.sol'; +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX +} from './handlers/BasicPoolHandler.sol'; import { LiquidationPoolHandler } from './handlers/LiquidationPoolHandler.sol'; import { BasicInvariants } from './BasicInvariants.t.sol'; -import { IBaseHandler } from './handlers/IBaseHandler.sol'; +import { IBaseHandler } from './interfaces/IBaseHandler.sol'; -contract LiquidationInvariant is BasicInvariants { +contract LiquidationInvariants is BasicInvariants { /**************************************************************************************************************************************/ /*** Invariant Tests ***/ /*************************************************************************************************************************************** * Auction - * A1: totalDebtInAuction = sum of all debt of all borrowers kicked - * A2: totalBondEscrowed = sum of all kicker's bond = total Bond in Auction - * A3: number of borrowers with debt = number of loans + number of auctioned borrowers - * A4: number of auctions = total borrowers kicked - * A5: for each auction, kicker locked bond is more than equal to auction bond + * A1: totalDebtInAuction = sum of all debt of all borrowers kicked + * A2: totalBondEscrowed = sum of all kicker's bond = total Bond in Auction + * A3: number of borrowers with debt = number of loans + number of auctioned borrowers + * A4: number of auctions = total borrowers kicked + * A5: for each auction, kicker locked bond is more than equal to auction bond + * A6: if a Liquidation is not taken then the take flag (Liquidation.alreadyTaken) should be False, if already taken then the take flag should be True ****************************************************************************************************************************************/ LiquidationPoolHandler internal _liquidationPoolHandler; @@ -34,88 +36,116 @@ contract LiquidationInvariant is BasicInvariants { excludeContract(address(_basicPoolHandler)); - _liquidationPoolHandler = new LiquidationPoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); + _liquidationPoolHandler = new LiquidationPoolHandler( + address(_pool), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + _handler = address(_liquidationPoolHandler); } // checks sum of all borrower's t0debt is equals to total pool t0debtInAuction - function invariant_debtInAuction_A1() public view { + function invariant_debtInAuction_A1() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); uint256 totalT0debtInAuction; - for(uint256 i = 0; i < actorCount; i++) { + + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - if(kickTime != 0) { + + if (kickTime != 0) { (uint256 t0debt, , ) = _pool.borrowerInfo(borrower); totalT0debtInAuction += t0debt; } } + require(_pool.totalT0DebtInAuction() == totalT0debtInAuction, "Incorrect debt in auction"); } // checks sum of all kicker bond is equal to total pool bond - function invariant_bond_A2() public view { + function invariant_bond_A2() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); uint256 totalKickerBond; - for(uint256 i = 0; i < actorCount; i++) { - address kicker = IBaseHandler(_handler).actors(i); - (uint256 bondLocked, uint256 bondClaimable) = _pool.kickerInfo(kicker); - totalKickerBond += (bondLocked + bondClaimable); - } - uint256 totalBondInAuction; + for (uint256 i = 0; i < actorCount; i++) { + address kicker = IBaseHandler(_handler).actors(i); + (uint256 claimable, uint256 bond) = _pool.kickerInfo(kicker); - for(uint256 i = 0; i < actorCount; i++) { - address borrower = IBaseHandler(_handler).actors(i); - (, , uint256 bondSize, , , , , , , ) = _pool.auctionInfo(borrower); - totalBondInAuction += bondSize; + totalKickerBond += bond + claimable; } - require(totalBondInAuction == totalKickerBond, "Incorrect bond"); - (uint256 totalPoolBond, , , ) = _pool.reservesInfo(); require(totalPoolBond == totalKickerBond, "Incorrect bond"); - } + } // checks total borrowers with debt is equals to sum of borrowers unkicked and borrowers kicked // checks total auctions is equals to total borrowers kicked - function invariant_auctions_A3_A4() public view { + function invariant_auctions_A3_A4() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); uint256 totalBorrowersWithDebt; - for(uint256 i = 0; i < actorCount; i++) { + + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); (uint256 t0Debt, , ) = _pool.borrowerInfo(borrower); - if(t0Debt > 0) { - totalBorrowersWithDebt += 1; - } + + if (t0Debt > 0) totalBorrowersWithDebt += 1; } + ( , , uint256 loansCount) = _pool.loansInfo(); uint256 totalAuction = _pool.totalAuctionsInPool(); - require(totalBorrowersWithDebt == loansCount + totalAuction, "incorrect no of borrowers in LoanState"); + + require( + totalBorrowersWithDebt == loansCount + totalAuction, + "incorrect no of borrowers in LoanState" + ); uint256 borrowersKicked; - for(uint256 i = 0; i < actorCount; i++) { + + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); + (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - if(kickTime != 0) { - borrowersKicked += 1; - } + + if (kickTime != 0) borrowersKicked += 1; } + require(borrowersKicked == totalAuction, "Incorrect borrowers in auction"); } - function invariant_borrowers_A5() public view { + // for each auction, kicker locked bond is more than equal to auction bond + function invariant_borrowers_A5() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); - for(uint256 i = 0; i < actorCount; i++) { + + for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); (address kicker, , uint256 bondSize, , , , , , , ) = _pool.auctionInfo(borrower); (, uint256 lockedAmount) = _pool.kickerInfo(kicker); + require(lockedAmount >= bondSize, "Incorrect bond locked"); } } - function invariant_call_summary() external view virtual override{ + // if a Liquidation is not taken then the take flag (Liquidation.alreadyTaken) should be False, if already taken then the take flag should be True + function invariant_auction_taken_A6() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (, , , , , , , , , bool alreadyTaken) = _pool.auctionInfo(borrower); + + require( + alreadyTaken == IBaseHandler(_handler).alreadyTaken(borrower), + "Incorrect take call on auction" + ); + } + } + + function invariant_call_summary() external virtual override useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); console.log("BLiquidationHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); @@ -132,9 +162,9 @@ contract LiquidationInvariant is BasicInvariants { console.log("BLiquidationHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.repayDebt")); console.log("UBLiquidationHandler.repayDebt ", IBaseHandler(_handler).numberOfCalls("UBBasicHandler.repayDebt")); console.log("BLiquidationHandler.kickAuction ", IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.kickAuction")); - console.log("UBLiquidationHandler.kickAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.kickAuction")); + console.log("UBLiquidationHandler.kickAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.kickAuction")); console.log("BLiquidationHandler.takeAuction ", IBaseHandler(_handler).numberOfCalls("BLiquidationHandler.takeAuction")); - console.log("UBLiquidationHandler.takeAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.takeAuction")); + console.log("UBLiquidationHandler.takeAuction ", IBaseHandler(_handler).numberOfCalls("UBLiquidationHandler.takeAuction")); console.log("------------------"); console.log( "Sum", diff --git a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol index 0b9095fc8..75436425c 100644 --- a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol @@ -2,21 +2,41 @@ pragma solidity 0.8.14; -import '@std/Test.sol'; -import "@std/console.sol"; - -import { TestBase } from './TestBase.sol'; +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX } from './handlers/BasicPoolHandler.sol'; - -import { ReservePoolHandler } from './handlers/ReservePoolHandler.sol'; -import { LiquidationInvariant } from './LiquidationInvariant.t.sol'; -import { IBaseHandler } from './handlers/IBaseHandler.sol'; +import "@std/console.sol"; -contract ReserveInvariants is LiquidationInvariant { +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX +} from './handlers/BasicPoolHandler.sol'; + +import { ReservePoolHandler } from './handlers/ReservePoolHandler.sol'; +import { LiquidationInvariants } from './LiquidationInvariants.t.sol'; +import { IBaseHandler } from './interfaces/IBaseHandler.sol'; + +contract ReserveInvariants is LiquidationInvariants { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + * Reserves + * RE1 : Reserves are unchanged by pledging collateral + * RE2 : Reserves are unchanged by removing collateral + * RE3 : Reserves are unchanged by depositing quote token into a bucket + * RE4 : Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired + * RE5 : Reserves are unchanged by adding collateral token into a bucket + * RE6 : Reserves are unchanged by removing collateral token from a bucket + * RE7 : Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) and increase/decrease by bond penalty/reward on take. + * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. + * RE9 : Reserves increase by 3 months of interest when a loan is kicked + * RE10: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt + * RE11: Reserves decrease by claimableReserves by startClaimableReserveAuction + * RE12: Reserves decrease by amount of reserve used to settle a auction + ****************************************************************************************************************************************/ ReservePoolHandler internal _reservePoolHandler; - uint256 previousReserves; function setUp() public override virtual { @@ -24,36 +44,36 @@ contract ReserveInvariants is LiquidationInvariant { excludeContract(address(_liquidationPoolHandler)); - _reservePoolHandler = new ReservePoolHandler(address(_pool), address(_quote), address(_collateral), address(_poolInfo), NUM_ACTORS); - _handler = address(_reservePoolHandler); + _reservePoolHandler = new ReservePoolHandler( + address(_pool), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); - (previousReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + _handler = address(_reservePoolHandler); } - // FIXME - function _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9() public { + function invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12() public useCurrentTimestamp { + uint256 previousReserves = IBaseHandler(_handler).previousReserves(); + uint256 increaseInReserves = IBaseHandler(_handler).increaseInReserves(); + uint256 decreaseInReserves = IBaseHandler(_handler).decreaseInReserves(); (uint256 currentReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Current Reserves -->", currentReserves); - console.log("Previous Reserves -->", previousReserves); - if(!IBaseHandler(_handler).shouldReserveChange()) { - require(currentReserves == previousReserves, "Incorrect Reserves change"); - } - - uint256 firstTakeIncreaseInReserve = IBaseHandler(_handler).firstTakeIncreaseInReserve(); - - console.log("firstTakeIncreaseInReserve -->", firstTakeIncreaseInReserve); - if(IBaseHandler(_handler).firstTake()) { - requireWithinDiff(currentReserves, previousReserves + firstTakeIncreaseInReserve, 1e2, "Incorrect Reserves change with first take"); - } - - uint256 loanKickIncreaseInReserve = IBaseHandler(_handler).loanKickIncreaseInReserve(); - - console.log("loanKickIncreaseInReserve -->", loanKickIncreaseInReserve); - if(loanKickIncreaseInReserve != 0) { - requireWithinDiff(currentReserves, previousReserves + loanKickIncreaseInReserve, 1e2, "Incorrect Reserves change with kick"); - } - previousReserves = currentReserves; - } + console.log("Previous Reserves -->", previousReserves); + console.log("Increase in Reserves -->", increaseInReserves); + console.log("Decrease in Reserves -->", decreaseInReserves); + console.log("Current Reserves -->", currentReserves); + + // TODO: check why rouding of 1 unit of WAD. Decrease reserve on startClaimableReserveAuction too + requireWithinDiff( + currentReserves, + previousReserves + increaseInReserves - decreaseInReserves, + 1, + "Incorrect Reserves change" + ); + } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol new file mode 100644 index 000000000..0f46ee686 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import '@std/Test.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { PoolCommons } from 'src/libraries/external/PoolCommons.sol'; +import { + MAX_FENWICK_INDEX, + MAX_PRICE, + MIN_PRICE +} from 'src/libraries/helpers/PoolHelper.sol'; + +import { Token } from '../../../utils/Tokens.sol'; + +import 'src/libraries/internal/Maths.sol'; +import '../interfaces/ITestBase.sol'; + +uint256 constant LENDER_MIN_BUCKET_INDEX = 2570; +uint256 constant LENDER_MAX_BUCKET_INDEX = 2572; + +uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; +uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; + +abstract contract BaseHandler is Test { + + // Tokens + Token internal _quote; + Token internal _collateral; + + // Pool + ERC20Pool internal _pool; + PoolInfoUtils internal _poolInfo; + + // Test invariant contract + ITestBase internal testContract; + + // Modifiers + address internal _actor; + uint256 internal _lenderBucketIndex; + uint256 internal _limitIndex; + + // deposits invariant test state + uint256[7389] internal fenwickDeposits; + mapping(address => mapping(uint256 => uint256)) public lenderDepositTime; // mapping of lender address to bucket index to deposit time + + address[] public actors; + mapping(bytes32 => uint256) public numberOfCalls; // Logging + mapping(address => uint256[]) public touchedBuckets; // Bucket tracking + + // exchange rate invariant test state + mapping(uint256 => bool) public exchangeRateShouldNotChange; // bucket exchange rate invariant check + mapping(uint256 => uint256) public previousExchangeRate; // mapping from bucket index to exchange rate before action + + // reserves invariant test state + uint256 public previousReserves; // reserves before action + uint256 public increaseInReserves; // amount of reserve decrease + uint256 public decreaseInReserves; // amount of reserve increase + + // auctions invariant test state + bool public firstTake; // if take is called on auction first time + mapping(address => bool) public alreadyTaken; // mapping borrower address to true if auction taken atleast once + + constructor( + address pool_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) { + // Tokens + _quote = Token(quote_); + _collateral = Token(collateral_); + + // Pool + _pool = ERC20Pool(pool_); + _poolInfo = PoolInfoUtils(poolInfo_); + + // Actors + actors = _buildActors(numOfActors_); + + // Test invariant contract + testContract = ITestBase(testContract_); + } + + /*****************/ + /*** Modifiers ***/ + /*****************/ + + /** + * @dev Use and update test invariant contract timestamp to make timestamp consistent throughout invariant test run. + */ + modifier useTimestamps() { + vm.warp(testContract.currentTimestamp()); + + _; + + testContract.setCurrentTimestamp(block.timestamp); + } + + /** + * @dev Resets all local states before each action. + */ + modifier updateLocalStateAndPoolInterest() { + _fenwickAccrueInterest(); + _updatePoolState(); + + _resetAndRecordReservesAndExchangeRate(); + + _; + } + + modifier useRandomActor(uint256 actorIndex_) { + vm.stopPrank(); + + _actor = actors[constrictToRange(actorIndex_, 0, actors.length - 1)]; + + vm.startPrank(_actor); + _; + vm.stopPrank(); + } + + modifier useRandomLenderBucket(uint256 bucketIndex_) { + uint256[] storage lenderBucketIndexes = touchedBuckets[_actor]; + + 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); + + lenderBucketIndexes.push(_lenderBucketIndex); + } else { + // if actor has touched more than three buckets, reuse one of the touched buckets + _lenderBucketIndex = lenderBucketIndexes[constrictToRange(bucketIndex_, 0, lenderBucketIndexes.length - 1)]; + } + + _; + } + + /*****************************/ + /*** Pool Helper Functions ***/ + /*****************************/ + + function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { + address[] memory actorsAddress = new address[](noOfActors_); + + for (uint i = 0; i < noOfActors_; i++) { + address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); + actorsAddress[i] = actor; + + vm.startPrank(actor); + + _quote.mint(actor, 1e45); + _quote.approve(address(_pool), 1e45); + + _collateral.mint(actor, 1e45); + _collateral.approve(address(_pool), 1e45); + + vm.stopPrank(); + } + + return actorsAddress; + } + + function _updatePoolState() internal { + _pool.updateInterest(); + } + + /** + * @dev Ensure that error is an Pool expected error. + */ + function _ensurePoolError(bytes memory err_) internal pure { + bytes32 err = keccak256(err_); + + require( + err == keccak256(abi.encodeWithSignature("InvalidAmount()")) || + err == keccak256(abi.encodeWithSignature("BucketBankruptcyBlock()")) || + err == keccak256(abi.encodeWithSignature("LUPBelowHTP()")) || + err == keccak256(abi.encodeWithSignature("InsufficientLiquidity()")) || + err == keccak256(abi.encodeWithSignature("RemoveDepositLockedByAuctionDebt()")) || + err == keccak256(abi.encodeWithSignature("NoClaim()")) || + err == keccak256(abi.encodeWithSignature("MoveToSameIndex()")) || + err == keccak256(abi.encodeWithSignature("DustAmountNotExceeded()")) || + err == keccak256(abi.encodeWithSignature("InvalidIndex()")) || + err == keccak256(abi.encodeWithSignature("InsufficientLPs()")) || + err == keccak256(abi.encodeWithSignature("AuctionNotCleared()")) || + err == keccak256(abi.encodeWithSignature("TransferorNotApproved()")) || + err == keccak256(abi.encodeWithSignature("TransferToSameOwner()")) || + err == keccak256(abi.encodeWithSignature("NoAllowance()")) || + err == keccak256(abi.encodeWithSignature("InsufficientCollateral()")) || + err == keccak256(abi.encodeWithSignature("AuctionActive()")) || + err == keccak256(abi.encodeWithSignature("BorrowerUnderCollateralized()")) || + err == keccak256(abi.encodeWithSignature("NoDebt()")) || + err == keccak256(abi.encodeWithSignature("AmountLTMinDebt()")) || + err == keccak256(abi.encodeWithSignature("BorrowerOk()")) || + err == keccak256(abi.encodeWithSignature("LimitIndexExceeded()")) || + err == keccak256(abi.encodeWithSignature("PriceBelowLUP()")) || + err == keccak256(abi.encodeWithSignature("NoAuction()")) || + err == keccak256(abi.encodeWithSignature("TakeNotPastCooldown()")) || + err == keccak256(abi.encodeWithSignature("AuctionPriceGtBucketPrice()")) || + err == keccak256(abi.encodeWithSignature("AuctionNotClearable()")) || + err == keccak256(abi.encodeWithSignature("ReserveAuctionTooSoon()")) || + err == keccak256(abi.encodeWithSignature("NoReserves()")) || + err == keccak256(abi.encodeWithSignature("NoReservesAuction()")), + "Unexpected revert error" + ); + } + + /**************************************/ + /*** Exchange Rate Helper Functions ***/ + /**************************************/ + + /** + * @dev Record the reserves and exchange rates before each action. + */ + function _resetAndRecordReservesAndExchangeRate() internal { + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + // reset the change flag before each action + exchangeRateShouldNotChange[bucketIndex] = false; + // record exchange rate before each action + previousExchangeRate[bucketIndex] = _pool.bucketExchangeRate(bucketIndex); + } + + // reset the reserves before each action + increaseInReserves = 0; + decreaseInReserves = 0; + // record reserves before each action + (previousReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); + } + + /********************************/ + /*** Fenwick Helper Functions ***/ + /********************************/ + + function _fenwickAdd(uint256 amount_, uint256 bucketIndex_) internal { + fenwickDeposits[bucketIndex_] += amount_; + } + + function _fenwickRemove(uint256 removedAmount_, uint256 bucketIndex_) internal { + fenwickDeposits[bucketIndex_] -= removedAmount_; + } + + function _fenwickAccrueInterest() internal { + ( , , , , uint256 pendingFactor) = _poolInfo.poolLoansInfo(address(_pool)); + + // poolLoansInfo returns 1e18 if no interest is pending or time elapsed... the contracts calculate 0 time elapsed which causes discrep + if (pendingFactor == 1e18) return; + + // get TP of worst loan, pendingInflator and poolDebt + uint256 maxThresholdPrice; + uint256 pendingInflator; + uint256 poolDebt; + { + (, poolDebt ,) = _pool.debtInfo(); + + (uint256 inflator, uint256 inflatorUpdate) = _pool.inflatorInfo(); + + (, maxThresholdPrice,) = _pool.loansInfo(); + maxThresholdPrice = Maths.wdiv(maxThresholdPrice, inflator); + + (uint256 interestRate, ) = _pool.interestRateInfo(); + + pendingInflator = PoolCommons.pendingInflator( + inflator, + inflatorUpdate, + interestRate + ); + } + + // get HTP and deposit above HTP + uint256 htp = Maths.wmul(maxThresholdPrice, pendingInflator); + uint256 htpIndex; + + if (htp > MAX_PRICE) htpIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest + else if (htp < MIN_PRICE) htpIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest + else htpIndex = _poolInfo.priceToIndex(htp); + + uint256 depositAboveHtp = fenwickSumTillIndex(htpIndex); + + if (depositAboveHtp != 0) { + uint256 utilization = _pool.depositUtilization(); + uint256 lenderInterestMargin = PoolCommons.lenderInterestMargin(utilization); + + uint256 newInterest = Maths.wmul( + lenderInterestMargin, + Maths.wmul(pendingFactor - Maths.WAD, poolDebt) + ); + + uint256 scale = Maths.wdiv(newInterest, depositAboveHtp) + Maths.WAD; + + // simulate scale being applied to all deposits above HTP + _fenwickMult(htpIndex, scale); + } + } + + function _fenwickMult(uint256 index_, uint256 scale_) internal { + while (index_ > 0) { + fenwickDeposits[index_] = Maths.wmul(fenwickDeposits[index_], scale_); + + index_--; + } + } + + /*********************************/ + /*** Auctions Helper Functions ***/ + /*********************************/ + + /** + * @dev Called by actions that can settle auctions in order to reset test state. + */ + function _auctionSettleStateReset(address actor_) internal { + (address kicker, , , , , , , , , ) = _pool.auctionInfo(actor_); + + // auction is settled if kicekr is 0x + bool auctionSettled = kicker == address(0); + // reset alreadyTaken flag if auction is settled + if (auctionSettled) alreadyTaken[actor_] = false; + } + + function _getKickerBond(address kicker_) internal view returns (uint256 bond_) { + (uint256 claimableBond, uint256 lockedBond) = _pool.kickerInfo(kicker_); + bond_ = claimableBond + lockedBond; + } + + function _updateCurrentTakeState(address borrower_, uint256 borrowerDebt_) internal { + if (!alreadyTaken[borrower_]) { + alreadyTaken[borrower_] = true; + + // **RE7**: Reserves increase by 7% of the loan quantity upon the first take. + increaseInReserves += Maths.wmul(borrowerDebt_, 0.07 * 1e18); + firstTake = true; + + // reset taken flag in case auciton was settled by take action + _auctionSettleStateReset(borrower_); + + } else firstTake = false; + } + + /**********************************/ + /*** Fenwick External Functions ***/ + /**********************************/ + + function fenwickSumTillIndex(uint256 index_) public view returns (uint256 sum_) { + while (index_ > 0) { + sum_ += fenwickDeposits[index_]; + + index_--; + } + } + + function fenwickIndexForSum(uint256 debt_) public view returns (uint256) { + uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; + + while (debt_ != 0 && bucketIndex <= LENDER_MAX_BUCKET_INDEX) { + if (fenwickDeposits[bucketIndex] >= debt_) return bucketIndex; + + debt_ -= fenwickDeposits[bucketIndex]; + + bucketIndex += 1; + } + + return MAX_FENWICK_INDEX; + } + + function fenwickSumAtIndex(uint256 index_) public view returns(uint256) { + return fenwickDeposits[index_]; + } + + function fenwickTreeSum() external view returns (uint256) { + return fenwickSumTillIndex(fenwickDeposits.length - 1); + } + + /*************************************/ + /*** Test Utils External Functions ***/ + /*************************************/ + + function getActorsCount() external view returns(uint256) { + return actors.length; + } + + function constrictToRange( + uint256 x_, + uint256 min_, + uint256 max_ + ) pure public returns (uint256 result_) { + require(max_ >= min_, "MAX_LESS_THAN_MIN"); + + uint256 size = max_ - min_; + + if (size == 0) return min_; // Using max would be equivalent as well. + if (max_ != type(uint256).max) size++; // Make the max inclusive. + + // Ensure max is inclusive in cases where x != 0 and max is at uint max. + if (max_ == type(uint256).max && x_ != 0) x_--; // Accounted for later. + + if (x_ < min_) x_ += size * (((min_ - x_) / size) + 1); + + result_ = min_ + ((x_ - min_) % size); + + // Account for decrementing x to make max inclusive. + if (max_ == type(uint256).max && x_ != 0) result_++; + } + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/TestBase.sol b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol similarity index 60% rename from tests/forge/ERC20Pool/invariants/TestBase.sol rename to tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol index 1e626e676..f4dea6090 100644 --- a/tests/forge/ERC20Pool/invariants/TestBase.sol +++ b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol @@ -3,18 +3,18 @@ pragma solidity 0.8.14; import '@std/Test.sol'; -import "forge-std/console.sol"; import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; -import { Token } from '../../utils/Tokens.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { InvariantTest } from './InvariantTest.sol'; -contract TestBase is InvariantTest, Test { +import { Token } from '../../../utils/Tokens.sol'; +import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol'; + +abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { // Mainnet ajna address - address internal _ajna = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079; + address internal _ajna = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079; Token internal _quote; Token internal _collateral; @@ -24,6 +24,15 @@ contract TestBase is InvariantTest, Test { PoolInfoUtils internal _poolInfo; ERC20PoolFactory internal _poolFactory; + uint256 public currentTimestamp; + + // use current timestamp for invariants + modifier useCurrentTimestamp { + vm.warp(currentTimestamp); + + _; + } + function setUp() public virtual { // Tokens _quote = new Token("Quote", "Q"); @@ -34,5 +43,11 @@ contract TestBase is InvariantTest, Test { _pool = ERC20Pool(_poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18)); _poolInfo = new PoolInfoUtils(); _impl = _poolFactory.implementation(); + + currentTimestamp = block.timestamp; + } + + function setCurrentTimestamp(uint256 currentTimestamp_) external { + currentTimestamp = currentTimestamp_; } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/InvariantTest.sol b/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol similarity index 98% rename from tests/forge/ERC20Pool/invariants/InvariantTest.sol rename to tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol index a1e7ab0e6..0edcb4584 100644 --- a/tests/forge/ERC20Pool/invariants/InvariantTest.sol +++ b/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.14; -contract InvariantTest { +abstract contract InvariantsTestHelpers { struct FuzzSelector { address addr; diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol new file mode 100644 index 000000000..e7cb15a32 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; + +import "src/libraries/internal/Maths.sol"; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX, + BaseHandler +} from './BaseHandler.sol'; + +/** + * @dev this contract manages multiple lenders + * @dev methods in this contract are called in random order + * @dev randomly selects a lender contract to make a txn + */ +abstract contract UnboundedBasicPoolHandler is BaseHandler { + + /*******************************/ + /*** Lender Helper Functions ***/ + /*******************************/ + + function _addQuoteToken( + uint256 amount_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.addQuoteToken']++; + + (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + (uint256 poolDebt, , ) = _pool.debtInfo(); + uint256 lupIndex = _pool.depositIndex(poolDebt); + (uint256 interestRate, ) = _pool.interestRateInfo(); + + try _pool.addQuoteToken(amount_, bucketIndex_, block.timestamp + 1 minutes) { + + // **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; + + bool depositBelowLup = lupIndex != 0 && bucketIndex_ > lupIndex; + if (depositBelowLup) { + uint256 intialAmount = amount_; + amount_ = Maths.wmul( + amount_, + Maths.WAD - _depositFeeRate(interestRate) + ); + // **RE3**: Reserves increase only when depositing quote token into a bucket below LUP + increaseInReserves += intialAmount - amount_; + } + + _fenwickAdd(amount_, bucketIndex_); + + // Post action condition + (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _removeQuoteToken( + uint256 amount_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.removeQuoteToken']++; + + (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + + 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; + + _fenwickRemove(removedAmount_, bucketIndex_); + + // Post action condition + (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _moveQuoteToken( + uint256 amount_, + uint256 fromIndex_, + uint256 toIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + + try _pool.moveQuoteToken( + amount_, + fromIndex_, + 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); + // **RE3**: Reserves increase only when moving quote tokens into a bucket below LUP. + increaseInReserves += amount_ - movedAmount_; + + _fenwickRemove(amount_, fromIndex_); + _fenwickAdd(movedAmount_, toIndex_); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _addCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.addCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + + _pool.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, ) = _pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + } + + function _removeCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.removeCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + + try _pool.removeCollateral(amount_, bucketIndex_) { + + // **R6**: Exchange rates are unchanged by removing collateral token from a bucket + exchangeRateShouldNotChange[bucketIndex_] = true; + + // Post action condition + (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _increaseLPsAllowance( + address receiver_, + uint256 bucketIndex_, + uint256 amount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + // approve as transferor + address[] memory transferors = new address[](1); + transferors[0] = receiver_; + _pool.approveLPsTransferors(transferors); + + uint256[] memory buckets = new uint256[](1); + buckets[0] = bucketIndex_; + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount_; + _pool.increaseLPsAllowance(receiver_, buckets, amounts); + } + + function _transferLps( + address sender_, + address receiver_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + uint256[] memory buckets = new uint256[](1); + buckets[0] = bucketIndex_; + + changePrank(receiver_); + + try _pool.transferLPs(sender_, receiver_, buckets) { + + (, uint256 senderDepositTime) = _pool.lenderInfo(bucketIndex_, sender_); + (, uint256 receiverDepositTime) = _pool.lenderInfo(bucketIndex_, receiver_); + + // **B6**: when receiving transferred LPs : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time + lenderDepositTime[receiver_][bucketIndex_] = Maths.max(senderDepositTime, receiverDepositTime); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /*********************************/ + /*** Borrower Helper Functions ***/ + /*********************************/ + + function _pledgeCollateral( + uint256 amount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pledgeCollateral']++; + + // **R1**: Exchange rates are unchanged by pledging collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + _pool.drawDebt(_actor, 0, 0, amount_); + } + + function _pullCollateral( + uint256 amount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pullCollateral']++; + + // **R2**: Exchange rates are unchanged by pulling collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + try _pool.repayDebt(_actor, 0, amount_, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _drawDebt( + uint256 amount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.drawDebt']++; + + (uint256 poolDebt, , ) = _pool.debtInfo(); + + // find bucket to borrow quote token + uint256 bucket = _pool.depositIndex(amount_ + poolDebt) - 1; + uint256 price = _poolInfo.indexToPrice(bucket); + uint256 collateralToPledge = ((amount_ * 1e18 + price / 2) / price) * 101 / 100 + 1; + + try _pool.drawDebt(_actor, amount_, 7388, collateralToPledge) { + + (uint256 interestRate, ) = _pool.interestRateInfo(); + + // **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt + increaseInReserves += Maths.wmul( + amount_, _borrowFeeRate(interestRate) + ); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + + // skip to make borrower undercollateralize + vm.warp(block.timestamp + 200 days); + } + + function _repayDebt( + uint256 amountToRepay_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.repayDebt']++; + + try _pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol new file mode 100644 index 000000000..67ca6eed5 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; +import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; + +import 'src/libraries/internal/Maths.sol'; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BaseHandler +} from './BaseHandler.sol'; + +abstract contract UnboundedLiquidationPoolHandler is BaseHandler { + + /*******************************/ + /*** Kicker Helper Functions ***/ + /*******************************/ + + function _kickAuction( + address borrower_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBLiquidationHandler.kickAuction']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + (uint256 interestRate, ) = _pool.interestRateInfo(); + + try _pool.kick(borrower_, 7388) { + + // **RE9**: Reserves increase by 3 months of interest when a loan is kicked + increaseInReserves += Maths.wmul(borrowerDebt, Maths.wdiv(interestRate, 4 * 1e18)); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _kickWithDeposit( + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + (address maxBorrower, , ) = _pool.loansInfo(); + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), maxBorrower); + (uint256 interestRate, ) = _pool.interestRateInfo(); + ( , , , uint256 depositBeforeAction, ) = _pool.bucketInfo(bucketIndex_); + + try _pool.kickWithDeposit(bucketIndex_, 7388) { + + ( , , , uint256 depositAfterAction, ) = _pool.bucketInfo(bucketIndex_); + + // **RE9**: Reserves increase by 3 months of interest when a loan is kicked + increaseInReserves += Maths.wmul(borrowerDebt, Maths.wdiv(interestRate, 4 * 1e18)); + + _fenwickRemove(depositBeforeAction - depositAfterAction, bucketIndex_); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _withdrawBonds( + address kicker_, + uint256 maxAmount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + + try _pool.withdrawBonds(kicker_, maxAmount_) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /******************************/ + /*** Taker Helper Functions ***/ + /******************************/ + + function _takeAuction( + address borrower_, + uint256 amount_, + address taker_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBLiquidationHandler.takeAuction']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); + + uint256 totalBondBeforeTake = _getKickerBond(kicker); + + try _pool.take(borrower_, amount_, taker_, bytes("")) { + + uint256 totalBondAfterTake = _getKickerBond(kicker); + + if (totalBondBeforeTake > totalBondAfterTake) { + // **RE7**: Reserves increase by bond penalty on take. + increaseInReserves += totalBondBeforeTake - totalBondAfterTake; + } else { + // **RE7**: Reserves decrease by bond reward on take. + decreaseInReserves += totalBondAfterTake - totalBondBeforeTake; + } + + _updateCurrentTakeState(borrower_, borrowerDebt); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _bucketTake( + address taker_, + address borrower_, + bool depositTake_, + uint256 bucketIndex_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + numberOfCalls['UBLiquidationHandler.bucketTake']++; + + (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + + (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); + (uint256 kickerLpsBeforeTake, ) = _pool.lenderInfo(bucketIndex_, kicker); + (uint256 takerLpsBeforeTake, ) = _pool.lenderInfo(bucketIndex_, _actor); + ( , , , uint256 depositBeforeAction, ) = _pool.bucketInfo(bucketIndex_); + + uint256 totalBondBeforeTake = _getKickerBond(kicker); + + try _pool.bucketTake(borrower_, depositTake_, bucketIndex_) { + + (uint256 kickerLpsAfterTake, ) = _pool.lenderInfo(bucketIndex_, kicker); + (uint256 takerLpsAfterTake, ) = _pool.lenderInfo(bucketIndex_, _actor); + ( , , , uint256 depositAfterAction, ) = _pool.bucketInfo(bucketIndex_); + + // **B7**: when awarded bucket take LPs : taker deposit time = timestamp of block when award happened + if (takerLpsAfterTake > takerLpsBeforeTake) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; + + if (kickerLpsAfterTake > kickerLpsBeforeTake) { + // **B7**: when awarded bucket take LPs : kicker deposit time = timestamp of block when award happened + lenderDepositTime[kicker][bucketIndex_] = block.timestamp; + } else { + // **RE7**: Reserves increase by bond penalty on take. + increaseInReserves += _getKickerBond(kicker) - totalBondBeforeTake; + } + + // **R7**: Exchange rates are unchanged under depositTakes + // **R8**: Exchange rates are unchanged under arbTakes + exchangeRateShouldNotChange[bucketIndex_] = true; + + _fenwickRemove(depositBeforeAction - depositAfterAction, bucketIndex_); + + _updateCurrentTakeState(borrower_, borrowerDebt); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /********************************/ + /*** Settler Helper Functions ***/ + /********************************/ + + function _settleAuction( + address borrower_, + uint256 maxDepth_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + ( + uint256 borrowerDebt, + uint256 collateral, + ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + + uint256 noOfBuckets = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX + 1; + + uint256[] memory changeInDeposit = new uint256[](noOfBuckets); + uint256 depositUsed; + + uint256 bucketDepth = maxDepth_; + + // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb + while (bucketDepth != 0 && borrowerDebt != 0 && collateral != 0) { + uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + uint256 bucketUsed = bucketIndex - LENDER_MIN_BUCKET_INDEX; + uint256 maxSettleableDebt = Maths.wmul(collateral, _priceAt(bucketIndex)); + + if (bucketIndex != MAX_FENWICK_INDEX) { + // debt is greater than bucket deposit then exchange all deposit with collateral + if (borrowerDebt > fenwickDeposits[bucketIndex] && maxSettleableDebt >= fenwickDeposits[bucketIndex]) { + borrowerDebt -= fenwickDeposits[bucketIndex]; + changeInDeposit[bucketUsed] += fenwickDeposits[bucketIndex]; + collateral -= Maths.wdiv(fenwickDeposits[bucketIndex], _priceAt(bucketIndex)); + depositUsed += fenwickDeposits[bucketIndex]; + } + // collateral value is greater than borrower debt then exchange collateral with deposit + else if (maxSettleableDebt >= borrowerDebt) { + changeInDeposit[bucketUsed] += borrowerDebt; + collateral -= Maths.wdiv(borrowerDebt, _priceAt(bucketIndex)); + depositUsed += borrowerDebt; + borrowerDebt = 0; + } + // exchange all collateral with deposit + else { + changeInDeposit[bucketUsed] += maxSettleableDebt; + depositUsed += maxSettleableDebt; + collateral = 0; + borrowerDebt -= maxSettleableDebt; + } + } else collateral = 0; + + bucketDepth -= 1; + } + + // reserves used for settling auction + uint256 reserveChange; + + // if collateral becomes 0 and still debt is left, settle debt by reserves and hpb making buckets bankrupt + if (borrowerDebt != 0 && collateral == 0) { + (uint256 reserves, , , , )= _poolInfo.poolReservesInfo(address(_pool)); + + reserveChange = Maths.min(reserves, borrowerDebt); + borrowerDebt -= reserveChange; + + while (bucketDepth != 0 && borrowerDebt != 0) { + uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + uint256 bucketUsed = bucketIndex - LENDER_MIN_BUCKET_INDEX; + + if (bucketIndex != MAX_FENWICK_INDEX) { + + // debt is greater than bucket deposit + if (borrowerDebt > (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed])) { + borrowerDebt -= (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); + changeInDeposit[bucketUsed] += (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); + depositUsed += (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); + } + // bucket deposit is greater than debt + else { + changeInDeposit[bucketUsed] += borrowerDebt; + depositUsed += borrowerDebt; + borrowerDebt = 0; + } + } + + bucketDepth -= 1; + } + } + + try _pool.settle(borrower_, maxDepth_) { + + // **RE12**: Reserves decrease by amount of reserve used to settle a auction + decreaseInReserves = reserveChange; + + for (uint256 bucket = 0; bucket <= maxDepth_; bucket++) { + _fenwickRemove(changeInDeposit[bucket], bucket + LENDER_MIN_BUCKET_INDEX); + } + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol new file mode 100644 index 000000000..bc82ff0c9 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { BaseHandler } from './BaseHandler.sol'; + +abstract contract UnboundedReservePoolHandler is BaseHandler { + + /*******************************/ + /*** Kicker Helper Functions ***/ + /*******************************/ + + function _startClaimableReserveAuction() internal useTimestamps updateLocalStateAndPoolInterest { + (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); + if (claimableReserves == 0) return; + + try _pool.startClaimableReserveAuction() { + + // **RE11**: Reserves increase by claimableReserves by startClaimableReserveAuction + decreaseInReserves += claimableReserves; + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /******************************/ + /*** Taker Helper Functions ***/ + /******************************/ + + function _takeReserves( + uint256 amount_ + ) internal useTimestamps updateLocalStateAndPoolInterest { + try _pool.takeReserves(amount_) returns (uint256 takenAmount_) { + + decreaseInReserves += takenAmount_; + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol deleted file mode 100644 index 863ece656..000000000 --- a/tests/forge/ERC20Pool/invariants/handlers/BaseHandler.sol +++ /dev/null @@ -1,153 +0,0 @@ - -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; -import '@std/Test.sol'; -import '@std/Vm.sol'; -import "forge-std/console.sol"; - -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; -import { Token } from '../../../utils/Tokens.sol'; -import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { InvariantTest } from '../InvariantTest.sol'; - - -uint256 constant LENDER_MIN_BUCKET_INDEX = 2570; -uint256 constant LENDER_MAX_BUCKET_INDEX = 2590; - -uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; -uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; - -contract BaseHandler is InvariantTest, Test { - - // Tokens - Token internal _quote; - Token internal _collateral; - - // Pool - ERC20Pool internal _pool; - PoolInfoUtils internal _poolInfo; - - // Modifiers - address internal _actor; - uint256 internal _lenderBucketIndex; - uint256 internal _limitIndex; - address[] public actors; - - // Logging - mapping(bytes32 => uint256) public numberOfCalls; - - // Lender tracking - mapping(address => uint256[]) public touchedBuckets; - - // bucket exchange rate invariant check - bool public shouldExchangeRateChange; - - bool public shouldReserveChange; - - // if take is called on auction first time - bool public firstTake; - - // mapping borrower address to first take on auction - mapping(address => bool) internal isFirstTakeOnAuction; - - // amount of reserve increase after first take - uint256 public firstTakeIncreaseInReserve; - - // amount of reserve increase after kicking a loan - uint256 public loanKickIncreaseInReserve; - - constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) { - // Tokens - _quote = Token(quote); - _collateral = Token(collateral); - - // Pool - _pool = ERC20Pool(pool); - _poolInfo = PoolInfoUtils(poolInfo); - - // Actors - actors = _buildActors(numOfActors); - } - - /**************************************************************************************************************************************/ - /*** Helper Functions ***/ - /**************************************************************************************************************************************/ - - modifier useRandomActor(uint256 actorIndex) { - // pre condition - firstTakeIncreaseInReserve = 0; - loanKickIncreaseInReserve = 0; - - vm.stopPrank(); - - address actor = actors[constrictToRange(actorIndex, 0, actors.length - 1)]; - _actor = actor; - vm.startPrank(actor); - _; - vm.stopPrank(); - } - - modifier useRandomLenderBucket(uint256 bucketIndex) { - uint256[] storage lenderBucketIndexes = touchedBuckets[_actor]; - 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); - lenderBucketIndexes.push(_lenderBucketIndex); - } else { - // if actor has touched more than three buckets, reuse one of the touched buckets - _lenderBucketIndex = lenderBucketIndexes[constrictToRange(bucketIndex, 0, lenderBucketIndexes.length - 1)]; - } - _; - } - - function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { - address[] memory actorsAddress = new address[](noOfActors_); - for(uint i = 0; i < noOfActors_; i++) { - address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); - actorsAddress[i] = actor; - - vm.startPrank(actor); - - _quote.mint(actor, 1e45); - _quote.approve(address(_pool), 1e45); - - _collateral.mint(actor, 1e45); - _collateral.approve(address(_pool), 1e45); - - vm.stopPrank(); - } - return actorsAddress; - } - - function getActorsCount() external view returns(uint256) { - return actors.length; - } - - function constrictToRange( - uint256 x, - uint256 min, - uint256 max - ) pure public returns (uint256 result) { - require(max >= min, "MAX_LESS_THAN_MIN"); - - uint256 size = max - min; - - if (size == 0) return min; // Using max would be equivalent as well. - if (max != type(uint256).max) size++; // Make the max inclusive. - - // Ensure max is inclusive in cases where x != 0 and max is at uint max. - if (max == type(uint256).max && x != 0) x--; // Accounted for later. - - if (x < min) x += size * (((min - x) / size) + 1); - - result = min + ((x - min) % size); - - // Account for decrementing x to make max inclusive. - if (max == type(uint256).max && x != 0) result++; - } - -} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol index df4662de1..e335aa854 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol @@ -1,359 +1,327 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: UNLICENSED + pragma solidity 0.8.14; -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; -import "forge-std/console.sol"; -import '@std/Test.sol'; -import '@std/Vm.sol'; +import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; -import { Token } from '../../../utils/Tokens.sol'; -import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; +import 'src/libraries/internal/Maths.sol'; -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX, + BaseHandler +} from '../base/BaseHandler.sol'; +import { UnboundedBasicPoolHandler } from '../base/UnboundedBasicPoolHandler.sol'; /** - * @dev this contract manages multiple lenders + * @dev this contract manages multiple actors * @dev methods in this contract are called in random order - * @dev randomly selects a lender contract to make a txn + * @dev randomly selects an actor contract to make a txn */ -abstract contract UnboundedBasicPoolHandler is BaseHandler { - - /**************************************************************************************************************************************/ - /*** Lender Functions ***/ - /**************************************************************************************************************************************/ - - function addQuoteToken(uint256 amount, uint256 bucketIndex) internal { - numberOfCalls['UBBasicHandler.addQuoteToken']++; - - shouldExchangeRateChange = false; - shouldReserveChange = false; - - // Pre condition - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); +contract BasicPoolHandler is UnboundedBasicPoolHandler { - _pool.addQuoteToken(amount, bucketIndex, block.timestamp + 1 minutes); + constructor( + address pool_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BaseHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { - // Post condition - (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); - require(lpBalanceAfter > lpBalanceBefore, "LP balance should increase"); } - function removeQuoteToken(uint256 amount, uint256 bucketIndex) internal { - numberOfCalls['UBBasicHandler.removeQuoteToken']++; + /*****************************/ + /*** Lender Test Functions ***/ + /*****************************/ - // Pre condition - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); - - if (lpBalanceBefore == 0) { - amount = constrictToRange(amount, 1, 1e36); - addQuoteToken(amount, bucketIndex); - } + function addQuoteToken( + uint256 actorIndex_, + uint256 amountToAdd_, + uint256 bucketIndex_ + ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.addQuoteToken']++; - (lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + // Prepare test phase + uint256 boundedAmount = _preAddQuoteToken(amountToAdd_); - try _pool.removeQuoteToken(amount, bucketIndex) { - // Post condition - (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); - require(lpBalanceAfter < lpBalanceBefore, "LP balance should decrease"); - shouldExchangeRateChange = false; - shouldReserveChange = false; - } - catch (bytes memory _err){ - bytes32 err = keccak256(_err); - require( - err == keccak256(abi.encodeWithSignature("LUPBelowHTP()")) || - err == keccak256(abi.encodeWithSignature("InsufficientLiquidity()")) || - err == keccak256(abi.encodeWithSignature("RemoveDepositLockedByAuctionDebt()")) || - err == keccak256(abi.encodeWithSignature("NoClaim()"))); - } + // Action phase + _addQuoteToken(boundedAmount, _lenderBucketIndex); } - function moveQuoteToken(uint256 amount, uint256 fromIndex, uint256 toIndex) internal { - if(fromIndex == toIndex) return; - - (uint256 lpBalance, ) = _pool.lenderInfo(fromIndex, _actor); + function removeQuoteToken( + uint256 actorIndex_, + uint256 amountToRemove_, + uint256 bucketIndex_ + ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.removeQuoteToken']++; - if (lpBalance == 0) { - addQuoteToken(amount, fromIndex); - } + // Prepare test phase + uint256 boundedAmount = _preRemoveQuoteToken(amountToRemove_); - try _pool.moveQuoteToken(amount, fromIndex, toIndex, block.timestamp + 1 minutes) { - shouldExchangeRateChange = false; - shouldReserveChange = false; - } - catch (bytes memory _err){ - bytes32 err = keccak256(_err); - require( - err == keccak256(abi.encodeWithSignature("LUPBelowHTP()")) || - err == keccak256(abi.encodeWithSignature("InsufficientLiquidity()")) || - err == keccak256(abi.encodeWithSignature("MoveToSameIndex()")) || - err == keccak256(abi.encodeWithSignature("DustAmountNotExceeded()")) || - err == keccak256(abi.encodeWithSignature("InvalidIndex()")) || - err == keccak256(abi.encodeWithSignature("BucketBankruptcyBlock()")) - ); - } + // Action phase + _removeQuoteToken(boundedAmount, _lenderBucketIndex); } - function addCollateral(uint256 amount, uint256 bucketIndex) internal { - numberOfCalls['UBBasicHandler.addCollateral']++; - - shouldExchangeRateChange = false; - shouldReserveChange = false; - - // Pre condition - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + function moveQuoteToken( + uint256 actorIndex_, + uint256 amountToMove_, + uint256 fromIndex_, + uint256 toIndex_ + ) public useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.moveQuoteToken']++; - _pool.addCollateral(amount, bucketIndex, block.timestamp + 1 minutes); + // Prepare test phase + ( + uint256 boundedFromIndex, + uint256 boundedToIndex, + uint256 boundedAmount + ) = _preMoveQuoteToken(amountToMove_, fromIndex_, toIndex_); - // Post condition - (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); - require(lpBalanceAfter > lpBalanceBefore, "LP balance should increase"); + // Action phase + _moveQuoteToken(boundedAmount, boundedFromIndex, boundedToIndex); } - function removeCollateral(uint256 amount, uint256 bucketIndex) internal { - numberOfCalls['UBBasicHandler.removeCollateral']++; + function addCollateral( + uint256 actorIndex_, + uint256 amountToAdd_, + uint256 bucketIndex_ + ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.addCollateral']++; - shouldExchangeRateChange = false; - shouldReserveChange = false; + // Prepare test phase + uint256 boundedAmount = _preAddCollateral(amountToAdd_); - // Pre condition - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + // Action phase + _addCollateral(boundedAmount, _lenderBucketIndex); + } - if(lpBalanceBefore == 0) { - addCollateral(amount, bucketIndex); - } + function removeCollateral( + uint256 actorIndex_, + uint256 amountToRemove_, + uint256 bucketIndex_ + ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.removeCollateral']++; - (lpBalanceBefore, ) = _pool.lenderInfo(bucketIndex, _actor); + // Prepare test phase + uint256 boundedAmount = _preRemoveCollateral(amountToRemove_); - _pool.removeCollateral(amount, bucketIndex); + // Action phase + _removeCollateral(boundedAmount, _lenderBucketIndex); + } - // Post condition - (uint256 lpBalanceAfter, ) = _pool.lenderInfo(bucketIndex, _actor); - require(lpBalanceAfter < lpBalanceBefore, "LP balance should decrease"); + function transferLps( + uint256 fromActorIndex_, + uint256 toActorIndex_, + uint256 lpsToTransfer_, + uint256 bucketIndex_ + ) public useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + // Prepare test phase + (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); + + // Action phase + _increaseLPsAllowance(receiver, _lenderBucketIndex, boundedLps); + _transferLps(_actor, receiver, _lenderBucketIndex); } - /**************************/ - /*** Borrower Functions ***/ - /**************************/ + /*******************************/ + /*** Borrower Test Functions ***/ + /*******************************/ - function pledgeCollateral(uint256 amount) internal { - numberOfCalls['UBBasicHandler.pledgeCollateral']++; + function pledgeCollateral( + uint256 actorIndex_, + uint256 amountToPledge_ + ) public useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.pledgeCollateral']++; - shouldExchangeRateChange = false; - shouldReserveChange = false; + // Prepare test phase + uint256 boundedAmount = _prePledgeCollateral(amountToPledge_); - _pool.drawDebt(_actor, 0, 0, amount); - } + // Action phase + _pledgeCollateral(boundedAmount); - function pullCollateral(uint256 amount) internal { - numberOfCalls['UBBasicHandler.pullCollateral']++; - - try _pool.repayDebt(_actor, 0, amount, _actor, 7388) { - shouldExchangeRateChange = false; - shouldReserveChange = false; - } catch (bytes memory _err){ - bytes32 err = keccak256(_err); - require( - err == keccak256(abi.encodeWithSignature("InsufficientCollateral()")) || - err == keccak256(abi.encodeWithSignature("AuctionActive()")) - ); - } + // Cleanup phase + _auctionSettleStateReset(_actor); } - - function drawDebt(uint256 amount) internal { - numberOfCalls['UBBasicHandler.drawDebt']++; - // Pre Condition - // 1. borrower's debt should exceed minDebt - // 2. pool needs sufficent quote token to draw debt - // 3. drawDebt should not make borrower under collateralized - - // 1. borrower's debt should exceed minDebt - (uint256 debt, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); - if (amount < minDebt) amount = minDebt + 1; + function pullCollateral( + uint256 actorIndex_, + uint256 amountToPull_ + ) public useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.pullCollateral']++; + // Prepare test phase + uint256 boundedAmount = _prePullCollateral(amountToPull_); - // TODO: Need to constrain amount so LUP > HTP - + // Action phase + _pullCollateral(boundedAmount); + } - // 2. pool needs sufficent quote token to draw debt - uint256 poolQuoteBalance = _quote.balanceOf(address(_pool)); + function drawDebt( + uint256 actorIndex_, + uint256 amountToBorrow_ + ) public useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.drawDebt']++; - if (amount > poolQuoteBalance) { - addQuoteToken(amount * 2, LENDER_MAX_BUCKET_INDEX); - } + // Prepare test phase + uint256 boundedAmount = _preDrawDebt(amountToBorrow_); + + // Action phase + _drawDebt(boundedAmount); - // 3. drawing of addition debt will make them under collateralized - uint256 lup = _poolInfo.lup(address(_pool)); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + // Cleanup phase + _auctionSettleStateReset(_actor); + } - if (_collateralization(debt, collateral, lup) < 1) { - repayDebt(debt); - (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - require(debt == 0, "borrower has debt"); - } + function repayDebt( + uint256 actorIndex_, + uint256 amountToRepay_ + ) public useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.repayDebt']++; - (uint256 poolDebt, , ) = _pool.debtInfo(); + // Prepare test phase + uint256 boundedAmount = _preRepayDebt(amountToRepay_); - // find bucket to borrow quote token - uint256 bucket = _pool.depositIndex(amount + poolDebt) - 1; + // Action phase + _repayDebt(boundedAmount); - uint256 price = _poolInfo.indexToPrice(bucket); + // Cleanup phase + _auctionSettleStateReset(_actor); + } - uint256 collateralToPledge = ((amount * 1e18 + price / 2) / price) * 101 / 100; + /*******************************/ + /*** Prepare Tests Functions ***/ + /*******************************/ - try _pool.drawDebt(_actor, amount, 7388, collateralToPledge) { - shouldExchangeRateChange = true; - shouldReserveChange = true; - } - catch (bytes memory _err){ - bytes32 err = keccak256(_err); - require( - err == keccak256(abi.encodeWithSignature("BorrowerUnderCollateralized()")) || - err == keccak256(abi.encodeWithSignature("AuctionActive()")) - ); - } + function _preAddQuoteToken( + uint256 amountToAdd_ + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, _pool.quoteTokenDust(), 1e30); } - function repayDebt(uint256 amountToRepay) internal { - numberOfCalls['UBBasicHandler.repayDebt']++; - - // Pre condition - (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); - if (debt == 0) { - drawDebt(amountToRepay); - } + function _preRemoveQuoteToken( + uint256 amountToRemove_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRemove_, 1, 1e30); - try _pool.repayDebt(_actor, amountToRepay, 0, _actor, 7388) { - shouldExchangeRateChange = true; - shouldReserveChange = true; - } - catch(bytes memory _err) { - bytes32 err = keccak256(_err); - require( - err == keccak256(abi.encodeWithSignature("NoDebt()")) || - err == keccak256(abi.encodeWithSignature("AmountLTMinDebt()")) - ); + // ensure actor has quote tokens to remove + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if (lpBalanceBefore == 0) { + _addQuoteToken(boundedAmount_, _lenderBucketIndex); } } -} - - -/** - * @dev this contract manages multiple lenders - * @dev methods in this contract are called in random order - * @dev randomly selects a lender contract to make a txn - */ -contract BasicPoolHandler is UnboundedBasicPoolHandler { - - constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) BaseHandler(pool, quote, collateral, poolInfo, numOfActors) {} - - /**************************/ - /*** Lender Functions ***/ - /**************************/ - - function addQuoteToken(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { - numberOfCalls['BBasicHandler.addQuoteToken']++; - - uint256 totalSupply = _quote.totalSupply(); - uint256 minDeposit = totalSupply == 0 ? 1 : _quote.balanceOf(address(_actor)) / totalSupply + 1; - amount = constrictToRange(amount, minDeposit, 1e36); - - // Action - super.addQuoteToken(amount, _lenderBucketIndex); + function _preMoveQuoteToken( + uint256 amountToMove_, + 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_, 1, 1e30); + + // ensure actor has LPs to move + (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); + if (lpBalance == 0) _addQuoteToken(boundedAmount_, boundedToIndex_); + + (uint256 lps, ) = _pool.lenderInfo(boundedFromIndex_, _actor); + // restrict amount to move by available deposit inside bucket + uint256 availableDeposit = _poolInfo.lpsToQuoteTokens(address(_pool), lps, boundedFromIndex_); + boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); } - function removeQuoteToken(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { - numberOfCalls['BBasicHandler.removeQuoteToken']++; - - uint256 poolBalance = _quote.balanceOf(address(_pool)); + function _preAddCollateral( + uint256 amountToAdd_ + ) internal pure returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, 1e6, 1e30); + } - if (poolBalance < amount) return; // (not enough quote token to withdraw / quote tokens are borrowed) + function _preRemoveCollateral( + uint256 amountToRemove_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRemove_, 1, 1e30); - // Action - super.removeQuoteToken(amount, _lenderBucketIndex); + // ensure actor has collateral to remove + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if(lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); } - function moveQuoteToken(uint256 actorIndex, uint256 amount, uint256 fromBucketIndex, uint256 toBucketIndex) public useRandomActor(actorIndex) { - numberOfCalls['BBasicHandler.moveQuoteToken']++; + function _preTransferLps( + uint256 toActorIndex_, + uint256 lpsToTransfer_ + ) internal returns (address receiver_, uint256 boundedLps_) { + // ensure actor has LPs to transfer + (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if(senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); - fromBucketIndex = constrictToRange(fromBucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - toBucketIndex = constrictToRange(toBucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + boundedLps_ = constrictToRange(lpsToTransfer_, 1, senderLpBalance); - amount = constrictToRange(amount, 1, 1e36); - - super.moveQuoteToken(amount, fromBucketIndex, toBucketIndex); + receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; } - function addCollateral(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { - numberOfCalls['BBasicHandler.addCollateral']++; - - amount = constrictToRange(amount, 1, 1e36); - - // Action - super.addCollateral(amount, _lenderBucketIndex); + function _prePledgeCollateral( + uint256 amountToPledge_ + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPledge_, _pool.collateralScale(), 1e30); } - function removeCollateral(uint256 actorIndex, uint256 amount, uint256 bucketIndex) public useRandomActor(actorIndex) useRandomLenderBucket(bucketIndex) { - numberOfCalls['BBasicHandler.removeCollateral']++; - - (uint256 lpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - ( , uint256 bucketCollateral, , , ) = _pool.bucketInfo(_lenderBucketIndex); - - if (lpBalance == 0 || bucketCollateral == 0) return; // no value in bucket - - amount = constrictToRange(amount, 1, 1e36); - - // Action - super.removeCollateral(amount, _lenderBucketIndex); + function _prePullCollateral( + uint256 amountToPull_ + ) internal pure returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPull_, 1, 1e30); } + function _preDrawDebt( + uint256 amountToBorrow_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToBorrow_, 1e6, 1e30); - /**************************/ - /*** Borrower Functions ***/ - /**************************/ + // Pre Condition + // 1. borrower's debt should exceed minDebt + // 2. pool needs sufficent quote token to draw debt + // 3. drawDebt should not make borrower under collateralized - function pledgeCollateral(uint256 actorIndex, uint256 amountToPledge) public useRandomActor(actorIndex) { - numberOfCalls['BBasicHandler.pledgeCollateral']++; + // 1. borrower's debt should exceed minDebt + (uint256 debt, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); - amountToPledge = constrictToRange(amountToPledge, 1, 1e36); + if (boundedAmount_ < minDebt) boundedAmount_ = minDebt + 1; - // Action - super.pledgeCollateral(amountToPledge); - } + // TODO: Need to constrain amount so LUP > HTP - function pullCollateral(uint256 actorIndex, uint256 amountToPull) public useRandomActor(actorIndex) { - numberOfCalls['BBasicHandler.pullCollateral']++; + // 2. pool needs sufficent quote token to draw debt + uint256 poolQuoteBalance = _quote.balanceOf(address(_pool)); - amountToPull = constrictToRange(amountToPull, 1, 1e36); + if (boundedAmount_ > poolQuoteBalance) { + _addQuoteToken(boundedAmount_ * 2, LENDER_MAX_BUCKET_INDEX); + } - // Action - super.pullCollateral(amountToPull); - } + // 3. drawing of addition debt will make them under collateralized + uint256 lup = _poolInfo.lup(address(_pool)); + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - function drawDebt(uint256 actorIndex, uint256 amountToBorrow) public useRandomActor(actorIndex) { - numberOfCalls['BBasicHandler.drawDebt']++; + if (_collateralization(debt, collateral, lup) < 1) { + _repayDebt(debt); - amountToBorrow = constrictToRange(amountToBorrow, 1, 1e36); - - // Action - super.drawDebt(amountToBorrow); + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); - // skip time to make borrower undercollateralized - vm.warp(block.timestamp + 200 days); + require(debt == 0, "borrower has debt"); + } } - function repayDebt(uint256 actorIndex, uint256 amountToRepay) public useRandomActor(actorIndex) { - numberOfCalls['BBasicHandler.repayDebt']++; - - amountToRepay = constrictToRange(amountToRepay, 1, 1e36); + function _preRepayDebt( + uint256 amountToRepay_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRepay_, _pool.quoteTokenDust(), 1e30); - // Action - super.repayDebt(amountToRepay); + // ensure actor has debt to repay + (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); + if (debt == 0) { + boundedAmount_ = _preDrawDebt(boundedAmount_); + _drawDebt(boundedAmount_); + } } } diff --git a/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol deleted file mode 100644 index 092c21f7e..000000000 --- a/tests/forge/ERC20Pool/invariants/handlers/IBaseHandler.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -interface IBaseHandler { - - function getActorsCount() external view returns(uint256); - - function actors(uint256) external view returns(address); - - function numberOfCalls(bytes32) external view returns(uint256); - - function shouldExchangeRateChange() external view returns(bool); - - function shouldReserveChange() external view returns(bool); - - function firstTake() external view returns(bool); - - function firstTakeIncreaseInReserve() external view returns(uint256); - - function loanKickIncreaseInReserve() external view returns(uint256); -} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol index 47fc9b43b..95edfd0ee 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol @@ -1,149 +1,167 @@ - // SPDX-License-Identifier: UNLICENSED + pragma solidity 0.8.14; -import '@std/Vm.sol'; +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX, + BaseHandler +} from '../base/BaseHandler.sol'; +import { UnboundedLiquidationPoolHandler } from '../base/UnboundedLiquidationPoolHandler.sol'; import { BasicPoolHandler } from './BasicPoolHandler.sol'; -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; -import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; -import { Maths } from 'src/libraries/internal/Maths.sol'; -abstract contract UnBoundedLiquidationPoolHandler is BaseHandler { - function kickAuction(address borrower) internal { - numberOfCalls['UBLiquidationHandler.kickAuction']++; +contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHandler { - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + constructor( + address pool_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BasicPoolHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { - try _pool.kick(borrower, MAX_FENWICK_INDEX) { - shouldExchangeRateChange = true; - shouldReserveChange = true; - loanKickIncreaseInReserve = Maths.wmul(borrowerDebt, 0.25 * 1e18); - } - catch { - } } - function takeAuction(address borrower, uint256 amount, address taker) internal { - numberOfCalls['UBLiquidationHandler.takeAuction']++; - - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); - - try _pool.take(borrower, amount, taker, bytes("")) { - shouldExchangeRateChange = true; - shouldReserveChange = true; - - if(!isFirstTakeOnAuction[borrower]) { - firstTakeIncreaseInReserve = Maths.wmul(borrowerDebt, 0.07 * 1e18); - firstTake = true; - isFirstTakeOnAuction[borrower] = true; - } - else { - isFirstTakeOnAuction[borrower] = false; - firstTake = false; - } - } - catch { - } - } + /*****************************/ + /*** Kicker Test Functions ***/ + /*****************************/ - function bucketTake(address borrower, bool depositTake, uint256 bucketIndex) internal { - numberOfCalls['UBLiquidationHandler.bucketTake']++; - - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower); + function kickAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 kickerIndex_ + ) external useTimestamps { + _kickAuction(borrowerIndex_, amount_, kickerIndex_); + } - try _pool.bucketTake(borrower, depositTake, bucketIndex) { - shouldExchangeRateChange = true; - shouldReserveChange = true; + function kickWithDeposit( + uint256 kickerIndex_, + uint256 bucketIndex_ + ) external useRandomActor(kickerIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + _kickWithDeposit(_lenderBucketIndex); + } - if(!firstTake) { - firstTakeIncreaseInReserve = Maths.wmul(borrowerDebt, 0.07 * 1e18); - firstTake = true; - } - else { - firstTake = false; - } - } - catch { - } + function withdrawBonds( + uint256 kickerIndex_, + uint256 maxAmount_ + ) external useRandomActor(kickerIndex_) useTimestamps { + _withdrawBonds(_actor, maxAmount_); } -} -contract LiquidationPoolHandler is UnBoundedLiquidationPoolHandler, BasicPoolHandler { + /****************************/ + /*** Taker Test Functions ***/ + /****************************/ - constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) BasicPoolHandler(pool, quote, collateral, poolInfo, numOfActors) {} + function takeAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 actorIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BLiquidationHandler.takeAuction']++; - function _kickAuction(uint256 borrowerIndex, uint256 amount, uint256 kickerIndex) internal useRandomActor(kickerIndex) { - numberOfCalls['BLiquidationHandler.kickAuction']++; + amount_ = constrictToRange(amount_, 1, 1e30); - shouldExchangeRateChange = true; + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); - address borrower = actors[borrowerIndex]; - address kicker = _actor; - amount = constrictToRange(amount, 1, 1e36); + address borrower = actors[borrowerIndex_]; + address taker = _actor; ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - if (kickTime == 0) { - (uint256 debt, , ) = _pool.borrowerInfo(borrower); - if (debt == 0) { - changePrank(borrower); - _actor = borrower; - super.drawDebt(amount); - } - changePrank(kicker); - _actor = kicker; - super.kickAuction(borrower); - } - - // skip some time for more interest - vm.warp(block.timestamp + 2 hours); - } + if (kickTime == 0) _kickAuction(borrowerIndex_, amount_ * 100, actorIndex_); - function kickAuction(uint256 borrowerIndex, uint256 amount, uint256 kickerIndex) external { - _kickAuction(borrowerIndex, amount, kickerIndex); + changePrank(taker); + _takeAuction(borrower, amount_, taker); } - function takeAuction(uint256 borrowerIndex, uint256 amount, uint256 actorIndex) external useRandomActor(actorIndex){ - numberOfCalls['BLiquidationHandler.takeAuction']++; - - amount = constrictToRange(amount, 1, 1e36); - - shouldExchangeRateChange = true; + function bucketTake( + uint256 borrowerIndex_, + uint256 bucketIndex_, + bool depositTake_, + uint256 takerIndex_ + ) external useRandomActor(takerIndex_) useTimestamps { + numberOfCalls['BLiquidationHandler.bucketTake']++; - borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); + bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - address borrower = actors[borrowerIndex]; + address borrower = actors[borrowerIndex_]; address taker = _actor; ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - if (kickTime == 0) { - _kickAuction(borrowerIndex, amount * 100, actorIndex); - } + if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); + changePrank(taker); - super.takeAuction(borrower, amount, taker); + _bucketTake(taker, borrower, depositTake_, bucketIndex_); } - function bucketTake(uint256 borrowerIndex, uint256 bucketIndex, bool depositTake, uint256 takerIndex) external useRandomActor(takerIndex) { - numberOfCalls['BLiquidationHandler.bucketTake']++; + /******************************/ + /*** Settler Test Functions ***/ + /******************************/ - shouldExchangeRateChange = true; + function settleAuction( + uint256 actorIndex_, + uint256 borrowerIndex_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); + bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - borrowerIndex = constrictToRange(borrowerIndex, 0, actors.length - 1); + address borrower = actors[borrowerIndex_]; + uint256 maxDepth = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX; - bucketIndex = constrictToRange(bucketIndex, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + address actor = _actor; - address borrower = actors[borrowerIndex]; - address taker = _actor; + ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); + + changePrank(actor); + // skip time to make auction clearable + vm.warp(block.timestamp + 73 hours); + _settleAuction(borrower, maxDepth); + + _auctionSettleStateReset(borrower); + } + + /************************/ + /*** Helper Functions ***/ + /************************/ + + function _kickAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 kickerIndex_ + ) internal useRandomActor(kickerIndex_) { + numberOfCalls['BLiquidationHandler.kickAuction']++; + + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); + address borrower = actors[borrowerIndex_]; + address kicker = _actor; + amount_ = constrictToRange(amount_, 1, 1e30); ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); if (kickTime == 0) { - _kickAuction(borrowerIndex, 1e24, bucketIndex); + (uint256 debt, , ) = _pool.borrowerInfo(borrower); + + if (debt == 0) { + changePrank(borrower); + _actor = borrower; + uint256 drawDebtAmount = _preDrawDebt(amount_); + _drawDebt(drawDebtAmount); + } + + changePrank(kicker); + _actor = kicker; + _kickAuction(borrower); } - changePrank(taker); - super.bucketTake(borrower, depositTake, bucketIndex); - } + + // skip some time for more interest + vm.warp(block.timestamp + 2 hours); + } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol index 1e592a2e3..13795f544 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol @@ -1,48 +1,58 @@ - // SPDX-License-Identifier: UNLICENSED + pragma solidity 0.8.14; -import '@std/Vm.sol'; +import { UnboundedReservePoolHandler } from '../base/UnboundedReservePoolHandler.sol'; import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; -import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BaseHandler } from './BaseHandler.sol'; -import { Auctions } from 'src/libraries/external/Auctions.sol'; - -abstract contract UnBoundedReservePoolHandler is BaseHandler { - function startClaimableReserveAuction() internal { - (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); - if(claimableReserves == 0) return; - try _pool.startClaimableReserveAuction(){ - shouldReserveChange = true; - } catch { - } - } - function takeReserves(uint256 amount) internal { - try _pool.takeReserves(amount){ - shouldReserveChange = true; - } catch { - } +contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandler { + + constructor( + address pool_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) LiquidationPoolHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + } -} -contract ReservePoolHandler is UnBoundedReservePoolHandler, LiquidationPoolHandler { + /*******************************/ + /*** Reserves Test Functions ***/ + /*******************************/ - constructor(address pool, address quote, address collateral, address poolInfo, uint256 numOfActors) LiquidationPoolHandler(pool, quote, collateral, poolInfo, numOfActors) {} + function startClaimableReserveAuction( + uint256 actorIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + // Action phase + _startClaimableReserveAuction(); + } + + function takeReserves( + uint256 actorIndex_, + uint256 amountToTake_ + ) external useRandomActor(actorIndex_) useTimestamps { + // Prepare test phase + uint256 boundedAmount = _preTakeReserves(amountToTake_); - function startClaimableReserveAuction(uint256 actorIndex) external useRandomActor(actorIndex) { - super.startClaimableReserveAuction(); + // Action phase + _takeReserves(boundedAmount); } - function takeReserves(uint256 actorIndex, uint256 amount) external useRandomActor(actorIndex) { + /*******************************/ + /*** Prepare Tests Functions ***/ + /*******************************/ + + function _preTakeReserves( + uint256 amountToTake_ + ) internal returns (uint256 boundedAmount_) { (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); + if (claimableReservesRemaining == 0) _startClaimableReserveAuction(); - if(claimableReservesRemaining == 0) { - super.startClaimableReserveAuction(); - } (, , claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - - amount = constrictToRange(amount, 0, claimableReservesRemaining); - super.takeReserves(amount); + boundedAmount_ = constrictToRange(amountToTake_, 0, claimableReservesRemaining); } + } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol b/tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol new file mode 100644 index 000000000..ad609ab20 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +interface IBaseHandler { + + function getActorsCount() external view returns(uint256); + function actors(uint256) external view returns(address); + + function numberOfCalls(bytes32) external view returns(uint256); + + function fenwickSumAtIndex(uint256) external view returns(uint256); + function fenwickTreeSum() external view returns(uint256); + function fenwickSumTillIndex(uint256) external view returns(uint256); + + function exchangeRateShouldNotChange(uint256) external view returns(bool); + function previousExchangeRate(uint256) external view returns(uint256); + + function isKickerRewarded() external view returns(bool); + function kickerBondChange() external view returns(uint256); + + function previousReserves() external view returns(uint256); + function increaseInReserves() external view returns(uint256); + function decreaseInReserves() external view returns(uint256); + + function firstTake() external view returns(bool); + function alreadyTaken(address) external view returns(bool); + + function lenderDepositTime(address lender, uint256 bucketIndex) external view returns(uint256); +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol b/tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol new file mode 100644 index 000000000..b71220442 --- /dev/null +++ b/tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +interface ITestBase { + + function currentTimestamp() external view returns (uint256 currentTimestamp); + + function setCurrentTimestamp(uint256 currentTimestamp) external; + +} diff --git a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol index 57e0a9819..0b1853471 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol @@ -4,118 +4,260 @@ pragma solidity 0.8.14; import { BasicInvariants } from "../invariants/BasicInvariants.t.sol"; -import '@std/console.sol'; - contract RegressionTestBasic is BasicInvariants { function setUp() public override { super.setUp(); } - function test_regression_invariantUnderflow_1() external { + function test_regression_Underflow_1() external { _basicPoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000); + // check invariants hold true - invariant_Lps_B1(); invariant_quoteTokenBalance_QT1(); } - function test_exchange_rate_bug_simulation() external { - // Action sequence - // 1. addQuoteToken(6879, 2570) - // 2. addCollateral(3642907759282013932739218713, 2570) - // 3. removeCollateral(296695924278944779257290397234298756, 2570) - - uint256 previousExchangeRate = 1e18; + function test_regression_exchange_rate_1() external { _basicPoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809); - ( , uint256 quote, uint256 collateral, uint256 lps, , uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After addQuoteToken(6879, 2570)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - previousExchangeRate = exchangeRate; _basicPoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592); - ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After addCollateral(3642907759282013932739218713, 2570)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - previousExchangeRate = exchangeRate; _basicPoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636); - ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After removeCollateral(296695924278944779257290397234298756, 2570)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - } - - function test_exchange_rate_bug2() external { - uint256 previousExchangeRate = 1e18; - _basicPoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); - - ( , uint256 quote, uint256 collateral, uint256 lps, , uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - previousExchangeRate = exchangeRate; - _basicPoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); - ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - previousExchangeRate = exchangeRate; + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_2() external { + _basicPoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); + _basicPoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); _basicPoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - - ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - // require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); - previousExchangeRate = exchangeRate; - _basicPoolHandler.removeCollateral(1, 1e36, 2570); _basicPoolHandler.removeQuoteToken(1, 1e36, 2570); - _basicPoolHandler.removeCollateral(2, 1e36, 2570); _basicPoolHandler.removeQuoteToken(2, 1e36, 2570); - ( , quote, collateral, lps, , exchangeRate) = _poolInfo.bucketInfo(address(_pool), 2570); - console.log("After removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933)"); - console.log("============"); - console.log("Quote Tokens -->", quote); - console.log("Collateral Tokens -->", collateral); - console.log("Lps -->", lps); - console.log("Exchange Rate-->", exchangeRate); - console.log("============"); - require(previousExchangeRate == exchangeRate, "Incorrect exchange rate"); + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test will fail when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_3() external { + _basicPoolHandler.addQuoteToken(2842, 304, 2468594405605444095992); + _basicPoolHandler.addCollateral(0, 1, 3); + _basicPoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _basicPoolHandler.removeCollateral(0, 1, 3); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_4() external { + _basicPoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 587135207579305083672251579076072787077); + _basicPoolHandler.removeCollateral(712291886391993882782748602346033231793324080118979183300958, 673221151277569661050873992210938589, 999999997387885196930781163353866909746906615); + _basicPoolHandler.removeCollateral(4434852123445331038838, 92373980881732279172264, 16357203); + _basicPoolHandler.addQuoteToken(6532756, 16338, 2488340072929715905208495398161339232954907500634); + _basicPoolHandler.removeCollateral(934473801621702106582064701468475360, 999999998588451849650292641565069384488310108, 2726105246641027837873401505120164058057757115396); + _basicPoolHandler.addQuoteToken(0, 3272, 688437777000000000); + _basicPoolHandler.removeQuoteToken(36653992905059663682442427, 3272, 688437777000000000); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_5() external { + _basicPoolHandler.drawDebt(1156, 1686); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicPoolHandler.addQuoteToken(711, 2161, 2012); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570] + function test_regression_exchange_rate_6() external { + _basicPoolHandler.addCollateral(999999999000000000000000081002632733724231666, 999999999243662968633890481597751057821356823, 1827379824097500721086759239664926559); + _basicPoolHandler.addQuoteToken(108018811574020559, 3, 617501271956497833026154369680502407518122199901237699791086943); + _basicPoolHandler.addCollateral(95036573736725249741129171676163161793295193492729984020, 5009341426566798172861627799, 2); + _basicPoolHandler.removeCollateral(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 5814100241); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + // Fixed with commit -> https://github.com/ajna-finance/contracts/pull/613/commits/f106f0f7c96c1662325bdb5151fd745544e6dce0 + function test_regression_exchange_rate_7() external { + _basicPoolHandler.addCollateral(999999999249784004703856120761629301735818638, 15200, 2324618456838396048595845067026807532884041462750983926777912015561); + _basicPoolHandler.addQuoteToken(0, 2, 60971449684543878); + _basicPoolHandler.addCollateral(0, 648001392760875820320327007315181208349883976901103343226563974622543668416, 38134304133913510899173609232567613); + _basicPoolHandler.removeCollateral(0, 1290407354289435191451647900348688457414638662069174249777953, 125945131546441554612275631955778759442752893948134984981883798); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_8() external { + _basicPoolHandler.drawDebt(0, 10430); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicPoolHandler.addCollateral(86808428701435509359888008280539191473421, 35, 89260656586096811497271673595050); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_exchange_rate_9() external { + _basicPoolHandler.addQuoteToken(179828875014111774829603408358905079754763388655646874, 39999923045226513122629818514849844245682430, 12649859691422584279364490330583846883); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicPoolHandler.addCollateral(472, 2100, 11836); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicPoolHandler.pledgeCollateral(7289, 8216); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_fenwick_deposit_1() external { + _basicPoolHandler.addQuoteToken(60321923115154876306287876901335341390357684483818363750, 2, 0); + _basicPoolHandler.repayDebt(58055409653178, 2); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_fenwick_deposit_2() external { + _basicPoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2146593659305556796718319988088528090847459411703413796483450011160); + _basicPoolHandler.addCollateral(16885296866566559818671993560820380984757301691657405859955072474117, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 7764878663795446754367); + _basicPoolHandler.removeCollateral(999999997000000000000000000000000000000756426, 7366, 4723); + _basicPoolHandler.addQuoteToken(5673, 8294, 11316); + _basicPoolHandler.moveQuoteToken(919997327910338711763724656061931477, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3933155006830995444792575696); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_fenwick_deposit_3() external { + _basicPoolHandler.pullCollateral(64217420783566726909348297066823202824683000164554083, 651944294303386510182040138076901697073); + _basicPoolHandler.removeQuoteToken(172614182, 2999, 725); + _basicPoolHandler.addQuoteToken(52646814442098488638488433580148374391481084017027388775686120188766352301, 5021, 16410); + _basicPoolHandler.moveQuoteToken(2, 1, 3, 11769823729834119405789456482320067049929344685247053661486); + _basicPoolHandler.moveQuoteToken(1, 2833727997543655292227288672285470, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 7962018528962356191057551420322350); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_4() external { + _basicPoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 2, 1267); + _basicPoolHandler.pledgeCollateral(1700127358962530, 0); + _basicPoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_5() external { + _basicPoolHandler.repayDebt(281, 1502); + _basicPoolHandler.addCollateral(5529, 1090, 5431); + _basicPoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_6() external { + _basicPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _basicPoolHandler.addQuoteToken(1000000000000000, 19319, 308); + _basicPoolHandler.pullCollateral(4218, 4175); + invariant_fenwick_depositAtIndex_F1(); } + function test_regression_fenwick_prefixSum_1() external { + _basicPoolHandler.addQuoteToken(5851, 999999999999999999999999999999000087, 1938); + _basicPoolHandler.addCollateral(135454721201807374404103595951250949, 172411742705067521609848985260337891060745418778973, 3); + _basicPoolHandler.pledgeCollateral(2, 185978674898652363737734333012844452989790885966093618883814734917759475); + _basicPoolHandler.moveQuoteToken(976453319, 2825105681459470134743617749102858205411027017903767825282483319, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 145056082857394229503325854914710239303685607721150607568547620026); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_index_1() external { + _basicPoolHandler.addQuoteToken(3056, 915, 1594); + _basicPoolHandler.pullCollateral(274694202801760577094218807, 1); + _basicPoolHandler.addQuoteToken(1088, 3407, 3555); + _basicPoolHandler.addCollateral(1557, 13472, 15303); + _basicPoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 40692552539277917058910464963); + _basicPoolHandler.pullCollateral(1131485716992204156645660898919702, 30971207810832254868222941038507448); + _basicPoolHandler.removeCollateral(27428712668923640148402320299830959263828759458932482391338247903954077260349, 1136, 3944); + _basicPoolHandler.moveQuoteToken(9746204317995874651524496302383356801834068305156642323380998069579800880, 1723109236200550802774859945265636287, 3213180193920898024510373220802133410941904907229061207617048152428481, 0); + + invariant_fenwick_bucket_index_F3(); + } + + function test_regression_transferLps_1() external { + _basicPoolHandler.transferLps(0, 1, 200, 2570); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_transferLps_2() external { + _basicPoolHandler.transferLps(37233021465377552730514154972012012669272, 45957263314208417069590941186697869465410494677646946058359554, 405, 89727160292150007024940); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_transferLps_3() external { + _basicPoolHandler.transferLps(1795, 6198, 3110, 11449); + + invariant_Bucket_deposit_time_B5_B6_B7(); + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_pull_collateral_when_encumbered_greater_than_pledged() external { + _basicPoolHandler.drawDebt(1535776046383997344779595, 5191646246012456798576386242824793107669233); + _basicPoolHandler.transferLps(17293, 19210, 227780, 999999999999999999999999999999999999999999997); + _basicPoolHandler.removeQuoteToken(0, 0, 2); + _basicPoolHandler.pullCollateral(115, 149220); + } + + function test_regression_incorrect_zero_deposit_buckets_1() external { + _basicPoolHandler.repayDebt(15119, 6786); + _basicPoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1578322581132549441186648538841, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_fenwick_index_2() external { + uint256 depositAt2570 = 570036521745120847917211; + uint256 depositAt2571 = _basicPoolHandler.constrictToRange(2578324552477056269186646552413, 1, 1000000000000000000000000000000); + uint256 depositAt2572 = 1212; + _basicPoolHandler.addQuoteToken(1, depositAt2570, 2570); + _basicPoolHandler.addQuoteToken(1, depositAt2571, 2571); + _basicPoolHandler.addQuoteToken(1, depositAt2572, 2572); + assertEq(_pool.depositIndex(depositAt2570), 2570); + assertEq(_pool.depositIndex(depositAt2570 + depositAt2571), 2571); + assertEq(_pool.depositIndex(depositAt2570 + depositAt2571 + depositAt2572), 2572); + } + + function test_regression_collateralBalance_CT1_CT7() external { + _basicPoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _basicPoolHandler.repayDebt(2712912126128356234217, 251720382485531952743041849848); + _basicPoolHandler.addQuoteToken(253022590763482364356576159, 999999999999999273028438503236995092261608400, 712808213364422679443324012750); + _basicPoolHandler.removeQuoteToken(121890555084215923472733925382, 0, 3); + + invariant_collateralBalance_CT1_CT7(); + } + + // TODO: poolBalance + poolDebt >= _pool.depositSize fails by 1 unit of WAD (1.000115588871659711 >= 1.000115588871659712) + function test_regression_invariant_quoteTokenBalance_QT1() external { + _basicPoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098); + _basicPoolHandler.repayDebt(1, 2); + _basicPoolHandler.removeCollateral(200953640940463935290718680397023889633667961549, 2481, 3); + _basicPoolHandler.moveQuoteToken(695230664226651211376892782958299806602599384639648126900062519785408512, 1000115588871659705, 22812, 1955101796782211288928817909562); + _basicPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639932, 103); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_fenwick_deposit_8() external { + _basicPoolHandler.drawDebt(226719918559509764892175185709, 228676957600917178383525685311331); + + invariant_fenwick_depositAtIndex_F1(); + } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 29c6b6076..35fd0f050 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -2,47 +2,42 @@ pragma solidity 0.8.14; -import { LiquidationInvariant } from "../invariants/LiquidationInvariant.t.sol"; +import { LiquidationInvariants } from "../invariants/LiquidationInvariants.t.sol"; -import '@std/console.sol'; - -contract RegressionTestLiquidation is LiquidationInvariant { +contract RegressionTestLiquidation is LiquidationInvariants { function setUp() public override { super.setUp(); - } function test_regression_quote_token() external { _liquidationPoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - // check invariants hold true invariant_quoteTokenBalance_QT1(); } - function test_arithmetic_overflow() external { + function test_regression_arithmetic_overflow() external { _liquidationPoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903); _liquidationPoolHandler.addQuoteToken(3487, 871, 1654); - // check invariants hold true invariant_quoteTokenBalance_QT1(); } - function test_bucket_take_lps_bug() public { + function test_regression_bucket_take_lps() external { _liquidationPoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); _liquidationPoolHandler.addQuoteToken(1, 20033186019073, 1); _liquidationPoolHandler.bucketTake(0, 0, false, 2876997751); - invariant_Lps_B1(); + invariant_Lps_B1_B4(); } - function test_interest_rate_bug() public { + function test_regression_interest_rate() external { _liquidationPoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769); invariant_interest_rate_I1(); } - function test_incorrect_no_of_borrowers() public { + function test_regression_incorrect_no_of_borrowers() external { _liquidationPoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513); _liquidationPoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271); _liquidationPoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294); @@ -67,4 +62,35 @@ contract RegressionTestLiquidation is LiquidationInvariant { invariant_auctions_A3_A4(); } + + // test was failing due to deposit time update even if kicker lp reward is 0. + // resolved with PR: https://github.com/ajna-finance/contracts/pull/674 + function test_regression_bucket_deposit_time() external { + _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 2079356830967144967054363629631641573895835179323954988585146991431, 233005625580787863707944); + _liquidationPoolHandler.bucketTake(21616, 1047473235778002354, false, 1062098588952039043823357); + _liquidationPoolHandler.bucketTake(1673497622984405133414814181152, 94526073941076989987362055170246, false, 1462); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_transfer_taker_lps_bucket_deposit_time() external { + _liquidationPoolHandler.settleAuction(3637866246331061119113494215, 0, 6163485280468362485998190762304829820899757798629605592174295845105660515); + _liquidationPoolHandler.transferLps(1610, 1000000000018496758270674070884, 168395863093969200027183125335, 2799494920515362640996160058); + _liquidationPoolHandler.bucketTake(0, 10619296457595008969473693936299982020664977642271808785891719078511288, true, 1681500683437506364426133778273769573223975355182845498494263153646356302); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_invariant_fenwick_depositAtIndex_F1() external { + _liquidationPoolHandler.moveQuoteToken(4058, 2725046678043704335543997294802562, 16226066, 4284); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_depositKick() external { + _liquidationPoolHandler.repayDebt(13418, 1160); + _liquidationPoolHandler.kickWithDeposit(143703836638834364678, 470133688850921941603); + + invariant_fenwick_depositAtIndex_F1(); + } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index 53ed71d77..f014b70f7 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -2,9 +2,7 @@ pragma solidity 0.8.14; -import { ReserveInvariants } from "../invariants/ReserveInvariants.t.sol"; - -import '@std/console.sol'; +import { ReserveInvariants } from "../invariants/ReserveInvariants.t.sol"; contract RegressionTestReserve is ReserveInvariants { @@ -12,39 +10,155 @@ contract RegressionTestReserve is ReserveInvariants { super.setUp(); } - function _test_reserve_1() external { - (uint256 reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Initial Reserve -->", reserve); - + function test_regression_reserve_1() external { _reservePoolHandler.kickAuction(3833, 15167, 15812); + _reservePoolHandler.removeQuoteToken(3841, 5339, 3672); - (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Reserve after kick --->", reserve); - _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + // test was failing due to error in local fenwickAccureInterest method + function test_regression_reserve_2() external { + _reservePoolHandler.bucketTake(19730, 10740, false, 15745); - _reservePoolHandler.removeQuoteToken(3841, 5339, 3672); + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + _reservePoolHandler.addCollateral(14982, 18415, 2079); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_3() external { + _reservePoolHandler.repayDebt(404759030515771436961484, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + _reservePoolHandler.removeQuoteToken(1, 48462143332689486187207611220503504, 3016379223696706064676286307759709760607418884028758142005949880337746); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_4() external { + _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_5() external { + _reservePoolHandler.addQuoteToken(16175599156223678030374425049208907710, 7790130564765920091364739351727, 3); + _reservePoolHandler.takeReserves(5189, 15843); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, false, 32141946615464); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_6() external { + _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.removeQuoteToken(3, 76598848420614737624527356706527, 0); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_7() external { + _reservePoolHandler.addQuoteToken(3457, 669447918254181815570046125126321316, 999999999837564549363536522206516458546098684); + _reservePoolHandler.takeReserves(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.takeAuction(1340780, 50855928079819281347583122859151761721081932621621575848930363902528865907253, 1955849966715168052511460257792969975295827229642304100359774335664); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_8() external { + _reservePoolHandler.addQuoteToken(0, 16517235514828622102184417372650002297563613398679232953, 3); + _reservePoolHandler.takeReserves(1, 824651); + _reservePoolHandler.kickAuction(353274873012743605831170677893, 0, 297442424590491337560428021161844134441441035247561757); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } - (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Reserve after removeQuoteToken --->", reserve); - _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + function test_regression_reserve_9() external { + _reservePoolHandler.addQuoteToken(8167, 13910, 6572); + _reservePoolHandler.removeQuoteToken(450224344766393467188006446127940623592343232978, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3); + _reservePoolHandler.addQuoteToken(1338758958425242459263005073411197235389119160018038412507867175716953081924, 0, 3); + _reservePoolHandler.removeQuoteToken(13684, 7152374202712184607581797, 37874588407625287908455929174); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_10() external { + _reservePoolHandler.drawDebt(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.takeAuction(57952503477150200455919212210202824, 59396836510148646246120666527, 253313800651499290076173012431766464943796699909751081638812681630219); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_11() external { + _reservePoolHandler.drawDebt(121976811044722028186086534321386307, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reservePoolHandler.removeQuoteToken(22099, 75368688232971077945057, 1089607217901154741924938851595); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_12() external { + _reservePoolHandler.drawDebt(7201, 13634); + _reservePoolHandler.startClaimableReserveAuction(4584); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_13() external { + _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 540213858694280098848655811354140073005); + _reservePoolHandler.takeAuction(0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 16744276840254269931315148200783781329474); + _reservePoolHandler.settleAuction(1052055081946638635908683442568, 2, 3); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_14() external { + _reservePoolHandler.settleAuction(437841947740231831335707997666789355668988087441752683415964733126988332082, 147808166723925302409649247274, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_fenwick_deposits_1() external { + _reservePoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_incorrect_zero_deposit_buckets_1() external { + _reservePoolHandler.addQuoteToken(26716, 792071517553389595371632366275, 1999999999999999449873579333598595527312558403); + + invariant_fenwick_prefixSumIndex_F4(); + _reservePoolHandler.takeAuction(3383098792294835418337099631478603398072656037191240558595006969488860, 23280466048203500609787983860018797249195596837096487660362732305, 999999999999999999999999012359); + + invariant_fenwick_prefixSumIndex_F4(); } - function _test_reserve_2() external { - (uint256 reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Initial Reserve -->", reserve); + function test_regression_incorrect_zero_deposit_buckets_2() external { + _reservePoolHandler.addCollateral(9093188371345232280759885514931620, 736370925, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.removeQuoteToken(2, 743823342719479363729966668312423206558602, 6003791801508574660825548152233943700089469549364090309); + _reservePoolHandler.removeQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); + _reservePoolHandler.addQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); + _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.addQuoteToken(22558, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 26798251134); + _reservePoolHandler.moveQuoteToken(699684583201376669946795465695023954383, 871337618071093223322748209250657757655686665685488924893819949988, 6856667370119202181100844692321254723509125063768335, 2); - _reservePoolHandler.bucketTake(19730, 10740, false, 15745); + invariant_fenwick_prefixSumIndex_F4(); - (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Reserve after bucketTake --->", reserve); - _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + } + function test_regression_incorrect_bond() external { + _reservePoolHandler.settleAuction(18129, 6125, 756); - _reservePoolHandler.addCollateral(14982, 18415, 2079); + invariant_bond_A2(); + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_reserves_fenwick_depositAtIndex_F1() external { + _reservePoolHandler.kickAuction(14062, 13380, 20332); + _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 250713412144308447525906089113510093407014793436690623); + _reservePoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - (reserve, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); - console.log("Reserve after addCollateral --->", reserve); - _invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9(); + invariant_fenwick_depositAtIndex_F1(); } } \ No newline at end of file diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/PositionManager.t.sol index 0481121fb..88b7e350b 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/PositionManager.t.sol @@ -2131,7 +2131,6 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract reedemParams = IPositionManagerOwnerActions.RedeemPositionsParams( tokenId, address(_pool), indexes ); - changePrank(testReceiver); vm.expectEmit(true, true, true, true); emit TransferLPs(address(_positionManager), testReceiver, indexes, 15_000 * 1e18); From 8524b4219976f5eca2501c9de99a7058b9cc399a Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 17 Mar 2023 06:48:08 +0200 Subject: [PATCH 18/70] Calculate reserve on the spot when needed - save gas and solve rounding issue by using current pool t0debt when calculating reserves (#688) --- src/ERC20Pool.sol | 12 ++++------ src/ERC721Pool.sol | 20 ++++++---------- .../pool/commons/IPoolInternals.sol | 4 +--- src/libraries/external/Auctions.sol | 23 ++++++++++++------- 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 945e85b16..2bbe9bbf1 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -341,21 +341,17 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); - uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getNormalizedPoolQuoteTokenBalance(); - - uint256 liabilities = Deposits.treeSum(deposits) + auctions.totalBondEscrowed + reserveAuction.unclaimed; - SettleResult memory result = Auctions.settlePoolDebt( auctions, buckets, deposits, loans, + reserveAuction, + poolState, SettleParams({ borrower: borrowerAddress_, - reserves: (assets > liabilities) ? (assets - liabilities) : 0, - inflator: poolState.inflator, - bucketDepth: maxDepth_, - poolType: poolState.poolType + poolBalance: _getNormalizedPoolQuoteTokenBalance(), + bucketDepth: maxDepth_ }) ); diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index a33a0e9b2..5bacfce3b 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -379,25 +379,19 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ) external nonReentrant override { PoolState memory poolState = _accruePoolInterest(); - uint256 assets = Maths.wmul(poolBalances.t0Debt, poolState.inflator) + _getNormalizedPoolQuoteTokenBalance(); - - uint256 liabilities = Deposits.treeSum(deposits) + auctions.totalBondEscrowed + reserveAuction.unclaimed; - - SettleParams memory params = SettleParams( - { - borrower: borrowerAddress_, - reserves: (assets > liabilities) ? (assets-liabilities) : 0, - inflator: poolState.inflator, - bucketDepth: maxDepth_, - poolType: poolState.poolType - } - ); + SettleParams memory params = SettleParams({ + borrower: borrowerAddress_, + poolBalance: _getNormalizedPoolQuoteTokenBalance(), + bucketDepth: maxDepth_ + }); SettleResult memory result = Auctions.settlePoolDebt( auctions, buckets, deposits, loans, + reserveAuction, + poolState, params ); diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 0b5a5c409..0c7b41266 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -37,10 +37,8 @@ struct KickResult { struct SettleParams { address borrower; // borrower address to settle - uint256 reserves; // current reserves in pool - uint256 inflator; // current pool inflator uint256 bucketDepth; // number of buckets to use when settle debt - uint256 poolType; // number of buckets to use when settle debt + uint256 poolBalance; // current pool quote token balance } struct SettleResult { diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index 3ac261c76..d0889bb69 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -202,6 +202,8 @@ library Auctions { mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, LoansState storage loans_, + ReserveAuctionState storage reserveAuction_, + PoolState calldata poolState_, SettleParams memory params_ ) external returns (SettleResult memory result_) { uint256 kickTime = auctions_.liquidations[params_.borrower].kickTime; @@ -225,8 +227,8 @@ library Auctions { vars.price = _priceAt(vars.index); if (vars.unscaledDeposit != 0) { - vars.debt = Maths.wmul(borrower.t0Debt, params_.inflator); // current debt to be settled - vars.maxSettleableDebt = Maths.wmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral + vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled + vars.maxSettleableDebt = Maths.wmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); // enough deposit in bucket and collateral avail to settle entire debt @@ -243,14 +245,14 @@ library Auctions { vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); // subtract from debt the corresponding t0 amount of deposit - borrower.t0Debt -= Maths.wdiv(vars.scaledDeposit, params_.inflator); + borrower.t0Debt -= Maths.wdiv(vars.scaledDeposit, poolState_.inflator); } // settle constrained by collateral available else { vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); vars.collateralUsed = borrower.collateral; - borrower.t0Debt -= Maths.wdiv(vars.maxSettleableDebt, params_.inflator); + borrower.t0Debt -= Maths.wdiv(vars.maxSettleableDebt, poolState_.inflator); } // remove settled collateral from loan @@ -294,8 +296,13 @@ library Auctions { // if there's still debt and no collateral if (borrower.t0Debt != 0 && borrower.collateral == 0) { + + uint256 assets = Maths.wmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; + uint256 liabilities = Deposits.treeSum(deposits_) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; + uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; + // settle debt from reserves -- round reserves down however - borrower.t0Debt -= Maths.min(borrower.t0Debt, (params_.reserves / params_.inflator) * 1e18); + borrower.t0Debt -= Maths.min(borrower.t0Debt, (reserves / poolState_.inflator) * 1e18); // if there's still debt after settling from reserves then start to forgive amount from next HPB // loop through remaining buckets if there's still debt to settle @@ -305,7 +312,7 @@ library Auctions { (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); vars.depositToRemove = Maths.wmul(vars.scale, vars.unscaledDeposit); - vars.debt = Maths.wmul(borrower.t0Debt, params_.inflator); + vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // enough deposit in bucket to settle entire debt if (vars.depositToRemove >= vars.debt) { @@ -314,7 +321,7 @@ library Auctions { // not enough deposit to settle entire debt, we settle only deposit amount } else { - borrower.t0Debt -= Maths.wdiv(vars.depositToRemove, params_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit + borrower.t0Debt -= Maths.wdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket Bucket storage hpbBucket = buckets_[vars.index]; @@ -342,7 +349,7 @@ library Auctions { deposits_, params_.borrower, borrower.collateral, - params_.poolType + poolState_.poolType ); } From c3f0a383b9c66022d8dc9c82994b678e5ab03991 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 17 Mar 2023 19:04:08 +0200 Subject: [PATCH 19/70] Invariants logic fixes (#691) * Fix logic for settle in invariant handler * Bump reserves invariants epsilon * Invariant take fix, always clean out settled auction * update exchange rate invariant (to handle bucket take failing tests) --- .../invariants/BasicInvariants.t.sol | 7 +- .../invariants/ReserveInvariants.t.sol | 4 +- .../ERC20Pool/invariants/base/BaseHandler.sol | 6 +- .../base/UnboundedLiquidationPoolHandler.sol | 130 ++++++++---------- .../RegressionTestLiquidation.t.sol | 26 ++++ .../regression/RegressionTestReserves.t.sol | 18 +++ 6 files changed, 111 insertions(+), 80 deletions(-) diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 96c3ed260..a41b9e4aa 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -224,6 +224,7 @@ contract BasicInvariants is InvariantsTestBase { function invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { uint256 currentExchangeRate = _pool.bucketExchangeRate(bucketIndex); + (uint256 currentBucketLps, , , , ) = _pool.bucketInfo(bucketIndex); if (IBaseHandler(_handler).exchangeRateShouldNotChange(bucketIndex)) { uint256 previousExchangeRate = IBaseHandler(_handler).previousExchangeRate(bucketIndex); @@ -235,9 +236,9 @@ contract BasicInvariants is InvariantsTestBase { console.log("======================================"); requireWithinDiff( - currentExchangeRate, - previousExchangeRate, - 1e17, // TODO: check why changing so much + Maths.wmul(currentExchangeRate, currentBucketLps), + Maths.wmul(previousExchangeRate, currentBucketLps), + 1e5, "Incorrect exchange Rate changed" ); } diff --git a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol index 75436425c..0de797492 100644 --- a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol @@ -67,12 +67,12 @@ contract ReserveInvariants is LiquidationInvariants { console.log("Increase in Reserves -->", increaseInReserves); console.log("Decrease in Reserves -->", decreaseInReserves); console.log("Current Reserves -->", currentReserves); + console.log("Required Reserves -->", previousReserves + increaseInReserves - decreaseInReserves); - // TODO: check why rouding of 1 unit of WAD. Decrease reserve on startClaimableReserveAuction too requireWithinDiff( currentReserves, previousReserves + increaseInReserves - decreaseInReserves, - 1, + 1e15, "Incorrect Reserves change" ); } diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index 0f46ee686..1e8889ac8 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -335,10 +335,10 @@ abstract contract BaseHandler is Test { increaseInReserves += Maths.wmul(borrowerDebt_, 0.07 * 1e18); firstTake = true; - // reset taken flag in case auciton was settled by take action - _auctionSettleStateReset(borrower_); - } else firstTake = false; + + // reset taken flag in case auction was settled by take action + _auctionSettleStateReset(borrower_); } /**********************************/ diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol index 67ca6eed5..99cce8ef8 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -162,91 +162,77 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { uint256 maxDepth_ ) internal useTimestamps updateLocalStateAndPoolInterest { ( - uint256 borrowerDebt, + uint256 borrowerT0Debt, uint256 collateral, - ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + ) = _pool.borrowerInfo(borrower_); + (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - uint256 noOfBuckets = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX + 1; - - uint256[] memory changeInDeposit = new uint256[](noOfBuckets); - uint256 depositUsed; - - uint256 bucketDepth = maxDepth_; - - // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb - while (bucketDepth != 0 && borrowerDebt != 0 && collateral != 0) { - uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); - uint256 bucketUsed = bucketIndex - LENDER_MIN_BUCKET_INDEX; - uint256 maxSettleableDebt = Maths.wmul(collateral, _priceAt(bucketIndex)); - - if (bucketIndex != MAX_FENWICK_INDEX) { - // debt is greater than bucket deposit then exchange all deposit with collateral - if (borrowerDebt > fenwickDeposits[bucketIndex] && maxSettleableDebt >= fenwickDeposits[bucketIndex]) { - borrowerDebt -= fenwickDeposits[bucketIndex]; - changeInDeposit[bucketUsed] += fenwickDeposits[bucketIndex]; - collateral -= Maths.wdiv(fenwickDeposits[bucketIndex], _priceAt(bucketIndex)); - depositUsed += fenwickDeposits[bucketIndex]; - } - // collateral value is greater than borrower debt then exchange collateral with deposit - else if (maxSettleableDebt >= borrowerDebt) { - changeInDeposit[bucketUsed] += borrowerDebt; - collateral -= Maths.wdiv(borrowerDebt, _priceAt(bucketIndex)); - depositUsed += borrowerDebt; - borrowerDebt = 0; - } - // exchange all collateral with deposit - else { - changeInDeposit[bucketUsed] += maxSettleableDebt; - depositUsed += maxSettleableDebt; - collateral = 0; - borrowerDebt -= maxSettleableDebt; - } - } else collateral = 0; - - bucketDepth -= 1; - } - - // reserves used for settling auction - uint256 reserveChange; + try _pool.settle(borrower_, maxDepth_) { - // if collateral becomes 0 and still debt is left, settle debt by reserves and hpb making buckets bankrupt - if (borrowerDebt != 0 && collateral == 0) { - (uint256 reserves, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - - reserveChange = Maths.min(reserves, borrowerDebt); - borrowerDebt -= reserveChange; + (uint256 inflator, ) = _pool.inflatorInfo(); + uint256 depositUsed; - while (bucketDepth != 0 && borrowerDebt != 0) { - uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); - uint256 bucketUsed = bucketIndex - LENDER_MIN_BUCKET_INDEX; + // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb + while (maxDepth_ != 0 && borrowerT0Debt != 0 && collateral != 0) { + uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + uint256 maxSettleableDebt = Maths.wmul(collateral, _priceAt(bucketIndex)); if (bucketIndex != MAX_FENWICK_INDEX) { - - // debt is greater than bucket deposit - if (borrowerDebt > (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed])) { - borrowerDebt -= (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); - changeInDeposit[bucketUsed] += (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); - depositUsed += (fenwickDeposits[bucketIndex] - changeInDeposit[bucketUsed]); + // debt is greater than bucket deposit then exchange all deposit with collateral + if (Maths.wmul(borrowerT0Debt, inflator) > fenwickDeposits[bucketIndex] && maxSettleableDebt >= fenwickDeposits[bucketIndex]) { + borrowerT0Debt -= Maths.wdiv(fenwickDeposits[bucketIndex], inflator); + collateral -= Maths.wdiv(fenwickDeposits[bucketIndex], _priceAt(bucketIndex)); + depositUsed += fenwickDeposits[bucketIndex]; + fenwickDeposits[bucketIndex] = 0; + } + // collateral value is greater than borrower debt then exchange collateral with deposit + else if (maxSettleableDebt >= Maths.wmul(borrowerT0Debt, inflator)) { + fenwickDeposits[bucketIndex] -= Maths.wmul(borrowerT0Debt, inflator); + collateral -= Maths.wdiv(Maths.wmul(borrowerT0Debt, inflator), _priceAt(bucketIndex)); + depositUsed += Maths.wmul(borrowerT0Debt, inflator); + borrowerT0Debt = 0; } - // bucket deposit is greater than debt + // exchange all collateral with deposit else { - changeInDeposit[bucketUsed] += borrowerDebt; - depositUsed += borrowerDebt; - borrowerDebt = 0; + fenwickDeposits[bucketIndex] -= maxSettleableDebt; + depositUsed += maxSettleableDebt; + collateral = 0; + borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); } - } + } else collateral = 0; - bucketDepth -= 1; + maxDepth_ -= 1; } - } - try _pool.settle(borrower_, maxDepth_) { - - // **RE12**: Reserves decrease by amount of reserve used to settle a auction - decreaseInReserves = reserveChange; + // 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)); + // **RE12**: Reserves decrease by amount of reserve used to settle a auction + decreaseInReserves = reservesBeforeAction - reservesAfterAction; + + borrowerT0Debt -= Maths.min(decreaseInReserves, borrowerT0Debt); + + while (maxDepth_ != 0 && borrowerT0Debt != 0) { + uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + + if (bucketIndex != MAX_FENWICK_INDEX) { + // debt is greater than bucket deposit + if (Maths.wmul(borrowerT0Debt, inflator) > (fenwickDeposits[bucketIndex])) { + borrowerT0Debt -= Maths.wdiv(fenwickDeposits[bucketIndex], inflator); + depositUsed += fenwickDeposits[bucketIndex]; + fenwickDeposits[bucketIndex] = 0; + } + // bucket deposit is greater than debt + else { + fenwickDeposits[bucketIndex] -= Maths.wmul(borrowerT0Debt, inflator); + depositUsed += Maths.wmul(borrowerT0Debt, inflator); + borrowerT0Debt = 0; + } + } - for (uint256 bucket = 0; bucket <= maxDepth_; bucket++) { - _fenwickRemove(changeInDeposit[bucket], bucket + LENDER_MIN_BUCKET_INDEX); + maxDepth_ -= 1; + } } } catch (bytes memory err) { diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 35fd0f050..4492198f4 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -93,4 +93,30 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_fenwick_depositAtIndex_F1(); } + + function test_regression_invariant_incorrect_take_2() external { + _liquidationPoolHandler.kickAuction(13452, 7198, 11328); + _liquidationPoolHandler.takeAuction(6772, 18720, 6668); + _liquidationPoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1666258487708695528254610529989951, 490873240291829575083322665078478117042861655783753); + + invariant_auction_taken_A6(); + } + + function test_regression_invariant_exchange_rate_bucket_take_1() external { + _liquidationPoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); + _liquidationPoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); + _liquidationPoolHandler.bucketTake(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_invariant_exchange_rate_bucket_take_2() external { + _liquidationPoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); + _liquidationPoolHandler.settleAuction(186120755740, 2, 59199623628501455128); + _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); + _liquidationPoolHandler.bucketTake(2, 259574184, true, 248534890472324170412180243783490514876275); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index f014b70f7..e919491e2 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -161,4 +161,22 @@ contract RegressionTestReserve is ReserveInvariants { invariant_fenwick_depositAtIndex_F1(); } + + function test_regression_invariant_reserves_settle_1() external { + _reservePoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886); + _reservePoolHandler.startClaimableReserveAuction(18407833277983020451007887294192863287187933); + _reservePoolHandler.settleAuction(2720, 3319, 516); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_reserves_settle_2() external { + _reservePoolHandler.takeAuction(3, 214198155653990209702223102757081411626927025, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.repayDebt(36, 19087); + _reservePoolHandler.drawDebt(2550145944163683156825587547113715005197220288637184, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + } \ No newline at end of file From b6957546514bfbc864f717d798281ea17262c80f Mon Sep 17 00:00:00 2001 From: mattcushman <36414299+mattcushman@users.noreply.github.com> Date: Thu, 23 Mar 2023 07:53:54 -0400 Subject: [PATCH 20/70] Invariant F1, F2 and F4 fix (#697) * fix for f1 invariant in settle - divide debt by settle amount * Changes to invariant test 4 * Fix settle logic and F1 and F2 failures, added override to new function * F1, F2 fixes: Add useTimestamps to kick to accrue interest * Update local fenwick with pool deposits before each action * Renamed prefixSum method to depositUpToIndex --------- Co-authored-by: mwc Co-authored-by: grandizzy Co-authored-by: prateek105 --- src/base/Pool.sol | 5 ++ .../pool/commons/IPoolDerivedState.sol | 11 ++++- tests/INVARIANTS.md | 2 +- .../invariants/BasicInvariants.t.sol | 40 +++++++--------- .../ERC20Pool/invariants/base/BaseHandler.sol | 11 ++++- .../base/UnboundedLiquidationPoolHandler.sol | 46 +++++++++--------- .../handlers/LiquidationPoolHandler.sol | 4 +- .../regression/RegressionTestBasic.t.sol | 2 +- .../RegressionTestLiquidation.t.sol | 47 +++++++++++++++++++ .../regression/RegressionTestReserves.t.sol | 16 ++++++- 10 files changed, 129 insertions(+), 55 deletions(-) diff --git a/src/base/Pool.sol b/src/base/Pool.sol index c8063738c..1ebf6db7a 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -749,6 +749,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); } + /// @inheritdoc IPoolDerivedState + function depositUpToIndex(uint256 index_) external view override returns (uint256) { + return Deposits.prefixSum(deposits, index_); + } + /// @inheritdoc IPoolDerivedState function depositIndex(uint256 debt_) external view override returns (uint256) { return Deposits.findIndexOfSum(deposits, debt_); diff --git a/src/interfaces/pool/commons/IPoolDerivedState.sol b/src/interfaces/pool/commons/IPoolDerivedState.sol index a19fa0b54..07198be57 100644 --- a/src/interfaces/pool/commons/IPoolDerivedState.sol +++ b/src/interfaces/pool/commons/IPoolDerivedState.sol @@ -11,6 +11,15 @@ interface IPoolDerivedState { uint256 index_ ) external view returns (uint256 exchangeRate_); + /** + * @notice Returns the prefix sum of a given bucket + * @param index_ The target index + * @return Prefix sum + */ + function depositUpToIndex( + uint256 index_ + ) external view returns (uint256); + /** * @notice Returns the bucket index for a given debt amount. * @param debt_ The debt amount to calculate bucket index for. @@ -32,4 +41,4 @@ interface IPoolDerivedState { */ function depositUtilization() external view returns (uint256); -} \ No newline at end of file +} diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 75de10952..af4853598 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -47,7 +47,7 @@ - **F1**: Value represented at index `i` (`Deposits.valueAt(i)`) is equal to the accumulation of scaled values incremented or decremented from index `i` - **F2**: For any index `i`, the prefix sum up to and including `i` is the sum of values stored in indices `j<=i` - **F3**: For any index `i < MAX_FENWICK_INDEX`, `findIndexOfSum(prefixSum(i)) > i` -- **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j))`, where j is the next index from i with deposits != 0 +- **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `depositAtIndex(j) == 0 for i < j < findIndexOfSum(prefixSum(i)+1)` ## Exchange rate invariants ## - **R1**: Exchange rates are unchanged by pledging collateral diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index a41b9e4aa..447577b2c 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -377,33 +377,25 @@ contract BasicInvariants is InvariantsTestBase { } } - // For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)) where j is the next index from i with deposits != 0 + // **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `depositAt(j) == 0 for i0 function invariant_fenwick_prefixSumIndex_F4() public useCurrentTimestamp { - uint256 prefixSum; - - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); - - if (depositAtIndex != 0) { - prefixSum += depositAtIndex; - uint256 nextBucketIndexWithDeposit = _pool.depositIndex(prefixSum + 1); - (, , , uint256 depositAtNextBucket, ) = _pool.bucketInfo(nextBucketIndexWithDeposit); - uint256 prefixSumTillNextBucket = prefixSum + depositAtNextBucket; - - console.log("============"); - console.log("Deposit Index of presum -->", _pool.depositIndex(prefixSum)); - console.log("Presum -->", prefixSum); - console.log("depositAtNextBucket ===>", depositAtNextBucket); - console.log("prefixSumTillNextBucket -->", prefixSumTillNextBucket); - console.log("nextBucketIndexWithDeposit -->", nextBucketIndexWithDeposit); - console.log("BucketIndex -->", bucketIndex); - console.log("Pool deposit Index -->", _pool.depositIndex(prefixSumTillNextBucket - depositAtNextBucket)); - + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; ) { + uint256 nextNonzeroBucket = _pool.depositIndex(_pool.depositUpToIndex(bucketIndex)+1); + console.log("bucketIndex: ", bucketIndex); + console.log("Next nonzero bucket: ", nextNonzeroBucket); + for(uint256 j = bucketIndex + 1; j < nextNonzeroBucket && j < LENDER_MAX_BUCKET_INDEX; j++) { + (, , , uint256 depositAtJ, ) = _pool.bucketInfo(j); + // console.log("Deposit at %s is %s", j, depositAtJ); require( - bucketIndex == _pool.depositIndex(prefixSumTillNextBucket - depositAtNextBucket), - "Incorrect buckets with 0 deposit" + depositAtJ == 0, + "F4: incorrect buckets with 0 deposit" ); } + (, , , uint256 depositAtNextIndex, ) = _pool.bucketInfo(nextNonzeroBucket); + console.log("Deposit at nonzero bucket %s is %s", nextNonzeroBucket, depositAtNextIndex); + assertGe(depositAtNextIndex, 0, "F4: incorrect buckets with 0 deposit"); + assertGe(nextNonzeroBucket+1, bucketIndex); + bucketIndex = nextNonzeroBucket+1; // can skip ahead } } @@ -435,4 +427,4 @@ contract BasicInvariants is InvariantsTestBase { ); } -} \ No newline at end of file +} diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index 1e8889ac8..ee76a624c 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -108,6 +108,7 @@ abstract contract BaseHandler is Test { * @dev Resets all local states before each action. */ modifier updateLocalStateAndPoolInterest() { + _updateLocalFenwick(); _fenwickAccrueInterest(); _updatePoolState(); @@ -305,6 +306,14 @@ abstract contract BaseHandler is Test { index_--; } } + + // 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; + } + } /*********************************/ /*** Auctions Helper Functions ***/ @@ -406,4 +415,4 @@ abstract contract BaseHandler is Test { if (max_ == type(uint256).max && x_ != 0) result_++; } -} \ No newline at end of file +} diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol index 99cce8ef8..ffd12ecb3 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -166,42 +166,40 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { uint256 collateral, ) = _pool.borrowerInfo(borrower_); (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); + (uint256 inflator, ) = _pool.inflatorInfo(); try _pool.settle(borrower_, maxDepth_) { - (uint256 inflator, ) = _pool.inflatorInfo(); - uint256 depositUsed; - // settle borrower debt with exchanging borrower collateral with quote tokens starting from hpb while (maxDepth_ != 0 && borrowerT0Debt != 0 && collateral != 0) { - uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + uint256 bucketIndex = fenwickIndexForSum(1); uint256 maxSettleableDebt = Maths.wmul(collateral, _priceAt(bucketIndex)); + uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; + uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); if (bucketIndex != MAX_FENWICK_INDEX) { - // debt is greater than bucket deposit then exchange all deposit with collateral - if (Maths.wmul(borrowerT0Debt, inflator) > fenwickDeposits[bucketIndex] && maxSettleableDebt >= fenwickDeposits[bucketIndex]) { - borrowerT0Debt -= Maths.wdiv(fenwickDeposits[bucketIndex], inflator); - collateral -= Maths.wdiv(fenwickDeposits[bucketIndex], _priceAt(bucketIndex)); - depositUsed += fenwickDeposits[bucketIndex]; - fenwickDeposits[bucketIndex] = 0; - } - // collateral value is greater than borrower debt then exchange collateral with deposit - else if (maxSettleableDebt >= Maths.wmul(borrowerT0Debt, inflator)) { - fenwickDeposits[bucketIndex] -= Maths.wmul(borrowerT0Debt, inflator); - collateral -= Maths.wdiv(Maths.wmul(borrowerT0Debt, inflator), _priceAt(bucketIndex)); - depositUsed += Maths.wmul(borrowerT0Debt, inflator); + // enough deposit in bucket and collateral avail to settle entire debt + if (fenwickDeposit >= borrowerDebt && maxSettleableDebt >= borrowerDebt) { + fenwickDeposits[bucketIndex] -= borrowerDebt; + collateral -= Maths.wdiv(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.wdiv(fenwickDeposit, _priceAt(bucketIndex)); + borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); + } // exchange all collateral with deposit else { fenwickDeposits[bucketIndex] -= maxSettleableDebt; - depositUsed += maxSettleableDebt; collateral = 0; borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); } } else collateral = 0; maxDepth_ -= 1; + } // if collateral becomes 0 and still debt is left, settle debt by reserves and hpb making buckets bankrupt @@ -211,22 +209,22 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // **RE12**: Reserves decrease by amount of reserve used to settle a auction decreaseInReserves = reservesBeforeAction - reservesAfterAction; - borrowerT0Debt -= Maths.min(decreaseInReserves, borrowerT0Debt); + borrowerT0Debt -= Maths.min(Maths.wdiv(decreaseInReserves, inflator), borrowerT0Debt); while (maxDepth_ != 0 && borrowerT0Debt != 0) { - uint256 bucketIndex = fenwickIndexForSum(1 + depositUsed); + 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 (Maths.wmul(borrowerT0Debt, inflator) > (fenwickDeposits[bucketIndex])) { - borrowerT0Debt -= Maths.wdiv(fenwickDeposits[bucketIndex], inflator); - depositUsed += fenwickDeposits[bucketIndex]; + if (borrowerDebt > fenwickDeposit) { fenwickDeposits[bucketIndex] = 0; + borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); } // bucket deposit is greater than debt else { - fenwickDeposits[bucketIndex] -= Maths.wmul(borrowerT0Debt, inflator); - depositUsed += Maths.wmul(borrowerT0Debt, inflator); + fenwickDeposits[bucketIndex] -= borrowerDebt; borrowerT0Debt = 0; } } diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol index 95edfd0ee..e70f89a77 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol @@ -136,7 +136,7 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan uint256 borrowerIndex_, uint256 amount_, uint256 kickerIndex_ - ) internal useRandomActor(kickerIndex_) { + ) internal useTimestamps useRandomActor(kickerIndex_) { numberOfCalls['BLiquidationHandler.kickAuction']++; borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); @@ -164,4 +164,4 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan // skip some time for more interest vm.warp(block.timestamp + 2 hours); } -} \ No newline at end of file +} diff --git a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol index 0b1853471..d5691de40 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol @@ -260,4 +260,4 @@ contract RegressionTestBasic is BasicInvariants { invariant_fenwick_depositAtIndex_F1(); } -} \ No newline at end of file +} diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 4492198f4..570fbafa0 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -119,4 +119,51 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } + + function test_regression_invariant_settle_F1_1() external { + _liquidationPoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980); + _liquidationPoolHandler.kickAuction(5135, 1752, 6350); + _liquidationPoolHandler.kickAuction(142699, 4496, 4356); + _liquidationPoolHandler.moveQuoteToken(1173, 1445, 792325212, 447); + _liquidationPoolHandler.settleAuction(18308, 3145, 947); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F1_2() external { + _liquidationPoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationPoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); + _liquidationPoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); + _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); + _liquidationPoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F1_3() external { + _liquidationPoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); + _liquidationPoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); + _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F2_1() external { + _liquidationPoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationPoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); + _liquidationPoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); + _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); + _liquidationPoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_invariant_settle_F2_2() external { + _liquidationPoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); + _liquidationPoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); + _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + + invariant_fenwick_depositsTillIndex_F2(); + } + } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index e919491e2..ab49e5d7f 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -179,4 +179,18 @@ contract RegressionTestReserve is ReserveInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } -} \ No newline at end of file + function test_regression_invariant_repayDebt_F2_1() external { + _reservePoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045); + _reservePoolHandler.kickAuction(1000000000372489032271805343253, 33527, 2999999999999998999627510967728193679786334003); + _reservePoolHandler.takeAuction(30442763437987671335943625876181535412080651070033770037765737902267600059, 0, 62793434148368637031717982910725); + _reservePoolHandler.drawDebt(1, 2); + _reservePoolHandler.takeAuction(28478785935025462058931686388528614452411453327852591879599088, 1426479312070353, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.drawDebt(10933, 2937); + _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 6122968755523040); + _reservePoolHandler.repayDebt(10917282482493108186780095138347753666882231491750232316870663654516774564, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + + invariant_fenwick_depositsTillIndex_F2(); + } + +} From 90b78dba54e0786946f2ad38d590491072548d27 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 24 Mar 2023 06:21:36 +0200 Subject: [PATCH 21/70] Mul before div consistency (#699) * _dwatp mul before div * t0KickPenalty mul before div * _lenderInterestMargin calculate by mul before div --- src/libraries/external/Auctions.sol | 2 +- src/libraries/external/PoolCommons.sol | 2 +- src/libraries/helpers/PoolHelper.sol | 2 +- .../ERC20PoolLiquidationsArbTake.t.sol | 54 ++++++++--------- .../ERC20PoolLiquidationsDepositTake.t.sol | 60 +++++++++---------- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 32 +++++----- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 4 +- 7 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index d0889bb69..a78dab23a 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -890,7 +890,7 @@ library Auctions { Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); // when loan is kicked, penalty of three months of interest is added - vars.t0KickPenalty = Maths.wmul(kickResult_.t0KickedDebt, Maths.wdiv(poolState_.rate, 4 * 1e18)); + vars.t0KickPenalty = Maths.wdiv(Maths.wmul(kickResult_.t0KickedDebt, poolState_.rate), 4 * 1e18); vars.kickPenalty = Maths.wmul(vars.t0KickPenalty, poolState_.inflator); kickResult_.t0PoolDebt = poolState_.t0Debt + vars.t0KickPenalty; diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 8069cec73..88bb53b5e 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -310,7 +310,7 @@ library PoolCommons { // cubic root of the percentage of meaningful unutilized deposit uint256 crpud = PRBMathUD60x18.pow(base, ONE_THIRD); // finish calculating Net Interest Margin, and then convert to Lender Interest Margin - return 1e18 - Maths.wmul(Maths.wdiv(crpud, CUBIC_ROOT_1000000), 0.15 * 1e18); + return 1e18 - Maths.wdiv(Maths.wmul(crpud, 0.15 * 1e18), CUBIC_ROOT_1000000); } } diff --git a/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index 67dc5dd3e..27d708e35 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -142,7 +142,7 @@ import { Maths } from '../internal/Maths.sol'; uint256 inflator_, uint256 t0Debt2ToCollateral_ ) pure returns (uint256) { - return t0Debt_ == 0 ? 0 : Maths.wmul(inflator_, Maths.wdiv(t0Debt2ToCollateral_, t0Debt_)); + return t0Debt_ == 0 ? 0 : Maths.wdiv(Maths.wmul(inflator_, t0Debt2ToCollateral_), t0Debt_); } /** diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index 620495ae5..fdc3046a6 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -163,7 +163,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.778456451861613480 * 1e18, + debt: 19.778456451861613481 * 1e18, collateral: 2 * 1e18, bond: 0.195342779771472726 * 1e18, transferAmount: 0.195342779771472726 * 1e18 @@ -180,7 +180,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 328.175870016074179200 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889228225930806740 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) @@ -217,7 +217,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { }); _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.779116873676490456 * 1e18, + borrowerDebt: 19.779116873676490457 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.982985835729561629 * 1e18 @@ -241,7 +241,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { exchangeRate: 1.013413645989143096 * 1e18 }); _assertReserveAuction({ - reserves: 24.540805142364516444 * 1e18, + reserves: 24.540805142364516445 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -259,14 +259,14 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 7.251730722192532064 * 1e18, - debtInAuction: 19.779116873676490456 * 1e18, + debtInAuction: 19.779116873676490457 * 1e18, thresholdPrice: 9.889558436838245228 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.779116873676490456 * 1e18, + borrowerDebt: 19.779116873676490457 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.982985835729561629 * 1e18 @@ -307,7 +307,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { }); // reserves should remain the same after arb take _assertReserveAuction({ - reserves: 25.925343323521870782 * 1e18, + reserves: 25.925343323521870783 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -315,7 +315,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { }); _assertBorrower({ borrower: _borrower, - borrowerDebt: 6.805228224892631302 * 1e18, + borrowerDebt: 6.805228224892631303 * 1e18, borrowerCollateral: 0, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0 @@ -331,7 +331,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 7.251730722192532064 * 1e18, - debtInAuction: 6.805228224892631302 * 1e18, + debtInAuction: 6.805228224892631303 * 1e18, thresholdPrice: 0, neutralPrice: 10.255495938002318100 * 1e18 }) @@ -360,8 +360,8 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 20.510991876004636192 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, - thresholdPrice: 9.889482233342512889 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, + thresholdPrice: 9.889482233342512890 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) ); @@ -376,7 +376,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.778964466685025779 * 1e18, + borrowerDebt: 19.778964466685025780 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 152.208547722958917634 * 1e18 @@ -389,7 +389,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kicker: _lender, index: _i1505_26, collateralArbed: 1.031812215971460994 * 1e18, - quoteTokenAmount: 21.163491979352977584 * 1e18, + quoteTokenAmount: 21.163491979352977585 * 1e18, bondChange: 0.195342779771472726 * 1e18, isReward: false, lpAwardTaker: 1_531.986011313779866429 * 1e18, @@ -419,11 +419,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 26_531.986011313779866429 * 1e18, collateral: 1.031812215971460994 * 1e18, - deposit: 24_978.836508020647022416 * 1e18, + deposit: 24_978.836508020647022415 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 26.111530884129144302 * 1e18, + reserves: 26.111530884129144303 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -446,8 +446,8 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 20.510991876004636192 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, - thresholdPrice: 9.889482233342512889 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, + thresholdPrice: 9.889482233342512890 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) ); @@ -462,7 +462,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.778964466685025779 * 1e18, + borrowerDebt: 19.778964466685025780 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.982993410135902682 * 1e18 @@ -507,9 +507,9 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { }); _assertBorrower({ borrower: _borrower, - borrowerDebt: 6.163491979352977583 * 1e18, + borrowerDebt: 6.163491979352977584 * 1e18, borrowerCollateral: 1.268684806142984527 * 1e18, - borrowert0Np: 5.057793757429320955 * 1e18, + borrowert0Np: 5.057793757429320956 * 1e18, borrowerCollateralization: 2.001018319047304755 * 1e18 }); _assertLenderLpBalance({ @@ -568,14 +568,14 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 82.043967504018544800 * 1e18, - debtInAuction: 19.778761259189860403 * 1e18, - thresholdPrice: 9.889380629594930201 * 1e18, + debtInAuction: 19.778761259189860404 * 1e18, + thresholdPrice: 9.889380629594930202 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.778761259189860403 * 1e18, + borrowerDebt: 19.778761259189860404 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.983003509435146965 * 1e18 @@ -587,7 +587,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kicker: _lender, index: _i10016, collateralArbed: 0.257950403803869741 * 1e18, - quoteTokenAmount: 21.163274547333150631 * 1e18, + quoteTokenAmount: 21.163274547333150632 * 1e18, bondChange: 0.195342779771472726 * 1e18, isReward: false, lpAwardTaker: 2_562.597355112798042 * 1e18, @@ -615,7 +615,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i10016, lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LPs awarded for arb taker collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket + deposit: 978.836725452666849367 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 }); _assertAuction( @@ -672,14 +672,14 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 116.027691555080513536 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889355228821139433 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.778710457642278866 * 1e18, + borrowerDebt: 19.778710457642278867 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.983006034276170567 * 1e18 diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 31ad2a943..2df199de0 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -164,7 +164,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 20.189067248182664593 * 1e18, + debt: 20.189067248182664594 * 1e18, collateral: 2 * 1e18, bond: 0.199398195043779403 * 1e18, transferAmount: 0.199398195043779403 * 1e18 @@ -181,8 +181,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 334.988967673549397664 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094533624091332296 * 1e18, + debtInAuction: 20.189067248182664594 * 1e18, + thresholdPrice: 10.094533624091332297 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); @@ -217,7 +217,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { }); _assertBorrower({ borrower: _borrower, - borrowerDebt: 20.189741380689676442 * 1e18, + borrowerDebt: 20.189741380689676443 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.962993599742653326 * 1e18 @@ -241,8 +241,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { exchangeRate: 1.000003244027008957 * 1e18 }); _assertReserveAuction({ - reserves: 286.940599154163800220 * 1e18, - claimableReserves : 245.508462704973068078 * 1e18, + reserves: 286.940599154163800221 * 1e18, + claimableReserves : 245.508462704973068079 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -259,14 +259,14 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 7.402280333270247968 * 1e18, - debtInAuction: 20.189741380689676442 * 1e18, + debtInAuction: 20.189741380689676443 * 1e18, thresholdPrice: 10.094870690344838221 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 20.189741380689676442 * 1e18, + borrowerDebt: 20.189741380689676443 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.962993599742653326 * 1e18 @@ -307,8 +307,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { }); // reserves should remain the same after deposit take _assertReserveAuction({ - reserves: 288.353881050812077571 * 1e18, - claimableReserves : 247.012858322088119572 * 1e18, + reserves: 288.353881050812077572 * 1e18, + claimableReserves : 247.012858322088119573 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -324,14 +324,14 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 7.402280333270247968 * 1e18, - debtInAuction: 1.966997287334847886 * 1e18, + debtInAuction: 1.966997287334847887 * 1e18, thresholdPrice: 0, neutralPrice: 10.468405239798418677 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 1.966997287334847886 * 1e18, + borrowerDebt: 1.966997287334847887 * 1e18, borrowerCollateral: 0, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0 @@ -360,8 +360,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 20.936810479596837344 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094792904825850359 * 1e18, + debtInAuction: 20.189067248182664594 * 1e18, + thresholdPrice: 10.094792904825850360 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); @@ -376,7 +376,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 20.189585809651700719 * 1e18, + borrowerDebt: 20.189585809651700720 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 149.112888462473727465 * 1e18 @@ -396,7 +396,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kicker: _lender, index: _i1505_26, collateralArbed: 0.014351542794629452 * 1e18, - quoteTokenAmount: 21.602856816327319769 * 1e18, + quoteTokenAmount: 21.602856816327319770 * 1e18, bondChange: 0.199398195043779403 * 1e18, isReward: false, lpAwardTaker: 0, @@ -426,12 +426,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 25_000 * 1e18, collateral: 0.014351542794629452 * 1e18, - deposit: 24_978.397143183672680231 * 1e18, + deposit: 24_978.397143183672680230 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 288.543944498908261870 * 1e18, - claimableReserves : 247.213075232011850972 * 1e18, + reserves: 288.543944498908261871 * 1e18, + claimableReserves : 247.213075232011850973 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -453,8 +453,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 20.936810479596837344 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094792904825850359 * 1e18, + debtInAuction: 20.189067248182664594 * 1e18, + thresholdPrice: 10.094792904825850360 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); @@ -469,7 +469,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 20.189585809651700719 * 1e18, + borrowerDebt: 20.189585809651700720 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.963001020098637267 * 1e18 @@ -501,7 +501,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { totalBondEscrowed: 0.049398195043779403 * 1e18, auctionPrice: 0, debtInAuction: 0, - thresholdPrice: 3.317960196583009903 * 1e18, + thresholdPrice: 3.317960196583009904 * 1e18, neutralPrice: 0 }) ); @@ -514,7 +514,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { }); _assertBorrower({ borrower: _borrower, - borrowerDebt: 6.602856816327319769 * 1e18, + borrowerDebt: 6.602856816327319770 * 1e18, borrowerCollateral: 1.990034968812238781 * 1e18, borrowert0Np: 3.384038787324199948 * 1e18, borrowerCollateralization: 2.929901291475173000 * 1e18 @@ -575,14 +575,14 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 83.747241918387349408 * 1e18, - debtInAuction: 20.189378383465778990 * 1e18, + debtInAuction: 20.189378383465778991 * 1e18, thresholdPrice: 10.094689191732889495 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 20.189378383465778990 * 1e18, + borrowerDebt: 20.189378383465778991 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.963010913995558897 * 1e18 @@ -594,7 +594,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kicker: _lender, index: _i10016, collateralArbed: 0.002156704581707556 * 1e18, - quoteTokenAmount: 21.602634870308383519 * 1e18, + quoteTokenAmount: 21.602634870308383520 * 1e18, bondChange: 0.199398195043779403 * 1e18, isReward: false, lpAwardTaker: 0, @@ -623,7 +623,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i10016, lpBalance: 1_000 * 1e18, // LP balance in arbed bucket increased with LPs awarded for deposit taker collateral: 0.002156704581707556 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.397365129691616481* 1e18, // quote token amount is diminished in arbed bucket + deposit: 978.397365129691616480 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 }); _assertAuction( @@ -673,8 +673,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.199398195043779403 * 1e18, auctionPrice: 118.436485332323967968 * 1e18, - debtInAuction: 20.189067248182664592 * 1e18, - thresholdPrice: 10.094663263626140337 * 1e18, + debtInAuction: 20.189067248182664594 * 1e18, + thresholdPrice: 10.094663263626140338 * 1e18, neutralPrice: 10.468405239798418677 * 1e18 }) ); diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 6b8604254..92599c6cb 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -165,7 +165,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.778456451861613480 * 1e18, + debt: 19.778456451861613481 * 1e18, collateral: 2 * 1e18, bond: 0.195342779771472726 * 1e18, transferAmount: 0.195342779771472726 * 1e18 @@ -182,7 +182,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { poolSize: 73_093.873009488594553000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 835.035237319063220561 * 1e18, - poolDebt: 8_117.624599705640061720 * 1e18, + poolDebt: 8_117.624599705640061721 * 1e18, actualUtilization: 0.111200336982269042 * 1e18, targetUtilization: 0.833449668459897038 * 1e18, minDebtAmount: 811.762459970564006172 * 1e18, @@ -194,7 +194,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { ); _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.778456451861613480 * 1e18, + borrowerDebt: 19.778456451861613481 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 10.115967548076923081 * 1e18, borrowerCollateralization: 0.983018658578564579 * 1e18 @@ -220,7 +220,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 328.175870016074179200 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889228225930806740 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) @@ -232,7 +232,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { locked: 0.195342779771472726 * 1e18 }); _assertReserveAuction({ - reserves: 24.501590217045508720 * 1e18, + reserves: 24.501590217045508721 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -298,7 +298,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.778456451861613480 * 1e18, + debt: 19.778456451861613481 * 1e18, collateral: 2 * 1e18, bond: 0.195342779771472726 * 1e18, transferAmount: 0.195342779771472726 * 1e18 @@ -315,7 +315,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 328.175870016074179200 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889228225930806740 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) @@ -347,7 +347,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 0, debtInAuction: 0, - thresholdPrice: 8.889228225930806739 * 1e18, + thresholdPrice: 8.889228225930806740 * 1e18, neutralPrice: 0 }) ); @@ -362,7 +362,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 19.500754673204780610 * 1e18, + borrowerDebt: 19.500754673204780611 * 1e18, borrowerCollateral: 2 * 1e18, borrowert0Np: 9.254718877190426162 * 1e18, borrowerCollateralization: 0.997017400397270737 * 1e18 @@ -372,7 +372,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.720138163278334392 * 1e18, + debt: 19.720138163278334393 * 1e18, collateral: 2 * 1e18, bond: 0.195007546732047806 * 1e18, transferAmount: 0 @@ -401,7 +401,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 329.321295632797165376 * 1e18, - debtInAuction: 19.720038163278334392 * 1e18, + debtInAuction: 19.720038163278334393 * 1e18, thresholdPrice: 9.860019081639167196 * 1e18, neutralPrice: 10.291290488524911418 * 1e18 }) @@ -431,7 +431,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 0, debtInAuction: 0, - thresholdPrice: 4.860069081639167195 * 1e18, + thresholdPrice: 4.860069081639167196 * 1e18, neutralPrice: 0 }) ); @@ -512,7 +512,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.778456451861613480 * 1e18, + debt: 19.778456451861613481 * 1e18, collateral: 2 * 1e18, bond: 0.195342779771472726 * 1e18, transferAmount: 0.195342779771472726 * 1e18 @@ -529,7 +529,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 328.175870016074179200 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889228225930806740 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) @@ -616,7 +616,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 19.778456451861613480 * 1e18, + debt: 19.778456451861613481 * 1e18, collateral: 2 * 1e18, bond: 0.195342779771472726 * 1e18, transferAmount: 0.195342779771472726 * 1e18 @@ -633,7 +633,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 0.195342779771472726 * 1e18, auctionPrice: 328.175870016074179200 * 1e18, - debtInAuction: 19.778456451861613480 * 1e18, + debtInAuction: 19.778456451861613481 * 1e18, thresholdPrice: 9.889228225930806740 * 1e18, neutralPrice: 10.255495938002318100 * 1e18 }) diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index b705891df..eb15b4d10 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -2102,7 +2102,7 @@ contract ERC20PoolLiquidationsTakeAndRepayAllDebtInPoolTest is ERC20HelperContra _kick({ from: _kicker, borrower: _borrower, - debt: 104.162540773774892915 * 1e18, + debt: 104.162540773774892916 * 1e18, collateral: 0.067433366047580170 * 1e18, bond: 1.028765834802714992 * 1e18, transferAmount: 1.028765834802714992 * 1e18 @@ -2118,7 +2118,7 @@ contract ERC20PoolLiquidationsTakeAndRepayAllDebtInPoolTest is ERC20HelperContra borrower: _borrower, maxCollateral: 0.067433366047580170 * 1e18, bondChange: 1.028765834802714992 * 1e18, - givenAmount: 111.455789568155429076 * 1e18, + givenAmount: 111.455789568155429077 * 1e18, collateralTaken: 0.010471063560951988 * 1e18, isReward: false }); From fb2528e3a1f42536e847652c8a299746b5efdd4e Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Fri, 24 Mar 2023 16:40:08 +0530 Subject: [PATCH 22/70] Fix settle with reserves and interest accrual roundings (#687) * Fix: settle pool debt from reserves calculations * Add: Test for settle pool debt with reserves * Update failing tests * Remove bad debt from reserves calculations for settlePoolDebt * Revert "Remove bad debt from reserves calculations for settlePoolDebt" This reverts commit 37630fce32b96fb6a5ef6fcddf7a0a0f3b9aa7f6. * Update lender factor calculation to round down * Update unit tests and scale calculation in local fenwick accrue interest * Fix tests * Add tearDown in testSettleWithReserves * Update calculations to round down settleable borrower debt * Add failing regression tests * Code cleanup * Add wdivUp method to round up with division, update deposit calculation in settle debt and fix unit tests * Failing test with MAX_LESS_THAN_MIN * Revert "Add wdivUp method to round up with division, update deposit calculation in settle debt and fix unit tests" This reverts commit 24da08599108943f0a96b14a8dbe2747773f88c7. * Add greaterThanWithinDiff method to check values greater or with in difference, fix regression test * Fix take reserves invariant * Run regression tests as part of CI * Fix underflow in paralles settle logic * Regression test to expose broken logic in replicated settle invariant test * Fix: Reserve logic in settleAuction handler * Add burnable test token, fix take reserves logic * Fix take invariant logic (#695) * Fix failing regression test, add new failing QT1 invariant regression test * Merge branch 'develop' into fix-settle-pool-debt-calculations * Updated QT1 epsilon to 1e13 * PR feedback --------- Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> --- .github/workflows/forge-test.yml | 2 +- src/libraries/external/Auctions.sol | 10 +- src/libraries/external/PoolCommons.sol | 2 +- tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol | 20 +-- .../forge/ERC20Pool/ERC20PoolCollateral.t.sol | 4 +- .../ERC20PoolInterestRateAndEMAs.t.sol | 26 ++- .../ERC20PoolLiquidationsArbTake.t.sol | 34 ++-- .../ERC20PoolLiquidationsDepositTake.t.sol | 18 +- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 10 +- ...ERC20PoolLiquidationsKickWithDeposit.t.sol | 6 +- .../ERC20Pool/ERC20PoolLiquidationsMisc.t.sol | 28 +-- .../ERC20PoolLiquidationsSettle.t.sol | 165 ++++++++++++++---- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 68 ++++---- .../ERC20Pool/ERC20PoolPurchaseQuote.t.sol | 12 +- .../forge/ERC20Pool/ERC20PoolQuoteToken.t.sol | 12 +- .../invariants/BasicInvariants.t.sol | 14 +- .../invariants/LiquidationInvariants.t.sol | 1 + .../invariants/ReserveInvariants.t.sol | 1 + .../ERC20Pool/invariants/base/BaseHandler.sol | 10 +- .../invariants/base/InvariantsTestBase.sol | 10 +- .../invariants/base/InvariantsTestHelpers.sol | 4 + .../base/UnboundedLiquidationPoolHandler.sol | 41 ++++- .../base/UnboundedReservePoolHandler.sol | 7 +- .../invariants/handlers/BasicPoolHandler.sol | 5 +- .../handlers/LiquidationPoolHandler.sol | 5 +- .../handlers/ReservePoolHandler.sol | 12 +- .../RegressionTestLiquidation.t.sol | 9 + .../regression/RegressionTestReserves.t.sol | 143 +++++++++++++++ tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol | 14 +- tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol | 42 ++--- .../forge/ERC721Pool/ERC721PoolInterest.t.sol | 12 +- .../ERC721PoolLiquidationsSettleAuction.t.sol | 36 ++-- .../ERC721PoolLiquidationsTake.t.sol | 4 +- .../ERC721Pool/ERC721PoolReserveAuction.t.sol | 24 +-- tests/forge/RewardsManager.t.sol | 114 ++++++------ tests/forge/utils/Tokens.sol | 9 + 36 files changed, 625 insertions(+), 309 deletions(-) diff --git a/.github/workflows/forge-test.yml b/.github/workflows/forge-test.yml index d1db00af8..5d5446cfa 100644 --- a/.github/workflows/forge-test.yml +++ b/.github/workflows/forge-test.yml @@ -31,5 +31,5 @@ jobs: - name: Run tests run: | - make test-with-gas-report + make test-with-gas-report && make test-regression id: test \ No newline at end of file diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index a78dab23a..4def9874a 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -228,7 +228,7 @@ library Auctions { if (vars.unscaledDeposit != 0) { vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled - vars.maxSettleableDebt = Maths.wmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral + vars.maxSettleableDebt = (borrower.collateral * vars.price) / 1e18; // max debt that can be settled with existing collateral vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); // enough deposit in bucket and collateral avail to settle entire debt @@ -245,14 +245,14 @@ library Auctions { vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); // subtract from debt the corresponding t0 amount of deposit - borrower.t0Debt -= Maths.wdiv(vars.scaledDeposit, poolState_.inflator); + borrower.t0Debt -= (vars.scaledDeposit * 1e18) / poolState_.inflator; } // settle constrained by collateral available else { vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); vars.collateralUsed = borrower.collateral; - borrower.t0Debt -= Maths.wdiv(vars.maxSettleableDebt, poolState_.inflator); + borrower.t0Debt -= (vars.maxSettleableDebt * 1e18) / poolState_.inflator; } // remove settled collateral from loan @@ -302,7 +302,7 @@ library Auctions { uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; // settle debt from reserves -- round reserves down however - borrower.t0Debt -= Maths.min(borrower.t0Debt, (reserves / poolState_.inflator) * 1e18); + borrower.t0Debt -= Maths.min(borrower.t0Debt, (reserves * 1e18) / poolState_.inflator); // if there's still debt after settling from reserves then start to forgive amount from next HPB // loop through remaining buckets if there's still debt to settle @@ -321,7 +321,7 @@ library Auctions { // not enough deposit to settle entire debt, we settle only deposit amount } else { - borrower.t0Debt -= Maths.wdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit + borrower.t0Debt -= (vars.depositToRemove * 1e18) / poolState_.inflator; // subtract from remaining debt the corresponding t0 amount of deposit Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket Bucket storage hpbBucket = buckets_[vars.index]; diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 88bb53b5e..1410d4fb6 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -222,7 +222,7 @@ library PoolCommons { Deposits.mult( deposits_, htpIndex, - Maths.wdiv(newInterest_, depositAboveHtp) + Maths.WAD // lender factor + (newInterest_ * 1e18) / depositAboveHtp + Maths.WAD // lender factor ); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol index 582eb91d5..b28a1323e 100644 --- a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol @@ -370,7 +370,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { PoolParams({ htp: 351.393939751686889789 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_055.509494601600700000 * 1e18, + poolSize: 50_055.509494601600650000 * 1e18, pledgedCollateral: 60 * 1e18, encumberedCollateral: 7.072654775682389039 * 1e18, poolDebt: expectedDebt, @@ -390,7 +390,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowert0Np: 441.424038461538461742 * 1e18, borrowerCollateralization: 8.483377444958217435 * 1e18 }); - _assertLenderInterest(liquidityAdded, 55.509494601600700000 * 1e18); + _assertLenderInterest(liquidityAdded, 55.509494601600650000 * 1e18); skip(10 days); @@ -408,7 +408,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { PoolParams({ htp: 422.372244265211513602 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_086.111097974181150000 * 1e18, + poolSize: 50_086.111097974181050000 * 1e18, pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.084387664333398317 * 1e18, poolDebt: expectedDebt, @@ -428,7 +428,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowert0Np: 445.838278846153846359 * 1e18, borrowerCollateralization: 7.057773002983275247 * 1e18 }); - _assertLenderInterest(liquidityAdded, 86.111097974181150000 * 1e18); + _assertLenderInterest(liquidityAdded, 86.111097974181050000 * 1e18); skip(10 days); @@ -443,11 +443,11 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { PoolParams({ htp: 423.143052860217066081 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_119.833720983122550000 * 1e18, + poolSize: 50_119.833720983122450000 * 1e18, pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.097316323771045135 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421646075713482023 * 1e18, + actualUtilization: 0.421646075713482024 * 1e18, targetUtilization: 0.138842620340978204 * 1e18, minDebtAmount: 2_115.715264301085330404 * 1e18, loans: 1, @@ -463,7 +463,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowert0Np: 448.381722115384615591 * 1e18, borrowerCollateralization: 7.044916376706357984 * 1e18 }); - _assertLenderInterest(liquidityAdded, 119.833720983122550000 * 1e18); + _assertLenderInterest(liquidityAdded, 119.833720983122450000 * 1e18); skip(10 days); @@ -475,7 +475,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { PoolParams({ htp: 423.992567137945688846 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_157.001040562732750000 * 1e18, + poolSize: 50_157.001040562732600000 * 1e18, pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.111565102073903530 * 1e18, poolDebt: expectedDebt, @@ -495,7 +495,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowert0Np: 448.381722115384615591 * 1e18, borrowerCollateralization: 7.030801136225104190 * 1e18 }); - _assertLenderInterest(liquidityAdded, 157.001040562732750000 * 1e18); + _assertLenderInterest(liquidityAdded, 157.001040562732600000 * 1e18); skip(10 days); @@ -505,7 +505,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { PoolParams({ htp: 423.992567137945688846 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_157.001040562732750000 * 1e18, + poolSize: 50_157.001040562732600000 * 1e18, pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.127271800648583574 * 1e18, poolDebt: expectedDebt, diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol index 1d763ce64..87d19912b 100644 --- a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol @@ -127,7 +127,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { PoolParams({ htp: 420.980136462780058369 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 30_024.492338129690940000 * 1e18, + poolSize: 30_024.492338129690910000 * 1e18, pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.061038044473493202 * 1e18, poolDebt: 21_049.0068231390029184310 * 1e18, @@ -163,7 +163,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { PoolParams({ htp: 2_981.007422784467321393 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 30_024.492338129690940000 * 1e18, + poolSize: 30_024.492338129690910000 * 1e18, pledgedCollateral: 7.061038044473493202 * 1e18, encumberedCollateral: 7.061038044473493202 * 1e18, poolDebt: 21_049.0068231390029184310 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index 3628d1b5b..f3b82ddde 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; - import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; import 'src/libraries/helpers/PoolHelper.sol'; @@ -10,8 +8,6 @@ import 'src/interfaces/pool/erc20/IERC20Pool.sol'; contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { - using EnumerableSet for EnumerableSet.AddressSet; - address internal _borrower; address internal _borrower2; address internal _borrower3; @@ -129,7 +125,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 461.177183352194672960 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 110_064.287293030035100000 * 1e18, + poolSize: 110_064.287293030035050000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 15.470514425000097931 * 1e18, poolDebt: 46_117.718335219467295955 * 1e18, @@ -167,7 +163,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 110_064.287293030035100000 * 1e18, + poolSize: 110_064.287293030035050000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 0, poolDebt: 0, @@ -237,7 +233,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 0.664020422940018855 * 1e18, lup: 100.332368143282009890 * 1e18, - poolSize: 1_000.062818094677887000 * 1e18, + poolSize: 1_000.062818094677886000 * 1e18, pledgedCollateral: 1_500 * 1e18, encumberedCollateral: 9.927311124438159308 * 1e18, poolDebt: 996.030634410028283604 * 1e18, @@ -375,7 +371,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 1.002309975983935259 * 1e18, lup: _p1505_26, - poolSize: 10_000.000000011461690000 * 1e18, + poolSize: 10_000.000000011461360000 * 1e18, pledgedCollateral: 0.00001 * 1e18, encumberedCollateral: 0.000000006658700114 * 1e18, poolDebt: 0.000010023099759839 * 1e18, @@ -441,7 +437,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 0.001443490644739886 * 1e18, lup: _p1505_26, - poolSize: 10_012.269795822391370000 * 1e18, + poolSize: 10_012.269795822390450000 * 1e18, pledgedCollateral: 10_000.0 * 1e18, encumberedCollateral: 0.009589619533804370 * 1e18, poolDebt: 14.434906454054174087 * 1e18, @@ -800,7 +796,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 200.666923956635192629 * 1e18, lup: 327.188250324085203338 * 1e18, - poolSize: 40_022.159734498291800000 * 1e18, + poolSize: 40_022.159734498291760000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 58.236654596124865142 * 1e18, poolDebt: 19_054.349122034189453676 * 1e18, @@ -838,7 +834,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 200.941998518054562716 * 1e18, lup: 327.188250324085203338 * 1e18, - poolSize: 40_045.121523671487120000 * 1e18, + poolSize: 40_045.121523671487080000 * 1e18, pledgedCollateral: 50_100 * 1e18, encumberedCollateral: 58.353196900686720280 * 1e18, poolDebt: 19_092.480394752519489737 * 1e18, @@ -855,7 +851,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { debtColEma: 3_635_946.432113250913630238 * 1e18, lupt0DebtEma: 6_225_824.779082841691329479 * 1e18, debtEma: 19_054.349122034189453676 * 1e18, - depositEma: 40_022.159713346932256568 * 1e18 + depositEma: 40_022.159713346932216568 * 1e18 }); skip(1 days); @@ -870,11 +866,11 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 200.969526704670605713 * 1e18, lup: 327.188250324085203338 * 1e18, - poolSize: 40_047.420826509653040000 * 1e18, + poolSize: 40_047.420826509653000000 * 1e18, pledgedCollateral: 50_100 * 1e18, encumberedCollateral: 58.370797186283932430 * 1e18, poolDebt: 19_098.239001402275530018 * 1e18, - actualUtilization: 0.476604459561723991 * 1e18, + actualUtilization: 0.476604459561723992 * 1e18, targetUtilization: 0.584213148132606714 * 1e18, // big col. deposit barely affects minDebtAmount: 954.911950070113776501 * 1e18, loans: 2, @@ -887,7 +883,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { debtColEma: 3_637_620.071316321624676289 * 1e18, // big col. deposit barely affects lupt0DebtEma: 6_226_528.935447105141859984 * 1e18, debtEma: 19_082.947576572936979769 * 1e18, - depositEma: 40_039.381071090348403568 * 1e18 + depositEma: 40_039.381071090348363568 * 1e18 }); } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index fdc3046a6..1c71d21f7 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -212,8 +212,8 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_026.820859853884158000 * 1e18, - exchangeRate: 1.013410429926942079 * 1e18 + deposit: 2_026.820859853884156000 * 1e18, + exchangeRate: 1.013410429926942078 * 1e18 }); _assertBorrower({ borrower: _borrower, @@ -229,7 +229,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { amount: 1 * 1e18, amountAdded: 0.999876712328767123 * 1e18, index: _i9_52, - lpAward: 0.999873539225944870 * 1e18, + lpAward: 0.999873539225944871 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -237,11 +237,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_026.827291978286192000 * 1e18, - exchangeRate: 1.013413645989143096 * 1e18 + deposit: 2_026.827291978286188000 * 1e18, + exchangeRate: 1.013413645989143094 * 1e18 }); _assertReserveAuction({ - reserves: 24.540805142364516445 * 1e18, + reserves: 24.540805142364596539 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -282,32 +282,32 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { quoteTokenAmount: 14.503461444385064128 * 1e18, bondChange: 0.145034614443850641 * 1e18, isReward: true, - lpAwardTaker: 5.260347799326822081 * 1e18, - lpAwardKicker: 0.143114921550409462 * 1e18 + lpAwardTaker: 5.260347799326822092 * 1e18, + lpAwardKicker: 0.143114921550409463 * 1e18 }); _assertLenderLpBalance({ lender: _taker, index: _i9_91, - lpBalance: 5.260347799326822081 * 1e18, + lpBalance: 5.260347799326822092 * 1e18, depositTime: _startTime + 100 days + 6.5 hours }); _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.143114921550409462 * 1e18, // rewarded with LPs in bucket + lpBalance: 2_000.143114921550409463 * 1e18, // rewarded with LPs in bucket depositTime: _startTime + 100 days + 6.5 hours }); _assertBucket({ index: _i9_91, - lpBalance: 2_005.403462720877231543 * 1e18, + lpBalance: 2_005.403462720877231555 * 1e18, collateral: 2 * 1e18, - deposit: 2_012.468865148344978514 * 1e18, - exchangeRate: 1.013413645989143096 * 1e18 + deposit: 2_012.468865148344974514 * 1e18, + exchangeRate: 1.013413645989143094 * 1e18 }); // reserves should remain the same after arb take _assertReserveAuction({ - reserves: 25.925343323521870783 * 1e18, + reserves: 25.925343323521950877 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -419,11 +419,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 26_531.986011313779866429 * 1e18, collateral: 1.031812215971460994 * 1e18, - deposit: 24_978.836508020647022415 * 1e18, + deposit: 24_978.836508020647022416 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 26.111530884129144303 * 1e18, + reserves: 26.111530884129151302 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -615,7 +615,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i10016, lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LPs awarded for arb taker collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.836725452666849367 * 1e18, // quote token amount is diminished in arbed bucket + deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 }); _assertAuction( diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 2df199de0..490eea54c 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -229,7 +229,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { amount: 1 * 1e18, amountAdded: 0.999876712328767123 * 1e18, index: _i9_52, - lpAward: 0.999873468712229081 * 1e18, + lpAward: 0.999873468712229082 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -237,12 +237,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_000.006488054017914000 * 1e18, - exchangeRate: 1.000003244027008957 * 1e18 + deposit: 2_000.006488054017912000 * 1e18, + exchangeRate: 1.000003244027008956 * 1e18 }); _assertReserveAuction({ - reserves: 286.940599154163800221 * 1e18, - claimableReserves : 245.508462704973068079 * 1e18, + reserves: 286.940599154163873221 * 1e18, + claimableReserves : 245.508462704973141079 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -302,13 +302,13 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000.198343053438495848 * 1e18, collateral: 2 * 1e18, - deposit: 1_980.370462064014808094 * 1e18, - exchangeRate: 1.000003244027008957 * 1e18 + deposit: 1_980.370462064014806094 * 1e18, + exchangeRate: 1.000003244027008956 * 1e18 }); // reserves should remain the same after deposit take _assertReserveAuction({ - reserves: 288.353881050812077572 * 1e18, - claimableReserves : 247.012858322088119573 * 1e18, + reserves: 288.353881050812150572 * 1e18, + claimableReserves : 247.012858322088192573 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 92599c6cb..20b104ffc 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -179,7 +179,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 8.097846143253778448 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 73_093.873009488594553000 * 1e18, + poolSize: 73_093.873009488594546000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 835.035237319063220561 * 1e18, poolDebt: 8_117.624599705640061721 * 1e18, @@ -232,7 +232,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { locked: 0.195342779771472726 * 1e18 }); _assertReserveAuction({ - reserves: 24.501590217045508721 * 1e18, + reserves: 24.501590217045515721 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -719,7 +719,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 9.721295865031779605 * 1e18, - poolSize: 73_114.174951097528962000 * 1e18, + poolSize: 73_114.174951097528960000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.290450922889736704 * 1e18, poolDebt: 9_996.315708608352095626 * 1e18, @@ -740,7 +740,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { from: _lender1, amount: 1 * 1e18, index: _i9_91, - lpAward: 0.945987267750984916 * 1e18, + lpAward: 0.945987267750984917 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -748,7 +748,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 9.721295865031779605 * 1e18, - poolSize: 73_115.811578712752188477 * 1e18, + poolSize: 73_115.811578712752113362 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.364405977643667984 * 1e18, poolDebt: 9_997.034647576329686631 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol index a0f8142e6..9911c3d4c 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol @@ -1182,7 +1182,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { from: _lender1, amount: 40_000 * 1e18, index: 2500, - lpAward: 39_980.573029659913797564 * 1e18, + lpAward: 39_980.573029659913837526 * 1e18, newLup: 3_863.654368867279344664 * 1e18 }); @@ -1236,7 +1236,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 19_721.271266431727390766 * 1e18, + poolSize: 19_721.271266431727390767 * 1e18, pledgedCollateral: 3_978.965725315792902720 * 1e18, encumberedCollateral: 0, poolDebt: 0, @@ -1253,7 +1253,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender1, index: 2500, - lpBalance: 59_951.726875813759937564 * 1e18, + lpBalance: 59_951.726875813759977526 * 1e18, depositTime: _startTime + 80 hours }); // assert lender1 as a kicker diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol index 1a1f25584..6697806b0 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol @@ -213,8 +213,8 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { index: _i9_72, lpBalance: 8_007.361474606956967925 * 1e18, collateral: 0, - deposit: 8_008.357922841256883806 * 1e18, - exchangeRate: 1.000124441520151159 * 1e18 + deposit: 8_008.357922841256875798 * 1e18, + exchangeRate: 1.000124441520151158 * 1e18 }); _assertAuction( AuctionParams({ @@ -317,7 +317,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 7.989580407145861718 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_008.830844290339406730 * 1e18, + poolSize: 63_008.830844290339398722 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 821.863722498661263922 * 1e18, poolDebt: 7_989.580407145861717463 * 1e18, @@ -333,7 +333,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _removeLiquidity({ from: _lender, - amount: 8_008.370021911110262233 * 1e18, + amount: 8_008.370021911110254226 * 1e18, index: _i9_72, newLup: 9.624807173121239337 * 1e18, lpRedeem: 8_007.361474606956967925 * 1e18 @@ -418,7 +418,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 7.990503913730158191 * 1e18, lup: 9.529276179422528643 * 1e18, - poolSize: 8_001.214422208222843222 * 1e18, + poolSize: 8_001.214422208222835222 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 838.521600516187410670 * 1e18, poolDebt: 7_990.503913730158190391 * 1e18, @@ -450,11 +450,11 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 0.000000099836282890 * 1e18, - poolSize: 8_002.338364349478818466 * 1e18, + poolSize: 8_002.338364349478802466 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 81_714_595_700.439346767851204401 * 1e18, poolDebt: 8_158.081492591040321202 * 1e18, - actualUtilization: 0.998661389645009826 * 1e18, + actualUtilization: 0.998661389645009827 * 1e18, targetUtilization: 0.838521600515025555 * 1e18, minDebtAmount: 0, loans: 0, @@ -510,11 +510,11 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 0.000000099836282890 * 1e18, - poolSize: 8_002.705599795829418969 * 1e18, + poolSize: 8_002.705599795829394966 * 1e18, pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 82_124_923_660.837160770168974387 * 1e18, poolDebt: 8_199.047110922993196875 * 1e18, - actualUtilization: 1.007788860397780715 * 1e18, + actualUtilization: 1.007788860397780716 * 1e18, targetUtilization: 0.912833017390738664 * 1e18, minDebtAmount: 0, loans: 0, @@ -548,11 +548,11 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 469.658050535194609928 * 1e18, + poolSize: 470.466606572217578962 * 1e18, pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 1.007788860397780715 * 1e18, + actualUtilization: 1.007788860397780716 * 1e18, targetUtilization: 0.912833017390738664 * 1e18, minDebtAmount: 0, loans: 0, @@ -592,10 +592,10 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { // TODO: review, strange values _assertBucket({ index: _i9_52, - lpBalance: 7_920.016955825447655318 * 1e18, + lpBalance: 7_920.016955825447655160 * 1e18, collateral: 0, - deposit: 469.658050535194609792 * 1e18, - exchangeRate: 0.059300131951074271 * 1e18 + deposit: 470.466606572217578831 * 1e18, + exchangeRate: 0.059402222141225726 * 1e18 }); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index cbfc7a87c..562b44c81 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -198,8 +198,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.174951097528962000 * 1e18, - exchangeRate: 1.057087475548764481 * 1e18 + deposit: 2_114.174951097528960000 * 1e18, + exchangeRate: 1.057087475548764480 * 1e18 }); _assertBucket({ index: _i9_81, @@ -288,15 +288,15 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998856000 * 1e18, - exchangeRate: 1.057155041798499428 * 1e18 + deposit: 2_114.310083596998850000 * 1e18, + exchangeRate: 1.057155041798499425 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, collateral: 0, - deposit: 5_000.319586842611445000 * 1e18, - exchangeRate: 1.000063917368522289 * 1e18 + deposit: 5_000.319586842611440000 * 1e18, + exchangeRate: 1.000063917368522288 * 1e18 }); _assertBucket({ index: _i9_72, @@ -327,8 +327,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998856000 * 1e18, - exchangeRate: 1.057155041798499428 * 1e18 + deposit: 2_114.310083596998850000 * 1e18, + exchangeRate: 1.057155041798499425 * 1e18 }); _settle({ @@ -379,8 +379,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_807.241482463513273501 * 1e18, - exchangeRate: 0.800658316587592116 * 1e18 + deposit: 8_807.556879218687261409 * 1e18, + exchangeRate: 0.800686989019880660 * 1e18 }); _assertBucket({ index: _i9_62, @@ -393,7 +393,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { PoolParams({ htp: 9.771304290202671377 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_807.241482463513273501 * 1e18, + poolSize: 63_807.556879218687261409 * 1e18, pledgedCollateral: 2 * 1e18, encumberedCollateral: 2.010288427770370775 * 1e18, poolDebt: 19.542608580405342754 * 1e18, @@ -480,8 +480,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.174951097528962000 * 1e18, - exchangeRate: 1.057087475548764481 * 1e18 + deposit: 2_114.174951097528960000 * 1e18, + exchangeRate: 1.057087475548764480 * 1e18 }); _assertBucket({ index: _i9_81, @@ -566,23 +566,23 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { _assertBucket({ index: _i9_91, lpBalance: 2_000 * 1e18, - collateral: 213.282459829062995573 * 1e18, + collateral: 213.282459829062995170 * 1e18, deposit: 0, - exchangeRate: 1.057580788993756155 * 1e18 + exchangeRate: 1.057580788993756153 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, - collateral: 509.467337074016313187 * 1e18, + collateral: 509.467337074016312678 * 1e18, deposit: 0, - exchangeRate: 1.000466672301396420 * 1e18 + exchangeRate: 1.000466672301396419 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, - collateral: 277.250203096920691240 * 1e18, - deposit: 8_289.317326783984381999 * 1e18, - exchangeRate: 0.998595325429936418 * 1e18 + collateral: 277.250203096920692152 * 1e18, + deposit: 8_290.291604705064327151 * 1e18, + exchangeRate: 0.998683896150034595 * 1e18 }); _assertBucket({ index: _i9_62, @@ -719,14 +719,14 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { from: _lender1, amount: 100 * 1e18, index: _i9_91, - lpAward: 94.593504307441637520 * 1e18, + lpAward: 94.593504307441637789 * 1e18, newLup: 9.721295865031779605 * 1e18 }); _assertLenderLpBalance({ lender: _lender1, index: _i9_91, - lpBalance: 94.593504307441637520 * 1e18, + lpBalance: 94.593504307441637789 * 1e18, depositTime: _startTime + 100 days + 10 hours }); @@ -867,12 +867,10 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0 * 1e18, - deposit: 9_036.356565003188009087 * 1e18, - exchangeRate: 0.821486960454835274 * 1e18 + deposit: 9_036.877948541081298779 * 1e18, + exchangeRate: 0.821534358958280118 * 1e18 }); - vm.expectEmit(true, true, false, true); - emit BucketBankruptcy(_i9_72, 5981); _pool.moveQuoteToken(10000000000 * 1e18, _i9_72, _i9_91, type(uint256).max); _assertBucket({ @@ -887,27 +885,45 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { - function testSettleAndBankruptcyOnHPBWithTinyDeposit() external { - address actor1 = makeAddr("actor1"); + address internal actor1; + address internal actor2; + address internal actor3; + address internal actor4; + address internal actor6; + address internal actor7; + address internal actor8; + + function setUp() external { + actor1 = makeAddr("actor1"); _mintQuoteAndApproveTokens(actor1, type(uint256).max); _mintCollateralAndApproveTokens(actor1, type(uint256).max); - address actor2 = makeAddr("actor2"); - _mintQuoteAndApproveTokens(actor2, type(uint256).max); + actor2 = makeAddr("actor2"); + _mintQuoteAndApproveTokens(actor2, type(uint256).max); _mintCollateralAndApproveTokens(actor2, type(uint256).max); - address actor3 = makeAddr("actor1"); + actor3 = makeAddr("actor3"); _mintQuoteAndApproveTokens(actor3, type(uint256).max); _mintCollateralAndApproveTokens(actor3, type(uint256).max); - address actor6 = makeAddr("actor6"); + actor4 = makeAddr("actor4"); + _mintQuoteAndApproveTokens(actor4, type(uint256).max); + _mintCollateralAndApproveTokens(actor4, type(uint256).max); + + actor6 = makeAddr("actor6"); _mintQuoteAndApproveTokens(actor6, type(uint256).max); _mintCollateralAndApproveTokens(actor6, type(uint256).max); - address actor8 = makeAddr("actor8"); + actor7 = makeAddr("actor7"); + _mintQuoteAndApproveTokens(actor7, type(uint256).max); + _mintCollateralAndApproveTokens(actor7, type(uint256).max); + + actor8 = makeAddr("actor8"); _mintQuoteAndApproveTokens(actor8, type(uint256).max); _mintCollateralAndApproveTokens(actor8, type(uint256).max); + } + function testSettleAndBankruptcyOnHPBWithTinyDeposit() external { changePrank(actor6); _pool.addQuoteToken(2_000_000 * 1e18, 2572, block.timestamp + 100); skip(100 days); @@ -948,7 +964,88 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { assertEq(collateral, 0); // no collateral added in bucket 2571 assertEq(deposit, 0); // entire deposit from bucket 2571 used to settle (borrowerDebt, borrowerCollateral, ) = _pool.borrowerInfo(actor1); - assertEq(borrowerDebt, 987909.179343464530923021 * 1e18); // decreased with 2 + assertEq(borrowerDebt, 987909.179343464530923022 * 1e18); // decreased with 1 assertEq(borrowerCollateral, 10066231386838.450530455239517417 * 1e18); // same as before settle } + + function testSettleWithReserves() external tearDown { + changePrank(actor2); + + ERC20Pool(address(_pool)).updateInterest(); + _addInitialLiquidity({ + from: actor2, + amount: 112_807_891_516.8015826259279868 * 1e18, + index: 2572 + }); + + ERC20Pool(address(_pool)).updateInterest(); + _drawDebtNoLupCheck({ + from: actor2, + borrower: actor2, + amountToBorrow: 56_403_945_758.4007913129639934 * 1e18, + limitIndex: 7388, + collateralToPledge: 21_009_851.171858165566322122 * 1e18 + }); + + // skip some time to make actor2 undercollateralized + skip(200 days); + + changePrank(actor4); + + ERC20Pool(address(_pool)).updateInterest(); + _kick({ + from: actor4, + borrower: actor2, + debt: 58_679_160_247.182050965227801655 * 1e18, + collateral: 21_009_851.171858165566322122 * 1e18, + bond: 580_263_636.560514719062821277 * 1e18, + transferAmount: 580_263_636.560514719062821277 * 1e18 + }); + + changePrank(actor1); + + ERC20Pool(address(_pool)).updateInterest(); + _startClaimableReserveAuction({ + from: actor1, + remainingReserves: 1_962_000_500.669895903463292555 * 1e18, + price: 1000000000 * 1e18, + epoch: 1 + }); + + changePrank(actor7); + + ERC20Pool(address(_pool)).updateInterest(); + _drawDebtNoLupCheck({ + from: actor7, + borrower: actor7, + amountToBorrow: 1_000_000 * 1e18, + limitIndex: 7388, + collateralToPledge: 372.489032271806320214 * 1e18 + }); + + // skip some time to make actor7 undercollateralized + skip(200 days); + + changePrank(actor6); + + ERC20Pool(address(_pool)).updateInterest(); + + (uint256 borrowerDebt, , ) = _poolUtils.borrowerInfo(address(_pool), actor2); + assertEq(borrowerDebt, 60_144_029_463.415046012797744619 * 1e18); + + (uint256 reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); + assertEq(reserves, 1_758_290_868.502349679615580158 * 1e18); + + // settle auction with reserves + _settle({ + from: actor6, + borrower: actor2, + maxDepth: 2, + settledDebt: 57_093_334_850.248360626382636508 * 1e18 + }); + + (reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); + + assertEq(reserves, 2); + } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index eb15b4d10..956f1fc75 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -297,8 +297,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188293467 * 1e18, - claimableReserves : 83.959896655448318900 * 1e18, + reserves: 179.552281242188305467 * 1e18, + claimableReserves : 83.959896655448330900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -310,7 +310,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806620000 * 1e18, + poolSize: 83_219.674636105806608000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.791200431324241706 * 1e18, poolDebt: 19_119.759164133922414841 * 1e18, @@ -388,7 +388,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281748675 * 1e18, + poolSize: 83_220.780619576281653638 * 1e18, pledgedCollateral: 1_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, @@ -438,7 +438,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281748675 * 1e18, + poolSize: 83_220.780619576281653638 * 1e18, pledgedCollateral: 2_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, @@ -574,8 +574,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188293467 * 1e18, - claimableReserves : 83.959896655448318900 * 1e18, + reserves: 179.552281242188305467 * 1e18, + claimableReserves : 83.959896655448330900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -587,7 +587,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806620000 * 1e18, + poolSize: 83_219.674636105806608000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -782,8 +782,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188293467 * 1e18, - claimableReserves : 83.959896655448318900 * 1e18, + reserves: 179.552281242188305467 * 1e18, + claimableReserves : 83.959896655448330900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -795,7 +795,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806620000 * 1e18, + poolSize: 83_219.674636105806608000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -1170,8 +1170,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188293467 * 1e18, - claimableReserves : 83.959896655448318900 * 1e18, + reserves: 179.552281242188305467 * 1e18, + claimableReserves : 83.959896655448330900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1183,7 +1183,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806620000 * 1e18, + poolSize: 83_219.674636105806608000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -1316,8 +1316,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.974413448899967463 * 1e18 }); _assertReserveAuction({ - reserves: 152.670996883580226810 * 1e18, - claimableReserves : 102.690517143674680866 * 1e18, + reserves: 152.670996883580228810 * 1e18, + claimableReserves : 102.690517143674682866 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1461,8 +1461,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581141072 * 1e18, - claimableReserves : 797.714616029939969106 * 1e18, + reserves: 851.124981254581150187 * 1e18, + claimableReserves : 797.714616029939978221 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1509,8 +1509,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581140719 * 1e18, - claimableReserves : 800.882859687599445518 * 1e18, + reserves: 851.124981254581149834 * 1e18, + claimableReserves : 800.882859687599454633 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1530,8 +1530,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: 3_696, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998856000 * 1e18, - exchangeRate: 1.057155041798499428 * 1e18 + deposit: 2_114.310083596998850000 * 1e18, + exchangeRate: 1.057155041798499425 * 1e18 }); _settle({ @@ -1599,8 +1599,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_936.356565003188009086 * 1e18, - exchangeRate: 0.812396051363926183 * 1e18 + deposit: 8_936.865619773958011125 * 1e18, + exchangeRate: 0.812442329070359819 * 1e18 }); _assertLenderLpBalance({ lender: _lender, @@ -1638,8 +1638,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { vm.revertTo(postTakeSnapshot); _assertReserveAuction({ - reserves: 851.124981254581140719 * 1e18, - claimableReserves : 800.882859687599445518 * 1e18, + reserves: 851.124981254581149834 * 1e18, + claimableReserves : 800.882859687599454633 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1649,10 +1649,10 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 0, - settledDebt: 839 * 1e18 + settledDebt: 839.502103169454551025 * 1e18 }); _assertReserveAuction({ - reserves: 0.509054770770003925 * 1e18, + reserves: 0, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -1664,7 +1664,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 1, - settledDebt: 2_085.437275399572357948 * 1e18 + settledDebt: 2_085.437275399572352030 * 1e18 }); _assertAuction( @@ -1678,14 +1678,14 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 105.065056948053351817 * 1e18, auctionPrice: 0.653111452826113536 * 1e18, - debtInAuction: 7_063.963021839423435914 * 1e18, + debtInAuction: 7_063.453967068653428874 * 1e18, thresholdPrice: 0, neutralPrice: 10.449783245217816340 * 1e18 }) ); _assertBorrower({ borrower: _borrower2, - borrowerDebt: 7_063.963021839423435914 * 1e18, + borrowerDebt: 7_063.453967068653428874 * 1e18, borrowerCollateral: 0, borrowert0Np: 10.307611531622595991 * 1e18, borrowerCollateralization: 0 @@ -1701,7 +1701,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 5, - settledDebt: 6_967.498245444704988974 * 1e18 + settledDebt: 6_966.996142275250443867 * 1e18 }); _assertAuction( @@ -1798,8 +1798,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { locked: 98.533942419792216457 * 1e18 }); _assertReserveAuction({ - reserves: 152.670996883580226810 * 1e18, - claimableReserves : 102.690517143674680866 * 1e18, + reserves: 152.670996883580228810 * 1e18, + claimableReserves : 102.690517143674682866 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 diff --git a/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol b/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol index 3ea87966d..06443987e 100644 --- a/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol @@ -236,7 +236,7 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { // bidder purchases all quote from the highest bucket uint256 amountToPurchase = 10_100 * 1e18; assertGt(_quote.balanceOf(address(_pool)), amountToPurchase); - uint256 amountWithInterest = 10_001.282942146414270000 * 1e18; + uint256 amountWithInterest = 10_001.282942146414260000 * 1e18; // adding extra collateral to account for interest accumulation uint256 collateralToPurchaseWith = Maths.wmul(Maths.wdiv(amountToPurchase, p2550), 1.01 * 1e18); assertEq(collateralToPurchaseWith, 3.388032491631335842 * 1e18); @@ -256,17 +256,17 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { amount: amountWithInterest, index: 2550, newLup: _priceAt(2552), - lpRedeem: 10_000.353515519837858817 * 1e18 + lpRedeem: 10_000.353515519837848818 * 1e18 }); // bidder withdraws unused collateral - uint256 expectedCollateral = 0.066448947605530164 * 1e18; + uint256 expectedCollateral = 0.066448947605530167 * 1e18; _removeAllCollateral({ from: _bidder, amount: expectedCollateral, index: 2550, - lpRedeem: 200.052013519410414906 * 1e18 + lpRedeem: 200.052013519410424905 * 1e18 }); _assertLenderLpBalance({ @@ -298,13 +298,13 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { skip(3600); // lender1 exchanges their LP for collateral - expectedCollateral = 1.328633417610322272 * 1e18; + expectedCollateral = 1.328633417610322269 * 1e18; _removeAllCollateral({ from: _lender1, amount: expectedCollateral, index: 2550, - lpRedeem: 4_000 * 1e18 + lpRedeem: 3_999.999999999999999063 * 1e18 }); _assertLenderLpBalance({ diff --git a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol index a75277d0e..20622c36e 100644 --- a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -1143,7 +1143,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { amountMoved: 2_499.657534246575342500 * 1e18, fromIndex: lupIndex, toIndex: 2954, - lpRedeemFrom: 2_499.902874075010984820 * 1e18, + lpRedeemFrom: 2_499.902874075010987320 * 1e18, lpAwardTo: 2_499.657534246575342500 * 1e18, newLup: _lup() }); @@ -1155,7 +1155,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { from: _lender1, amount: 1_000 * 1e18, index: 2873, - lpAward: 999.958129650486585454 * 1e18, + lpAward: 999.958129650486586454 * 1e18, newLup: 601.252968524772188572 * 1e18 }); @@ -1168,7 +1168,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { amountMoved: 2_499.691780821917807500 * 1e18, fromIndex: lupIndex, toIndex: 2954, - lpRedeemFrom: 2_499.815331532038893923 * 1e18, + lpRedeemFrom: 2_499.815331532038898922 * 1e18, lpAwardTo: 2_499.691780821917807500 * 1e18, newLup: _lup() }); @@ -1180,7 +1180,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { from: _lender1, amount: 9_000 * 1e18, index: 2873, - lpAward: 8_994.177960110671641166 * 1e18, + lpAward: 8_994.177960110671668131 * 1e18, newLup: 601.252968524772188572 * 1e18 }); @@ -1189,10 +1189,10 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { _removeAllLiquidity({ from: _lender, - amount: 5_003.525624026555367687 * 1e18, + amount: 5_003.525624026555345183 * 1e18, index: 2873, newLup: 601.252968524772188572 * 1e18, - lpRedeem: 5_000.281794392950121257 * 1e18 + lpRedeem: 5_000.281794392950113758 * 1e18 }); _removeAllLiquidity({ diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 447577b2c..2ee0b09ee 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -75,6 +75,7 @@ contract BasicInvariants is InvariantsTestBase { _basicPoolHandler = new BasicPoolHandler( address(_pool), + address(_ajna), address(_quote), address(_collateral), address(_poolInfo), @@ -84,6 +85,7 @@ contract BasicInvariants is InvariantsTestBase { _handler = address(_basicPoolHandler); + excludeContract(address(_ajna)); excludeContract(address(_collateral)); excludeContract(address(_quote)); excludeContract(address(_poolFactory)); @@ -171,8 +173,16 @@ contract BasicInvariants is InvariantsTestBase { , ) = _pool.reservesInfo(); - assertGe( - poolBalance + poolDebt, totalBondEscrowed + _pool.depositSize() + unClaimed, + uint256 assets = poolBalance + poolDebt; + uint256 liabilities = totalBondEscrowed + _pool.depositSize() + unClaimed; + + console.log("assets -> ", assets); + console.log("liabilities -> ", liabilities); + + greaterThanWithinDiff( + assets, + liabilities, + 1e13, "Incorrect pool quote token" ); } diff --git a/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol b/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol index e1e9d91f9..a747c7e69 100644 --- a/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol @@ -38,6 +38,7 @@ contract LiquidationInvariants is BasicInvariants { _liquidationPoolHandler = new LiquidationPoolHandler( address(_pool), + address(_ajna), address(_quote), address(_collateral), address(_poolInfo), diff --git a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol index 0de797492..df6f3fafc 100644 --- a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol @@ -46,6 +46,7 @@ contract ReserveInvariants is LiquidationInvariants { _reservePoolHandler = new ReservePoolHandler( address(_pool), + address(_ajna), address(_quote), address(_collateral), address(_poolInfo), diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index ee76a624c..5649780d4 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -16,7 +16,7 @@ import { MIN_PRICE } from 'src/libraries/helpers/PoolHelper.sol'; -import { Token } from '../../../utils/Tokens.sol'; +import { Token, BurnableToken } from '../../../utils/Tokens.sol'; import 'src/libraries/internal/Maths.sol'; import '../interfaces/ITestBase.sol'; @@ -33,6 +33,8 @@ abstract contract BaseHandler is Test { Token internal _quote; Token internal _collateral; + BurnableToken internal _ajna; + // Pool ERC20Pool internal _pool; PoolInfoUtils internal _poolInfo; @@ -68,6 +70,7 @@ abstract contract BaseHandler is Test { constructor( address pool_, + address ajna_, address quote_, address collateral_, address poolInfo_, @@ -75,6 +78,7 @@ abstract contract BaseHandler is Test { address testContract_ ) { // Tokens + _ajna = BurnableToken(ajna_); _quote = Token(quote_); _collateral = Token(collateral_); @@ -292,7 +296,7 @@ abstract contract BaseHandler is Test { Maths.wmul(pendingFactor - Maths.WAD, poolDebt) ); - uint256 scale = Maths.wdiv(newInterest, depositAboveHtp) + Maths.WAD; + uint256 scale = (newInterest * 1e18) / depositAboveHtp + Maths.WAD; // simulate scale being applied to all deposits above HTP _fenwickMult(htpIndex, scale); @@ -415,4 +419,4 @@ abstract contract BaseHandler is Test { if (max_ == type(uint256).max && x_ != 0) result_++; } -} +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol index f4dea6090..7ae7e4c60 100644 --- a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol +++ b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol @@ -8,17 +8,16 @@ import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { Token } from '../../../utils/Tokens.sol'; +import { Token, BurnableToken } from '../../../utils/Tokens.sol'; import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol'; abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { - // Mainnet ajna address - address internal _ajna = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079; - Token internal _quote; Token internal _collateral; + BurnableToken internal _ajna; + ERC20Pool internal _pool; ERC20Pool internal _impl; PoolInfoUtils internal _poolInfo; @@ -35,11 +34,12 @@ abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { function setUp() public virtual { // Tokens + _ajna = new BurnableToken("Ajna", "A"); _quote = new Token("Quote", "Q"); _collateral = new Token("Collateral", "C"); // Pool - _poolFactory = new ERC20PoolFactory(_ajna); + _poolFactory = new ERC20PoolFactory(address(_ajna)); _pool = ERC20Pool(_poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18)); _poolInfo = new PoolInfoUtils(); _impl = _poolFactory.implementation(); diff --git a/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol b/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol index 0edcb4584..66e824e78 100644 --- a/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol +++ b/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol @@ -73,4 +73,8 @@ abstract contract InvariantsTestHelpers { require(getDiff(x, y) <= expectedDiff, err); } + function greaterThanWithinDiff(uint256 x, uint256 y, uint256 expectedDiff, string memory err) internal pure { + require(x > y || getDiff(x, y) <= expectedDiff, err); + } + } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol index ffd12ecb3..40fabf53d 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -82,14 +82,25 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { ) internal useTimestamps updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.takeAuction']++; - (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - uint256 totalBondBeforeTake = _getKickerBond(kicker); + (uint256 borrowerDebtBeforeTake, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + uint256 totalBondBeforeTake = _getKickerBond(kicker); + uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)); try _pool.take(borrower_, amount_, taker_, bytes("")) { - uint256 totalBondAfterTake = _getKickerBond(kicker); + (uint256 borrowerDebtAfterTake, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); + uint256 totalBondAfterTake = _getKickerBond(kicker); + uint256 totalBalanceAfterTake = _quote.balanceOf(address(_pool)); + + if (borrowerDebtBeforeTake > borrowerDebtAfterTake) { + // **RE7**: Reserves decrease with debt covered by take. + decreaseInReserves += borrowerDebtBeforeTake - borrowerDebtAfterTake; + } else { + // **RE7**: Reserves increase by take penalty on first take. + increaseInReserves += borrowerDebtAfterTake - borrowerDebtBeforeTake; + } if (totalBondBeforeTake > totalBondAfterTake) { // **RE7**: Reserves increase by bond penalty on take. @@ -99,7 +110,18 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { decreaseInReserves += totalBondAfterTake - totalBondBeforeTake; } - _updateCurrentTakeState(borrower_, borrowerDebt); + // **RE7**: Reserves increase with the quote token paid by taker. + increaseInReserves += totalBalanceAfterTake - totalBalanceBeforeTake; + + if (!alreadyTaken[borrower_]) { + alreadyTaken[borrower_] = true; + + firstTake = true; + + } else firstTake = false; + + // reset taken flag in case auction was settled by take action + _auctionSettleStateReset(borrower_); } catch (bytes memory err) { _ensurePoolError(err); @@ -199,16 +221,19 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } else collateral = 0; 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)); - // **RE12**: Reserves decrease by amount of reserve used to settle a auction - decreaseInReserves = reservesBeforeAction - reservesAfterAction; - + 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) { diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol index bc82ff0c9..81d7c7034 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.14; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + import { BaseHandler } from './BaseHandler.sol'; abstract contract UnboundedReservePoolHandler is BaseHandler { @@ -30,9 +32,10 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { function _takeReserves( uint256 amount_ ) internal useTimestamps updateLocalStateAndPoolInterest { - try _pool.takeReserves(amount_) returns (uint256 takenAmount_) { + deal(address(_ajna), _actor, type(uint256).max); + IERC20(address(_ajna)).approve(address(_pool), type(uint256).max); - decreaseInReserves += takenAmount_; + try _pool.takeReserves(amount_) { } catch (bytes memory err) { _ensurePoolError(err); diff --git a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol index e335aa854..88440524e 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol @@ -23,12 +23,13 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { constructor( address pool_, + address ajna_, address quote_, address collateral_, address poolInfo_, uint256 numOfActors_, address testContract_ - ) BaseHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + ) BaseHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { } @@ -257,7 +258,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - boundedLps_ = constrictToRange(lpsToTransfer_, 1, senderLpBalance); + boundedLps_ = constrictToRange(lpsToTransfer_, 0, senderLpBalance); receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; } diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol index e70f89a77..f60262604 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol @@ -16,12 +16,13 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan constructor( address pool_, + address ajna_, address quote_, address collateral_, address poolInfo_, uint256 numOfActors_, address testContract_ - ) BasicPoolHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + ) BasicPoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { } @@ -164,4 +165,4 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan // skip some time for more interest vm.warp(block.timestamp + 2 hours); } -} +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol index 13795f544..e649cea6c 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol @@ -2,6 +2,8 @@ pragma solidity 0.8.14; +import 'src/libraries/internal/Maths.sol'; + import { UnboundedReservePoolHandler } from '../base/UnboundedReservePoolHandler.sol'; import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; @@ -10,12 +12,13 @@ contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandl constructor( address pool_, + address ajna_, address quote_, address collateral_, address poolInfo_, uint256 numOfActors_, address testContract_ - ) LiquidationPoolHandler(pool_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + ) LiquidationPoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { } @@ -47,12 +50,15 @@ contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandl function _preTakeReserves( uint256 amountToTake_ - ) internal returns (uint256 boundedAmount_) { + ) internal useTimestamps returns (uint256 boundedAmount_) { (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); if (claimableReservesRemaining == 0) _startClaimableReserveAuction(); + // skip enough time for auction price to decrease + skip(24 hours); + (, , claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - boundedAmount_ = constrictToRange(amountToTake_, 0, claimableReservesRemaining); + boundedAmount_ = constrictToRange(amountToTake_, 0, Maths.min(100_000 * 1e18, claimableReservesRemaining)); } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 570fbafa0..64078f259 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -120,6 +120,15 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } + function test_regression_quote_token_2() external { + _liquidationPoolHandler.kickAuction(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationPoolHandler.kickAuction(416882035302092397436677640325827, 7379, 253058086367250264569525665396366); + _liquidationPoolHandler.kickAuction(95740057146806695735694068330212313517380414204596464841344800376300745, 15462030827034, 17811087070659573835739283446817); + _liquidationPoolHandler.drawDebt(91685640224888183606335500279, 3284161781338443742266950748717011); + _liquidationPoolHandler.settleAuction(366366807138151363686, 2, 39227118695514892784493088788799944161631371060); + + invariant_quoteTokenBalance_QT1(); + } function test_regression_invariant_settle_F1_1() external { _liquidationPoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980); _liquidationPoolHandler.kickAuction(5135, 1752, 6350); diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index ab49e5d7f..533989bc4 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -179,6 +179,149 @@ contract RegressionTestReserve is ReserveInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_1() external { + _reservePoolHandler.kickAuction(22771, 1111716442170237736883602263032, 7068); + _reservePoolHandler.addCollateral(450013003559446434159001584489461823249847174057443177111241841181931, 312804075096415570730723645176181753809227168111076176815108, 0); + _reservePoolHandler.pledgeCollateral(1985831902099838153679635097394320832859625435, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reservePoolHandler.takeAuction(159178586166894, 2, 2); + _reservePoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653); + _reservePoolHandler.startClaimableReserveAuction(2162); + _reservePoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524); + _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_2() external { + _reservePoolHandler.drawDebt(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reservePoolHandler.pullCollateral(8213783947977569843117913236674123519747026, 26007879196259510050186964175498569516185804333067186877); + _reservePoolHandler.drawDebt(2301679051848045604, 2599238865); + _reservePoolHandler.addCollateral(4242066606167690018840733069974159, 2308657525655903223461843364795, 65478701235782653506998474972558); + _reservePoolHandler.kickAuction(69087967303211947138147234149237227681311399268590256122007, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 61509477439); + _reservePoolHandler.bucketTake(72107205250762587233492136850, 1244277915808615586782916545843, false, 39013151190969055659579687996); + _reservePoolHandler.transferLps(235427298074932216827475360756961, 2730975142229662626738653393718571, 1801094436838792863068211758488417, 879376648610435813515943108046); + _reservePoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208); + _reservePoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765); + _reservePoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383); + _reservePoolHandler.startClaimableReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589); + _reservePoolHandler.drawDebt(3, 2); + _reservePoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855); + _reservePoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758); + _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reservePoolHandler.startClaimableReserveAuction(0); + _reservePoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_3() external { + _reservePoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442); + _reservePoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189); + _reservePoolHandler.startClaimableReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); + _reservePoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804); + _reservePoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067); + _reservePoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2); + _reservePoolHandler.settleAuction(2475430586786710276861336070835, 2600907908657087816392951766665339, 618867463233346276220185869); + _reservePoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417); + _reservePoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615); + _reservePoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417); + _reservePoolHandler.startClaimableReserveAuction(1); + _reservePoolHandler.bucketTake(3, 1, true, 2); + _reservePoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893); + _reservePoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reservePoolHandler.pullCollateral(2, 2); + _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 60110048782249025340, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_4() external { + _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.repayDebt(123785744463475277851, 431477); + _reservePoolHandler.transferLps(8349868629210939854344368826901611192, 2050523511941068426657597285533, 482178822629563486190079445656644, 113294184847064316812952522804); + _reservePoolHandler.kickWithDeposit(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + _reservePoolHandler.settleAuction(2, 60232917818899277216367937385395389606, 109871490879953029603376159938904259489696033217506136); + _reservePoolHandler.repayDebt(11000946587948121111587595267746251370302202324589596297423219199459160, 1640564753028103680512592653747); + _reservePoolHandler.kickAuction(3981871706795545560915874060150150667177950440617972926122855684987, 198277768150818655020367, 2892877132676919180494078569276042); + _reservePoolHandler.addCollateral(1263277608, 63278488014355910828533249093658068159654702008400, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.pullCollateral(2673207612857671157084473752324442, 2000121050152966887141053752381); + _reservePoolHandler.removeCollateral(17512256671104333742254942029, 940622488995047370832475, 17490); + _reservePoolHandler.takeReserves(4664936529054748613171449032640911546982046023628226142220220474, 12228144613454452340256380805978754348438442703119); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_5() external { + _reservePoolHandler.settleAuction(841361270493647884419014561906636, 98291268956781519518581599501066994252857442823583923678216713962377882453983, 1406581758883); + _reservePoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0); + _reservePoolHandler.repayDebt(46968019084877, 3); + _reservePoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3); + // _reservePoolHandler.bucketTake(17377, 2748873005452892812548622619587, false, 999999999999999989712357375741033502535274466); + skip(2 hours); + _pool.updateInterest(); + /* + TODO: Check why deposit change is more than debt change in accrue interest in "updateInterest" + debt change --> 236352821760996207141053 + deposit change --> 236352821761181451576056 + */ + currentTimestamp = block.timestamp; + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_settle_3() external { + _reservePoolHandler.bucketTake(38522325070060518315904717784000000000, 74804166371079302281493396778, false, 243284095655821418741726406906); + _reservePoolHandler.removeQuoteToken(63300517263709739718213296806, 544282601310994378458621785271097, 93004761485750531023207874); + _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 10850580031398165201080403693039642, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reservePoolHandler.takeAuction(1006654503300439100037731502194, 999999999999999820916638470184939411687495097, 2999999999999999849116243910762621146260836956); + _reservePoolHandler.settleAuction(513358560825207984200760701, 527826952804937875408570995575150, 3075); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_reserves_settle_4() external { + _reservePoolHandler.kickAuction(999999999999999886611844846637902655009191722, 809319421722186623206028334686443, 33424777291596678039713); + _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 2503088493515274266); + _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 16755); + _reservePoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 2); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_1() external { + _reservePoolHandler.drawDebt(3, 2472487412192096145519673462983934503); + _reservePoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 50482403089838632034016548451617756782); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_2() external { + _reservePoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.takeReserves(9990, 2); + _reservePoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 32167191465467724730024789812); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_3() external { + _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 7863893832813740178393566165935290555711); + _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 672940003103495713632014456312899612181893075117989217767500902); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_4() external { + _reservePoolHandler.bucketTake(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.takeReserves(2000008144440715646777241504589, 695559613732339828463793224249); + _reservePoolHandler.takeAuction(5260, 3000000000000000000000010654836333921317470662, 6571232818648673809695471386); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + function test_regression_invariant_repayDebt_F2_1() external { _reservePoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934); _reservePoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045); diff --git a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol index 5712ea996..285b0cf8c 100644 --- a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol @@ -274,7 +274,7 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { PoolParams({ htp: 502.333658244714424687 * 1e18, lup: _priceAt(2550), - poolSize: 30_003.498905447098710000 * 1e18, + poolSize: 30_003.498905447098680000 * 1e18, pledgedCollateral: Maths.wad(3), encumberedCollateral: 0.500516446164039921 * 1e18, poolDebt: 1507.000974734143274062 * 1e18, @@ -293,8 +293,8 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { index: 2550, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 10_001.166301815699570000 * 1e18, - exchangeRate: 1.000116630181569957 * 1e18 + deposit: 10_001.166301815699560000 * 1e18, + exchangeRate: 1.000116630181569956 * 1e18 }); // check borrower info after partial repay _assertBorrower({ @@ -349,7 +349,7 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 30_005.083883677219620000 * 1e18, + poolSize: 30_005.083883677219590000 * 1e18, pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, @@ -366,15 +366,15 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { debtColEma: 755_981.012555885345015825 * 1e18, lupt0DebtEma: 4_531_205.827599034360703833 * 1e18, debtEma: 1_507.000974734143274062 * 1e18, - depositEma: 30_003.498902092092555534 * 1e18 + depositEma: 30_003.498902092092525534 * 1e18 }); // check bucket state after fully repay _assertBucket({ index: 2550, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 10_001.694627892406540000 * 1e18, - exchangeRate: 1.000169462789240654 * 1e18 + deposit: 10_001.694627892406530000 * 1e18, + exchangeRate: 1.000169462789240653 * 1e18 }); // check borrower info after fully repay _assertBorrower({ diff --git a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol index 079483d25..f26eb1cba 100644 --- a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol @@ -179,34 +179,34 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { rate: 0.05 * 1e18 }); (, , , uint256 depositEma) = _pool.emasInfo(); - assertEq(depositEma, 12_582.630574533185450994 * 1e18); // now moving toward 20_000 + assertEq(depositEma, 12_582.630574533185444544 * 1e18); // now moving toward 20_000 _skipAndAccrue({ time: 20 hours, // 24 hours since liquidity was added - mau: 0.393730664447534870 * 1e18, // still dropping toward 35% + mau: 0.393730664447534871 * 1e18, // still dropping toward 35% tu: 0.769999034610545182 * 1e18, // still at 77% rate: 0.045 * 1e18 // first interest rate drop }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 17_664.401438069534341122 * 1e18); // still moving toward 20_000 + assertEq(depositEma, 17_664.401438069534311691 * 1e18); // still moving toward 20_000 _skipAndAccrue({ time: 2 days, // 3 days since liquidity was added - mau: 0.350326278385275701 * 1e18, // reached 35% + mau: 0.350326278385275702 * 1e18, // reached 35% tu: 0.770061298755197770 * 1e18, // still at 77% rate: 0.0405 * 1e18 // second interest rate drop }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 19_855.678232854988936290 * 1e18); // reached (sort of) 20_000 + assertEq(depositEma, 19_855.678232854988878203 * 1e18); // reached (sort of) 20_000 _assertPool( PoolParams({ htp: 1_159.624091089473286060 * 1e18, lup: _p1505_26, - poolSize: 25_003.260972741349848786 * 1e18, // reflects additional 10_000 deposit + poolSize: 25_003.260972741349788790 * 1e18, // reflects additional 10_000 deposit pledgedCollateral: 6 * 1e18, encumberedCollateral: 4.622276093514343199 * 1e18, poolDebt: 6_957.744546536839716358 * 1e18, - actualUtilization: 0.350326278385275701 * 1e18, // dropped to 35% as expected + actualUtilization: 0.350326278385275702 * 1e18, // dropped to 35% as expected targetUtilization: 0.770061298755197770 * 1e18, minDebtAmount: 695.774454653683971636 * 1e18, loans: 1, @@ -234,7 +234,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 3 hours, - mau: 0.438034189478303511 * 1e18, // rising from 35% to 90% + mau: 0.438034189478303512 * 1e18, // rising from 35% to 90% tu: 0.783712586574747919 * 1e18, // increases as collateralization decreases rate: 0.0405 * 1e18 }); @@ -243,7 +243,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 9 hours, - mau: 0.625264252786034774 * 1e18, // still rising to 90% + mau: 0.625264252786034776 * 1e18, // still rising to 90% tu: 0.817638199962595844 * 1e18, rate: 0.0405 * 1e18 }); @@ -252,7 +252,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 4 days, - mau: 0.897117712497350667 * 1e18, // reached 90% + mau: 0.897117712497350670 * 1e18, // reached 90% tu: 0.947031347885781555 * 1e18, rate: 0.0405 * 1e18 }); @@ -262,11 +262,11 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { PoolParams({ htp: 1_497.940412039697435044 * 1e18, lup: _p1505_26, - poolSize: 25_011.246566016078933954 * 1e18, + poolSize: 25_011.246566016078833928 * 1e18, pledgedCollateral: 12 * 1e18, // 6 additional NFTs deposited encumberedCollateral: 11.941618338706780744 * 1e18, // all 12 NFTs are encumbered poolDebt: 17_975.284944476369220528 * 1e18, // includes new debt - actualUtilization: 0.897117712497350667 * 1e18, + actualUtilization: 0.897117712497350670 * 1e18, targetUtilization: 0.947031347885781555 * 1e18, minDebtAmount: 1_797.528494447636922053 * 1e18, loans: 1, @@ -289,7 +289,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }); _skipAndAccrue({ time: 40 hours, // 2 days after liquidity was added - mau: 0.744857048522821157 * 1e18, // 7_647 / 10_000 ~= 76% + mau: 0.744857048522821158 * 1e18, // 7_647 / 10_000 ~= 76% tu: 0.793326272355691526 * 1e18, // starting at 77% rate: 0.05 * 1e18 }); @@ -297,7 +297,7 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { debtColEma: 8_539_491.492000790693673965 * 1e18, // reflects newly drawn debt lupt0DebtEma: 10_764_160.711133073306706753 * 1e18, // unchanged from setup debtEma: 7_585.487807318324588356 * 1e18, // increasing toward 7_647 - depositEma: 10_183.816911394801817581 * 1e18 // decreasing toward 10_000 + depositEma: 10_183.816911394801808573 * 1e18 // decreasing toward 10_000 }); // bad actor comes along and deposits large amount for 5 minutes, and then withdraws @@ -310,10 +310,10 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _pool.updateInterest(); // not really needed, since removing liquidity will trigger rate update _removeAllLiquidity({ from: _attacker, - amount: 150_000.003089440923020314 * 1e18, + amount: 150_000.003089440922870341 * 1e18, index: _i1505_26, newLup: _p1505_26, - lpRedeem: 149_972.484368509876101687 * 1e18 + lpRedeem: 149_972.484368509876401577 * 1e18 }); _skipAndAccrue({ @@ -326,12 +326,12 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { debtColEma: 8_540_370.017841347311996670 * 1e18, lupt0DebtEma: 10_764_702.716470726509705193 * 1e18, debtEma: 7_585.843823429738778980 * 1e18, - depositEma: 10_903.062849361711217820 * 1e18 // still noticably impacted + depositEma: 10_903.062849361711208755 * 1e18 // still noticably impacted }); _skipAndAccrue({ time: 12 hours, - mau: 0.729141586574051708 * 1e18, // moving back toward 75% + mau: 0.729141586574051710 * 1e18, // moving back toward 75% tu: 0.798822457321421405 * 1e18, rate: 0.05 * 1e18 }); @@ -339,17 +339,17 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { debtColEma: 8_656_142.490618553816291562 * 1e18, lupt0DebtEma: 10_836_128.117434222666947215 * 1e18, debtEma: 7_621.315210378439120928 * 1e18, - depositEma: 10_452.448949164988301441 * 1e18 // moving down back to 10_000 + depositEma: 10_452.448949164988281908 * 1e18 // moving down back to 10_000 }); _assertPool( PoolParams({ htp: 1_276.218508787651473374 * 1e18, lup: _p1505_26, - poolSize: 15_002.306593887595240000 * 1e18, + poolSize: 15_002.306593887595200000 * 1e18, pledgedCollateral: 6 * 1e18, encumberedCollateral: 5.087022896986824909 * 1e18, poolDebt: 7_657.311052725908840244 * 1e18, // 7_647 principal plus some interest - actualUtilization: 0.729141586574051708 * 1e18, + actualUtilization: 0.729141586574051710 * 1e18, targetUtilization: 0.798822457321421405 * 1e18, minDebtAmount: 765.731105272590884024 * 1e18, loans: 1, diff --git a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol index 306010318..d6dc2ea8a 100644 --- a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol @@ -154,7 +154,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.800750077529217167 * 1e18 }); - _assertLenderInterest(liquidityAdded, 10.004632566415850000 * 1e18); + _assertLenderInterest(liquidityAdded, 10.004632566415800000 * 1e18); // borrower borrows some additional quote after some time has passed skip(10 days); @@ -178,7 +178,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.500002057800446964 * 1e18 }); - _assertLenderInterest(liquidityAdded, 14.290569428855600000 * 1e18); + _assertLenderInterest(liquidityAdded, 14.290569428855550000 * 1e18); // mint additional quote to borrower to enable repayment deal(address(_quote), _borrower, 20_000 * 1e18); @@ -301,7 +301,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.088376197116173336 * 1e18 }); - _assertLenderInterest(liquidityAdded, 0.155402195487240000 * 1e18); + _assertLenderInterest(liquidityAdded, 0.155402195487210000 * 1e18); skip(4 hours); @@ -352,7 +352,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.197213816827790670 * 1e18 }); - _assertLenderInterest(liquidityAdded, 0.371927286666750000 * 1e18); + _assertLenderInterest(liquidityAdded, 0.371927286666690000 * 1e18); skip(4 hours); @@ -361,7 +361,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { from: _lender, amount: 1 * 1e18, index: 2550, - lpAward: 0.999978744441788344 * 1e18, + lpAward: 0.999978744441788346 * 1e18, newLup: 2995.912459898389633881 * 1e18 }); liquidityAdded += 1e18; @@ -372,7 +372,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); - _assertLenderInterest(liquidityAdded, 0.637680300600420000 * 1e18); + _assertLenderInterest(liquidityAdded, 0.637680300600360000 * 1e18); expectedBorrower1Debt = 8_008.240798551896146546 * 1e18; diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol index 977a6a75e..d0f221b99 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -140,22 +140,22 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { from: _lender, borrower: _borrower, maxDepth: 1, - settledDebt: 4_996.799887883802397930 * 1e18 + settledDebt: 4_996.799887883802392935 * 1e18 }); // collateral in bucket used to settle auction increased with the amount used to settle debt _assertBucket({ index: 2500, lpBalance: 4_997.115384615384614 * 1e18, - collateral: 1.293874031008720309 * 1e18, + collateral: 1.293874031008720308 * 1e18, deposit: 0, - exchangeRate: 1.000393560665305039 * 1e18 + exchangeRate: 1.000393560665305038 * 1e18 }); // partial borrower debt is settled, borrower collateral decreased with the amount used to settle debt _assertBorrower({ borrower: _borrower, - borrowerDebt: 70.600130721308261690 * 1e18, - borrowerCollateral: 0.706125968991279691 * 1e18, + borrowerDebt: 70.600130721308266687 * 1e18, + borrowerCollateral: 0.706125968991279692 * 1e18, borrowert0Np: 2_627.524038461538462750 * 1e18, borrowerCollateralization: 0.000000000998539114 * 1e18 }); @@ -182,26 +182,26 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertBucket({ index: 2500, - lpBalance: 24_989.247267890536762825 * 1e18, - collateral: 1.293874031008720309 * 1e18, + lpBalance: 24_989.247267890536782809 * 1e18, + collateral: 1.293874031008720308 * 1e18, deposit: 20_000 * 1e18, - exchangeRate: 1.000393560665305039 * 1e18 + exchangeRate: 1.000393560665305038 * 1e18 }); _settle({ from: _lender, borrower: _borrower, maxDepth: 1, - settledDebt: 70.567900577736065945 * 1e18 + settledDebt: 70.567900577736070940 * 1e18 }); _assertBucket({ index: 2500, - lpBalance: 24_989.247267890536762825 * 1e18, + lpBalance: 24_989.247267890536782809 * 1e18, collateral: 1.312146920864032688 * 1e18, - deposit: 19_929.399869278691738310 * 1e18, - exchangeRate: 1.000393560665305039 * 1e18 + deposit: 19_929.399869278691733312 * 1e18, + exchangeRate: 1.000393560665305038 * 1e18 }); _assertBorrower({ borrower: _borrower, @@ -222,10 +222,10 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertBucket({ index: 2500, - lpBalance: 24_989.247267890536762825 * 1e18, + lpBalance: 24_989.247267890536782809 * 1e18, collateral: 2.624293841728065376 * 1e18, - deposit: 14_859.717685886623586002 * 1e18, - exchangeRate: 1.000393560665305039 * 1e18 + deposit: 14_859.717685886623581004 * 1e18, + exchangeRate: 1.000393560665305038 * 1e18 }); _assertBucket({ index: 7388, @@ -303,10 +303,10 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertBucket({ index: 2500, - lpBalance: 14_853.871786224081491640 * 1e18, + lpBalance: 14_853.871786224081501492 * 1e18, collateral: 0, - deposit: 14_859.717685886623586002 * 1e18, - exchangeRate: 1.000393560665305039 * 1e18 + deposit: 14_859.717685886623581004 * 1e18, + exchangeRate: 1.000393560665305038 * 1e18 }); _assertBucket({ index: MAX_FENWICK_INDEX, diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index 1f2fc03b0..d8a49491c 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -292,7 +292,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 7.749209044755361552 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000.000966222327608000 * 1e18, + poolSize: 73_000.000966222327535000 * 1e18, pledgedCollateral: 4 * 1e18, encumberedCollateral: 2.517692578855560848 * 1e18, poolDebt: 24.968422683457442924 * 1e18, @@ -364,7 +364,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739737879567360457 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000.000966222327608000 * 1e18, + poolSize: 73_000.000966222327535000 * 1e18, pledgedCollateral: 3.0 * 1e18, encumberedCollateral: 1.736300564176668638 * 1e18, poolDebt: 17.219213638702081372 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol index 4eebb40a0..ec508c4b6 100644 --- a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol @@ -86,7 +86,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); _assertReserveAuction({ - reserves: 831.584938142441953626 * 1e18, + reserves: 831.584938142442153626 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -110,8 +110,8 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); _assertReserveAuction({ - reserves: 831.584938142441953626 * 1e18, - claimableReserves : 831.584938142441953626 * 1e18, + reserves: 831.584938142442153626 * 1e18, + claimableReserves : 831.584938142442153626 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -120,7 +120,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // kick off a new auction _startClaimableReserveAuction({ from: _bidder, - remainingReserves: 823.269088761017534090 * 1e18, + remainingReserves: 823.269088761017732090 * 1e18, price: 1_000_000_000 * 1e18, epoch: 1 }); @@ -191,7 +191,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // kick off a new auction _startClaimableReserveAuction({ from: _bidder, - remainingReserves: 823.269088761017534090 * 1e18, + remainingReserves: 823.269088761017732090 * 1e18, price: 1_000_000_000 * 1e18, epoch: 1 }); @@ -233,7 +233,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { (uint256 debt,,) = _pool.debtInfo(); assertEq(debt, 0); - uint256 reserves = 831.584938142441953626 * 1e18; + uint256 reserves = 831.584938142442153626 * 1e18; uint256 claimableReserves = reserves; uint256 expectedReserves = reserves; _assertReserveAuction({ @@ -280,7 +280,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { _takeReserves({ from: _bidder, amount: 300 * 1e18, - remainingReserves: 523.269088761017534090 * 1e18, + remainingReserves: 523.269088761017732090 * 1e18, price: expectedPrice, epoch: 1 }); @@ -317,7 +317,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); expectedQuoteBalance += expectedReserves; assertEq(_quote.balanceOf(_bidder), expectedQuoteBalance); - assertEq(_ajnaToken.balanceOf(_bidder), 32_679.857522137076126942 * 1e18); + assertEq(_ajnaToken.balanceOf(_bidder), 32_679.857522137064987602 * 1e18); expectedReserves = 0; _assertReserveAuction({ @@ -354,8 +354,8 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { collateralToPull: 0, newLup: 251_183.992399245533703810 * 1e18 }); - uint256 reserves = 831.584938142441953626 * 1e18; - uint256 claimableReserves = 433.633068409488761858 * 1e18; + uint256 reserves = 831.584938142442153626 * 1e18; + uint256 claimableReserves = 433.633068409488961858 * 1e18; _assertReserveAuction({ reserves: reserves, claimableReserves : claimableReserves, @@ -433,7 +433,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // start an auction, confirm old claimable reserves are included alongside new claimable reserves skip(1 days); - reserves = 442.238729377483806565 * 1e18; + reserves = 442.238729377484010623 * 1e18; uint256 newClaimableReserves = reserves; _assertReserveAuction({ reserves: reserves, @@ -454,7 +454,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // take everything skip(28 hours); - assertEq(expectedReserves, 767.113079809102842738 * 1e18); + assertEq(expectedReserves, 767.113079809103242756 * 1e18); expectedPrice = 3.725290298461914062 * 1e18; _assertReserveAuction({ reserves: 0, diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index c160c7fdb..46b88db5c 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -414,9 +414,9 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater); assertEq(_ajnaToken.balanceOf(_updater), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133134320 * 1e18); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 4.089968908133134320 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 4.089968908133149070 * 1e18); // check only deposit owner can claim rewards uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); @@ -425,7 +425,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); - assertEq(rewardsEarned, 40.899689081331305425 * 1e18); + assertEq(rewardsEarned, 40.899689081331421210 * 1e18); // claim rewards accrued since deposit changePrank(_minterOne); @@ -493,7 +493,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 4.089968908133134320 * 1e18 + reward: 4.089968908133149070 * 1e18 }); _assertBurn({ @@ -508,7 +508,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), epoch: 1, timestamp: block.timestamp - 24 hours, - burned: 81.799378162662586331 * 1e18, + burned: 81.799378162662881374 * 1e18, interest: 6.443638300196908069 * 1e18 }); @@ -521,8 +521,8 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(2, 0), - reward: 86.809555428378489140 * 1e18, - updateRatesReward: 4.173624213367915345 * 1e18 + reward: 86.809555428378605150 * 1e18, + updateRatesReward: 4.173624213367915365 * 1e18 }); } @@ -566,7 +566,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 4.089968908133134320 * 1e18 + reward: 4.089968908133149070 * 1e18 }); skip(2 weeks); @@ -578,7 +578,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), epoch: 1, timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), - burned: 81.799378162662586331 * 1e18, + burned: 81.799378162662881374 * 1e18, interest: 6.443638300196908069 * 1e18 }); @@ -594,7 +594,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 4.206490995172287125 * 1e18 + reward: 4.206490995172302285 * 1e18 }); } @@ -782,14 +782,20 @@ contract RewardsManagerTest is ERC20HelperContract { index: _i9_81, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 4_936.350384467466066087 * 1e18, - exchangeRate: 0.493635038446746607 * 1e18 + deposit: 4_936.865619773958011818 * 1e18, + exchangeRate: 0.493686561977395801 * 1e18 }); /***********************/ /*** Reserve Auction ***/ /***********************/ + // skip some time to accumulate reserves + skip(50 days); + + // update pool reserves + _pool.updateInterest(); + // start reserve auction changePrank(_bidder); _ajnaToken.approve(address(_pool), type(uint256).max); @@ -1031,13 +1037,13 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater); assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072284751 * 1e18); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314416 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737968741 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380328766 * 1e18); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /*****************************/ @@ -1054,7 +1060,7 @@ contract RewardsManagerTest is ERC20HelperContract { // skip updating exchange rates and check available rewards uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380328766 * 1e18); + assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); // snapshot calling update exchange rate @@ -1064,13 +1070,13 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater2); assertEq(_ajnaToken.balanceOf(_updater2), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973576689 * 1e18); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606335 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973576689 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 517.072612267115797118 * 1e18); + assertEq(rewardsEarned, 517.072612267116262774 * 1e18); assertGt(rewardsEarned, rewardsEarnedNoUpdate); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -1091,7 +1097,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380328766 * 1e18); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); // call update exchange rate changePrank(_updater2); @@ -1103,7 +1109,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned won't increase since previous update was missed rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380328766 * 1e18); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); /*****************************/ /*** Fifth Reserve Auction ***/ @@ -1121,12 +1127,12 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater2); assertEq(_ajnaToken.balanceOf(_updater2), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266846067 * 1e18); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905357 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266846067 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049032067 * 1e18); + assertEq(rewardsEarned, 493.039460320049732206 * 1e18); // claim all rewards accrued since deposit changePrank(_minterOne); @@ -1217,9 +1223,9 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards are claimed from the indexes that the staker is moving away from vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133134320 * 1e18); + emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133149070 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464439745 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570280 * 1e18); // check MoveLiquidity emits for (uint256 i = 0; i < firstIndexes.length; ++i) { vm.expectEmit(true, true, true, true); @@ -1260,7 +1266,7 @@ contract RewardsManagerTest is ERC20HelperContract { // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets firstIndexes = _positionManager.getPositionIndexes(tokenId); - _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045773803754351 * 1e18); + _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045773803754366 * 1e18); /*********************/ /*** Claim Rewards ***/ @@ -1268,9 +1274,9 @@ contract RewardsManagerTest is ERC20HelperContract { // claim rewards accrued since second movement of lps changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464439745 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730457738037587731 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730457738037587904 * 1e18); _rewardsManager.claimRewards(tokenId, currentBurnEpoch); } @@ -1340,21 +1346,21 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(1, 0), - reward: 39.908019526547621234 * 1e18, + reward: 39.908019526547891790 * 1e18, updateRatesReward: 0 }); uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 39.908019526547621234 * 1e18); + assertEq(minterTwoBalance, 39.908019526547891790 * 1e18); _unstakeToken({ minter: _minterThree, pool: address(_poolOne), tokenId: tokenIdThree, claimedArray: _epochsClaimedArray(1, 0), - reward: 33.248129642902499062 * 1e18, + reward: 33.248129642902710516 * 1e18, updateRatesReward: 0 }); uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 33.248129642902499062 * 1e18); + assertEq(minterThreeBalance, 33.248129642902710516 * 1e18); assertGt(minterTwoBalance, minterThreeBalance); } @@ -1411,7 +1417,7 @@ contract RewardsManagerTest is ERC20HelperContract { uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); // second depositor stakes NFT, generating an update reward _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077340107 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); // calculate rewards earned since exchange rates have been updated uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); @@ -1477,11 +1483,11 @@ contract RewardsManagerTest is ERC20HelperContract { // minter two claims rewards accrued since deposit changePrank(_minterTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077340107 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); vm.expectEmit(true, true, true, true); emit ClaimRewards(_minterTwo, address(_poolOne), tokenIdTwo, _epochsClaimedArray(1, 1), idTwoRewardsAtTwo); _rewardsManager.claimRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterTwo), idTwoRewardsAtTwo + 8.175422393077340107 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterTwo), idTwoRewardsAtTwo + 8.175422393077328665 * 1e18); // check there are no remaining rewards available after claiming uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); @@ -1551,7 +1557,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 4.089968908133113615 * 1e18 + reward: 4.089968908133202125 * 1e18 }); _triggerReserveAuctions(TriggerReserveAuctionParams({ @@ -1564,7 +1570,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 13.717705175494177175 * 1e18 + reward: 13.717705175494265742 * 1e18 }); _triggerReserveAuctions(TriggerReserveAuctionParams({ @@ -1577,7 +1583,7 @@ contract RewardsManagerTest is ERC20HelperContract { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 27.568516982211776340 * 1e18 + reward: 27.568516982211953592 * 1e18 }); // proof of burn events @@ -1594,14 +1600,14 @@ contract RewardsManagerTest is ERC20HelperContract { epoch: 1, timestamp: block.timestamp - (52 weeks + 72 hours), interest: 6.443638300196908069 * 1e18, - burned: 81.799378162662586331 * 1e18 + burned: 81.799378162664356589 * 1e18 }); _assertBurn({ pool: address(_poolOne), epoch: 2, timestamp: block.timestamp - (26 weeks + 48 hours), - burned: 356.153481672544289291 * 1e18, + burned: 356.153481672547831475 * 1e18, interest: 28.092564949680668737 * 1e18 }); @@ -1609,7 +1615,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), epoch: 3, timestamp: block.timestamp - 24 hours, - burned: 907.523821316779267550 * 1e18, + burned: 907.523821316786357044 * 1e18, interest: 71.814132054505950833 * 1e18 }); @@ -1619,7 +1625,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(3, 0), - reward: 75.626985109731636395 * 1e18, + reward: 75.626985109732100715 * 1e18, updateRatesReward: 0 }); @@ -1628,7 +1634,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(3, 0), - reward: 378.134925548658181975 * 1e18, + reward: 378.134925548660503565 * 1e18, updateRatesReward: 0 }); } @@ -1707,7 +1713,7 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater); assertEq(_ajnaToken.balanceOf(_updater), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133134320 * 1e18); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); assertGt(_ajnaToken.balanceOf(_updater), 0); @@ -1722,11 +1728,11 @@ contract RewardsManagerTest is ERC20HelperContract { IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); - assertEq(managerBalance, 4.931444746461913270 * 1e18); + assertEq(managerBalance, 4.931444746461898520 * 1e18); changePrank(_minterOne); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331305425 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); vm.expectEmit(true, true, true, true); emit Unstake(_minterOne, address(_poolOne), tokenIdOne); _rewardsManager.unstake(tokenIdOne); @@ -1741,12 +1747,12 @@ contract RewardsManagerTest is ERC20HelperContract { // test when enough tokens in rewards manager contracts changePrank(_minterOne); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331305425 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); vm.expectEmit(true, true, true, true); emit Unstake(_minterOne, address(_poolOne), tokenIdOne); _rewardsManager.unstake(tokenIdOne); assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331305425 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421210 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); @@ -1822,18 +1828,18 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_minterOne); assertEq(_ajnaToken.balanceOf(_minterOne), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), depositIndexesOne, 4.089968908133134320 * 1e18); + emit UpdateExchangeRates(_minterOne, address(_poolOne), depositIndexesOne, 4.089968908133149070 * 1e18); uint256 updateReward = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexesOne); assertEq(_ajnaToken.balanceOf(_minterOne), updateReward); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133134320 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); // check owner in pool with accrued interest can properly claim rewards changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133134320 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331305425 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464439745 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); } diff --git a/tests/forge/utils/Tokens.sol b/tests/forge/utils/Tokens.sol index 378634a67..dea0b67ef 100644 --- a/tests/forge/utils/Tokens.sol +++ b/tests/forge/utils/Tokens.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; +import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; contract Token is ERC20 { @@ -49,3 +50,11 @@ contract TokenWithNDecimals is ERC20 { } +contract BurnableToken is ERC20, ERC20Burnable { + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} + + function mint(address to_, uint256 amount_) public { + _mint(to_, amount_); + } +} + From 7666dd0606c6a925df9debd7bacfed30a75dfd9c Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:16:24 -0400 Subject: [PATCH 23/70] Ensure PositionManager.moveLiquidity accrues interest (#700) * fixed moveLiquidity interest bug * tidy and tearDown some related tests * pr feedback * rebased against develop * fixed test following rebase --- src/PositionManager.sol | 3 + tests/forge/PositionManager.t.sol | 142 +++++++++++++++++++++++++++++- tests/forge/RewardsManager.t.sol | 4 +- 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 2c31b54f9..c1aa578d3 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -257,6 +257,9 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R // handle the case where owner attempts to move liquidity after they've already done so if (vars.depositTime == 0) revert RemovePositionFailed(); + // ensure bucketDeposit accounts for accrued interest + IPool(params_.pool).updateInterest(); + // retrieve info of bucket from which liquidity is moved ( vars.bucketLPs, diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/PositionManager.t.sol index 88b7e350b..6381bf8b7 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/PositionManager.t.sol @@ -1486,7 +1486,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.ownerOf(tokenId); } - function testMoveLiquidityPermissions() external { + function testMoveLiquidityPermissions() external tearDown { // generate a new address address testAddress = makeAddr("testAddress"); address notOwner = makeAddr("notOwner"); @@ -1512,7 +1512,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.moveLiquidity(moveLiquidityParams); } - function testMoveLiquidity() external { + function testMoveLiquidity() external tearDown { // generate a new address address testAddress1 = makeAddr("testAddress1"); address testAddress2 = makeAddr("testAddress2"); @@ -1844,6 +1844,142 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.moveLiquidity(moveLiquidityParams); } + function testMoveLiquidityWithInterest() external tearDown { + address lender1 = makeAddr("lender1"); + address lender2 = makeAddr("lender2"); + address borrower = makeAddr("borrower"); + _mintQuoteAndApproveManagerTokens(lender1, 2_000 * 1e18); + _mintQuoteAndApproveManagerTokens(lender2, 3_000 * 1e18); + _mintCollateralAndApproveTokens(borrower, 250 * 1e18); + _mintQuoteAndApproveTokens(borrower, 500 * 1e18); + + uint256 mintIndex = _i9_91; + uint256 moveIndex = _i9_52; + + // two lenders add liquidity to the same bucket + _addInitialLiquidity({ + from: lender1, + amount: 2_000 * 1e18, + index: mintIndex + }); + _addInitialLiquidity({ + from: lender2, + amount: 3_000 * 1e18, + index: mintIndex + }); + skip(2 hours); + + // borrower draws debt + _drawDebt({ + from: borrower, + borrower: borrower, + amountToBorrow: 1_000 * 1e18, + limitIndex: mintIndex, + collateralToPledge: 250 * 1e18, + newLup: _p9_91 + }); + skip(22 hours); + + // lenders mint and memorialize positions + changePrank(lender1); + uint256 tokenId1 = _mintNFT(lender1, lender1, address(_pool)); + uint256[] memory indexes = new uint256[](1); + indexes[0] = mintIndex; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 2_000 * 1e18; + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = + IPositionManagerOwnerActions.MemorializePositionsParams(tokenId1, indexes); + _positionManager.memorializePositions(memorializeParams); + skip(1 days); + + changePrank(lender2); + uint256 tokenId2 = _mintNFT(lender2, lender2, address(_pool)); + amounts[0] = 3_000 * 1e18; + _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams(tokenId2, indexes); + _positionManager.memorializePositions(memorializeParams); + skip(1 days); + + // check pool state + _assertLenderLpBalance({ + lender: lender1, + index: mintIndex, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: lender2, + index: mintIndex, + lpBalance: 0, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 5_000 * 1e18, + depositTime: _startTime + }); + + // lender 1 moves liquidity + changePrank(lender1); + IPositionManagerOwnerActions.MoveLiquidityParams memory moveLiquidityParams = + IPositionManagerOwnerActions.MoveLiquidityParams( + tokenId1, address(_pool), mintIndex, moveIndex, block.timestamp + 30 + ); + _positionManager.moveLiquidity(moveLiquidityParams); + + // check pool state + _assertLenderLpBalance({ + lender: address(_positionManager), + index: mintIndex, + lpBalance: 3_000 * 1e18, + depositTime: _startTime + }); + _assertLenderLpBalance({ + lender: address(_positionManager), + index: moveIndex, + lpBalance: 1_999.870115955512239554 * 1e18, + depositTime: _startTime + }); + skip(1 weeks); + + // lender1 redeems their NFT + changePrank(lender1); + address[] memory transferors = new address[](1); + transferors[0] = address(_positionManager); + _pool.approveLPsTransferors(transferors); + indexes[0] = moveIndex; + IPositionManagerOwnerActions.RedeemPositionsParams memory reedemParams = + IPositionManagerOwnerActions.RedeemPositionsParams( + tokenId1, address(_pool), indexes + ); + _positionManager.reedemPositions(reedemParams); + skip(2 days); + + // borrower repays + _repayDebt({ + from: borrower, + borrower: borrower, + amountToRepay: type(uint256).max, + amountRepaid: 1_002.608307827389905517 * 1e18, + collateralToPull: 250 * 1e18, + newLup: MAX_PRICE + }); + + // lender2 redeems their NFT + skip(1 days); + changePrank(lender2); + _pool.approveLPsTransferors(transferors); + indexes[0] = mintIndex; + reedemParams = IPositionManagerOwnerActions.RedeemPositionsParams( + tokenId2, address(_pool), indexes + ); + _positionManager.reedemPositions(reedemParams); + + // tearDown ensures buckets are empty + } + function testRedeemPositions() external { address testMinter = makeAddr("testMinter"); address notOwner = makeAddr("notOwner"); @@ -2499,7 +2635,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // call pool contract directly to add quote tokens uint256[] memory indexes = new uint256[](1); indexes[0] = 2550; - uint256[] memory amounts = new uint256[] (1); + uint256[] memory amounts = new uint256[](1); amounts[0] = 3_000 * 1e18; address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index 46b88db5c..a6ed103f8 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -1266,7 +1266,7 @@ contract RewardsManagerTest is ERC20HelperContract { // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets firstIndexes = _positionManager.getPositionIndexes(tokenId); - _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045773803754366 * 1e18); + _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045926578320550 * 1e18); /*********************/ /*** Claim Rewards ***/ @@ -1276,7 +1276,7 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_minterOne); assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730457738037587904 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249745 * 1e18); _rewardsManager.claimRewards(tokenId, currentBurnEpoch); } From 5f4dccd5f4bd51aa739595c5d0645246f95923e7 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 27 Mar 2023 09:41:15 +0300 Subject: [PATCH 24/70] Fix F3 and R1 invariant logic (#704) * Fix F3 and R1 invariant logic - revert logic for exchange rate to compare current values instead multiplying them by LPs - fix F3 to use prefixSum call * F3 invariant regression tests --- .../invariants/BasicInvariants.t.sol | 16 ++++------ .../RegressionTestLiquidation.t.sol | 30 +++++++++++++++++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 2ee0b09ee..1f943135b 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -234,7 +234,6 @@ contract BasicInvariants is InvariantsTestBase { function invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { uint256 currentExchangeRate = _pool.bucketExchangeRate(bucketIndex); - (uint256 currentBucketLps, , , , ) = _pool.bucketInfo(bucketIndex); if (IBaseHandler(_handler).exchangeRateShouldNotChange(bucketIndex)) { uint256 previousExchangeRate = IBaseHandler(_handler).previousExchangeRate(bucketIndex); @@ -246,9 +245,9 @@ contract BasicInvariants is InvariantsTestBase { console.log("======================================"); requireWithinDiff( - Maths.wmul(currentExchangeRate, currentBucketLps), - Maths.wmul(previousExchangeRate, currentBucketLps), - 1e5, + currentExchangeRate, + previousExchangeRate, + 1e17, "Incorrect exchange Rate changed" ); } @@ -367,17 +366,14 @@ contract BasicInvariants is InvariantsTestBase { } } - // For any index i < MAX_FENWICK_INDEX, findIndexOfSum(prefixSum(i)) > i + // For any index i < MAX_FENWICK_INDEX, depositIndex(depositUpToIndex(i)) > i function invariant_fenwick_bucket_index_F3() public useCurrentTimestamp { - uint256 prefixSum; - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); + uint256 prefixSum = _pool.depositUpToIndex(bucketIndex); + uint256 bucketIndexFromDeposit = _pool.depositIndex(prefixSum); if (depositAtIndex != 0) { - prefixSum += depositAtIndex; - uint256 bucketIndexFromDeposit = _pool.depositIndex(prefixSum); - console.log("===================Bucket Index : ", bucketIndex, " ==================="); console.log("Bucket Index from deposit -->", bucketIndexFromDeposit); console.log("========================================="); diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 64078f259..1a97734d0 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -102,7 +102,8 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_auction_taken_A6(); } - function test_regression_invariant_exchange_rate_bucket_take_1() external { + // FIXME: bucket take with 0 auction price + function _test_regression_invariant_exchange_rate_bucket_take_1() external { _liquidationPoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); _liquidationPoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); _liquidationPoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); @@ -111,7 +112,8 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - function test_regression_invariant_exchange_rate_bucket_take_2() external { + // FIXME: bucket take with 0 auction price + function _test_regression_invariant_exchange_rate_bucket_take_2() external { _liquidationPoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); _liquidationPoolHandler.settleAuction(186120755740, 2, 59199623628501455128); _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); @@ -175,4 +177,28 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_fenwick_depositsTillIndex_F2(); } + function test_regression_invariant_F3_1() external { + _liquidationPoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationPoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3731592205777443374190, 2); + _liquidationPoolHandler.takeAuction(3554599780774102176805971372130467746, 140835031537485528703906318530162192, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationPoolHandler.repayDebt(2692074105646752292572533908391, 1968526964305399089154844418825); + _liquidationPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 4553829); + _liquidationPoolHandler.bucketTake(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 0); + _liquidationPoolHandler.drawDebt(626971501456142588551128155365, 816763288150043968438676); + _liquidationPoolHandler.pullCollateral(381299861468989210101433912, 999999999999997998400442008957368645662570165); + + invariant_fenwick_bucket_index_F3(); + } + + function test_regression_invariant_F3_2() external { + _liquidationPoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); + _liquidationPoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); + _liquidationPoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); + _liquidationPoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_fenwick_bucket_index_F3(); + } + } \ No newline at end of file From 4670c84e1a7dbcc423ac5eeee3676cc27654c220 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Mon, 27 Mar 2023 09:45:36 -0400 Subject: [PATCH 25/70] removed unused error --- src/interfaces/rewards/IRewardsManagerErrors.sol | 5 ----- tests/forge/RewardsManager.t.sol | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/interfaces/rewards/IRewardsManagerErrors.sol b/src/interfaces/rewards/IRewardsManagerErrors.sol index ba471960a..81590f837 100644 --- a/src/interfaces/rewards/IRewardsManagerErrors.sol +++ b/src/interfaces/rewards/IRewardsManagerErrors.sol @@ -16,11 +16,6 @@ interface IRewardsManagerErrors { */ error EpochNotAvailable(); - /** - * @notice User attempted to record updated exchange rates outside of the allowed period. - */ - error ExchangeRateUpdateTooLate(); - /** * @notice User provided move index params that didn't match in size. */ diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index a6ed103f8..82ff3b40b 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -451,8 +451,8 @@ contract RewardsManagerTest is ERC20HelperContract { // check can't call update exchange rate after the update period has elapsed skip(2 weeks); - // changePrank(_updater); - // vm.expectRevert(IAjnaRewards.ExchangeRateUpdateTooLate.selector); + + // Although an `UpdateExchangeRates` is emmited the rates are not updated, as demonstrated by updateRewards == 0 uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); assertEq(updateRewards, 0); } From 70c7817ef1c86ae9cb8931573305c2c3a2692409 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 28 Mar 2023 08:24:54 +0300 Subject: [PATCH 26/70] Introduce Maths.floorWdiv and floorWmul (#705) --- src/libraries/external/Auctions.sol | 10 +++++----- src/libraries/internal/Maths.sol | 8 ++++++++ tests/forge/MathTest.t.sol | 10 ++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index 4def9874a..73b28b178 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -228,7 +228,7 @@ library Auctions { if (vars.unscaledDeposit != 0) { vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled - vars.maxSettleableDebt = (borrower.collateral * vars.price) / 1e18; // max debt that can be settled with existing collateral + vars.maxSettleableDebt = Maths.floorWmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); // enough deposit in bucket and collateral avail to settle entire debt @@ -245,14 +245,14 @@ library Auctions { vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); // subtract from debt the corresponding t0 amount of deposit - borrower.t0Debt -= (vars.scaledDeposit * 1e18) / poolState_.inflator; + borrower.t0Debt -= Maths.floorWdiv(vars.scaledDeposit, poolState_.inflator); } // settle constrained by collateral available else { vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); vars.collateralUsed = borrower.collateral; - borrower.t0Debt -= (vars.maxSettleableDebt * 1e18) / poolState_.inflator; + borrower.t0Debt -= Maths.floorWdiv(vars.maxSettleableDebt, poolState_.inflator); } // remove settled collateral from loan @@ -302,7 +302,7 @@ library Auctions { uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; // settle debt from reserves -- round reserves down however - borrower.t0Debt -= Maths.min(borrower.t0Debt, (reserves * 1e18) / poolState_.inflator); + borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(reserves, poolState_.inflator)); // if there's still debt after settling from reserves then start to forgive amount from next HPB // loop through remaining buckets if there's still debt to settle @@ -321,7 +321,7 @@ library Auctions { // not enough deposit to settle entire debt, we settle only deposit amount } else { - borrower.t0Debt -= (vars.depositToRemove * 1e18) / poolState_.inflator; // subtract from remaining debt the corresponding t0 amount of deposit + borrower.t0Debt -= Maths.floorWdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket Bucket storage hpbBucket = buckets_[vars.index]; diff --git a/src/libraries/internal/Maths.sol b/src/libraries/internal/Maths.sol index c01f6e059..369f563bf 100644 --- a/src/libraries/internal/Maths.sol +++ b/src/libraries/internal/Maths.sol @@ -14,10 +14,18 @@ library Maths { return (x * y + 1e18 / 2) / 1e18; } + function floorWmul(uint256 x, uint256 y) internal pure returns (uint256) { + return (x * y) / 1e18; + } + function wdiv(uint256 x, uint256 y) internal pure returns (uint256) { return (x * 1e18 + y / 2) / y; } + function floorWdiv(uint256 x, uint256 y) internal pure returns (uint256) { + return (x * 1e18) / y; + } + function max(uint256 x, uint256 y) internal pure returns (uint256) { return x >= y ? x : y; } diff --git a/tests/forge/MathTest.t.sol b/tests/forge/MathTest.t.sol index 51291ba55..28ecc6aa2 100644 --- a/tests/forge/MathTest.t.sol +++ b/tests/forge/MathTest.t.sol @@ -58,4 +58,14 @@ contract MathTest is DSTestPlus { assertEq(Maths.min(0, 9), 0); assertEq(Maths.min(smallerWad, largerWad), smallerWad); } + + function testFloorWdiv() external { + assertEq(Maths.wdiv( 1_001.4534563955 * 1e18, 55.24325 * 1e18), 18.128069155878772520 * 1e18); + assertEq(Maths.floorWdiv(1_001.4534563955 * 1e18, 55.24325 * 1e18), 18.128069155878772519 * 1e18); + } + + function testFloorWmul() external { + assertEq(Maths.wmul( 1.4534563955 * 1e18, 0.112224325121212178 * 1e18), 0.163113163078097153 * 1e18); + assertEq(Maths.floorWmul(1.4534563955 * 1e18, 0.112224325121212178 * 1e18), 0.163113163078097152 * 1e18); + } } From 33455539dba82e54bd3c9f6103543ebbb8ce29f2 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Wed, 29 Mar 2023 16:31:30 +0530 Subject: [PATCH 27/70] Update Min and Max amount limit in invariant tests (#708) * Updated minimum and maximum amount limit in invariant tests * Fix F1 and F2 failures * Fix F3 and F4 * Include tests that were failing in PR 701 * Fix F4 failure, move warp for kick * Test with reserve decreasing after add quote token * Fix F1 F2 invariants for kick - refresh local fenwick and accrue interest if need of draw debt / warp * Fix foundry.toml comments * Remove useTimestamps modifier from internal handler methods * Revert local updates in case of draw debt before kick * Add failing regression test * Fix compile error --------- Co-authored-by: grandizzy --- foundry.toml | 4 +- .../ERC20Pool/invariants/base/BaseHandler.sol | 5 +- .../base/UnboundedBasicPoolHandler.sol | 25 +++---- .../base/UnboundedLiquidationPoolHandler.sol | 12 ++-- .../base/UnboundedReservePoolHandler.sol | 4 +- .../invariants/handlers/BasicPoolHandler.sol | 40 ++++++----- .../handlers/LiquidationPoolHandler.sol | 18 +++-- .../handlers/ReservePoolHandler.sol | 5 +- .../regression/RegressionTestBasic.t.sol | 4 +- .../RegressionTestLiquidation.t.sol | 72 +++++++++++++++++++ .../regression/RegressionTestReserves.t.sol | 58 +++++++++++++++ 11 files changed, 193 insertions(+), 54 deletions(-) diff --git a/foundry.toml b/foundry.toml index a58a30960..c0afa1df8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,7 +26,7 @@ optimizer_runs = 200 runs = 300 [invariant] -runs = 1000 # The number of calls to make in the invariant tests -depth = 10 # The number of times to run the invariant tests +runs = 1000 # Number of times that a sequence of function calls is generated and run +depth = 30 # Number of function calls made in a given run. call_override = false # Override calls fail_on_revert = false # Fail the test if the contract reverts \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index 5649780d4..1a33a9767 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -27,6 +27,9 @@ uint256 constant LENDER_MAX_BUCKET_INDEX = 2572; uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; +uint256 constant MIN_AMOUNT = 1e6; +uint256 constant MAX_AMOUNT = 1e28; + abstract contract BaseHandler is Test { // Tokens @@ -329,7 +332,7 @@ abstract contract BaseHandler is Test { function _auctionSettleStateReset(address actor_) internal { (address kicker, , , , , , , , , ) = _pool.auctionInfo(actor_); - // auction is settled if kicekr is 0x + // auction is settled if kicker is 0x bool auctionSettled = kicker == address(0); // reset alreadyTaken flag if auction is settled if (auctionSettled) alreadyTaken[actor_] = false; diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol index e7cb15a32..d38cae271 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol @@ -30,7 +30,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _addQuoteToken( uint256 amount_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.addQuoteToken']++; (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); @@ -70,7 +70,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _removeQuoteToken( uint256 amount_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.removeQuoteToken']++; (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); @@ -95,7 +95,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { uint256 amount_, uint256 fromIndex_, uint256 toIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { try _pool.moveQuoteToken( amount_, @@ -123,7 +123,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _addCollateral( uint256 amount_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.addCollateral']++; (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); @@ -143,7 +143,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _removeCollateral( uint256 amount_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.removeCollateral']++; (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); @@ -166,7 +166,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { address receiver_, uint256 bucketIndex_, uint256 amount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { // approve as transferor address[] memory transferors = new address[](1); transferors[0] = receiver_; @@ -183,7 +183,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { address sender_, address receiver_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { uint256[] memory buckets = new uint256[](1); buckets[0] = bucketIndex_; @@ -208,7 +208,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _pledgeCollateral( uint256 amount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.pledgeCollateral']++; // **R1**: Exchange rates are unchanged by pledging collateral @@ -221,7 +221,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _pullCollateral( uint256 amount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.pullCollateral']++; // **R2**: Exchange rates are unchanged by pulling collateral @@ -238,7 +238,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { function _drawDebt( uint256 amount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.drawDebt']++; (uint256 poolDebt, , ) = _pool.debtInfo(); @@ -260,14 +260,11 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { } catch (bytes memory err) { _ensurePoolError(err); } - - // skip to make borrower undercollateralize - vm.warp(block.timestamp + 200 days); } function _repayDebt( uint256 amountToRepay_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.repayDebt']++; try _pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol index 40fabf53d..3d15dd529 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -21,7 +21,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { function _kickAuction( address borrower_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.kickAuction']++; (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); @@ -39,7 +39,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { function _kickWithDeposit( uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { (address maxBorrower, , ) = _pool.loansInfo(); (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), maxBorrower); (uint256 interestRate, ) = _pool.interestRateInfo(); @@ -62,7 +62,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { function _withdrawBonds( address kicker_, uint256 maxAmount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { try _pool.withdrawBonds(kicker_, maxAmount_) { @@ -79,7 +79,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { address borrower_, uint256 amount_, address taker_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.takeAuction']++; (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); @@ -133,7 +133,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { address borrower_, bool depositTake_, uint256 bucketIndex_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBLiquidationHandler.bucketTake']++; (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); @@ -182,7 +182,7 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { function _settleAuction( address borrower_, uint256 maxDepth_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { ( uint256 borrowerT0Debt, uint256 collateral, diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol index 81d7c7034..062f4142d 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol @@ -12,7 +12,7 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { /*** Kicker Helper Functions ***/ /*******************************/ - function _startClaimableReserveAuction() internal useTimestamps updateLocalStateAndPoolInterest { + function _startClaimableReserveAuction() internal updateLocalStateAndPoolInterest { (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); if (claimableReserves == 0) return; @@ -31,7 +31,7 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { function _takeReserves( uint256 amount_ - ) internal useTimestamps updateLocalStateAndPoolInterest { + ) internal updateLocalStateAndPoolInterest { deal(address(_ajna), _actor, type(uint256).max); IERC20(address(_ajna)).approve(address(_pool), type(uint256).max); diff --git a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol index 88440524e..2d8053aa3 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol @@ -10,6 +10,8 @@ import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, + MIN_AMOUNT, + MAX_AMOUNT, BaseHandler } from '../base/BaseHandler.sol'; import { UnboundedBasicPoolHandler } from '../base/UnboundedBasicPoolHandler.sol'; @@ -41,7 +43,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 actorIndex_, uint256 amountToAdd_, uint256 bucketIndex_ - ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { numberOfCalls['BBasicHandler.addQuoteToken']++; // Prepare test phase @@ -55,7 +57,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 actorIndex_, uint256 amountToRemove_, uint256 bucketIndex_ - ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { numberOfCalls['BBasicHandler.removeQuoteToken']++; // Prepare test phase @@ -70,7 +72,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 amountToMove_, uint256 fromIndex_, uint256 toIndex_ - ) public useRandomActor(actorIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BBasicHandler.moveQuoteToken']++; // Prepare test phase @@ -88,7 +90,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 actorIndex_, uint256 amountToAdd_, uint256 bucketIndex_ - ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { numberOfCalls['BBasicHandler.addCollateral']++; // Prepare test phase @@ -102,7 +104,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 actorIndex_, uint256 amountToRemove_, uint256 bucketIndex_ - ) public useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { numberOfCalls['BBasicHandler.removeCollateral']++; // Prepare test phase @@ -117,7 +119,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 toActorIndex_, uint256 lpsToTransfer_, uint256 bucketIndex_ - ) public useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + ) external useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { // Prepare test phase (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); @@ -133,7 +135,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function pledgeCollateral( uint256 actorIndex_, uint256 amountToPledge_ - ) public useRandomActor(actorIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BBasicHandler.pledgeCollateral']++; // Prepare test phase @@ -149,7 +151,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function pullCollateral( uint256 actorIndex_, uint256 amountToPull_ - ) public useRandomActor(actorIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BBasicHandler.pullCollateral']++; // Prepare test phase @@ -162,7 +164,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function drawDebt( uint256 actorIndex_, uint256 amountToBorrow_ - ) public useRandomActor(actorIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BBasicHandler.drawDebt']++; // Prepare test phase @@ -178,7 +180,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function repayDebt( uint256 actorIndex_, uint256 amountToRepay_ - ) public useRandomActor(actorIndex_) useTimestamps { + ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BBasicHandler.repayDebt']++; // Prepare test phase @@ -198,13 +200,13 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preAddQuoteToken( uint256 amountToAdd_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, _pool.quoteTokenDust(), 1e30); + boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); } function _preRemoveQuoteToken( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, 1, 1e30); + boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); // ensure actor has quote tokens to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); @@ -220,7 +222,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { ) 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_, 1, 1e30); + boundedAmount_ = constrictToRange(amountToMove_, MIN_AMOUNT, MAX_AMOUNT); // ensure actor has LPs to move (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); @@ -235,13 +237,13 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preAddCollateral( uint256 amountToAdd_ ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, 1e6, 1e30); + boundedAmount_ = constrictToRange(amountToAdd_, MIN_AMOUNT, MAX_AMOUNT); } function _preRemoveCollateral( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, 1, 1e30); + boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); // ensure actor has collateral to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); @@ -266,19 +268,19 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function _prePledgeCollateral( uint256 amountToPledge_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, _pool.collateralScale(), 1e30); + boundedAmount_ = constrictToRange(amountToPledge_, _pool.collateralScale(), MAX_AMOUNT); } function _prePullCollateral( uint256 amountToPull_ ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPull_, 1, 1e30); + boundedAmount_ = constrictToRange(amountToPull_, MIN_AMOUNT, MAX_AMOUNT); } function _preDrawDebt( uint256 amountToBorrow_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToBorrow_, 1e6, 1e30); + boundedAmount_ = constrictToRange(amountToBorrow_, MIN_AMOUNT, MAX_AMOUNT); // Pre Condition // 1. borrower's debt should exceed minDebt @@ -316,7 +318,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preRepayDebt( uint256 amountToRepay_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRepay_, _pool.quoteTokenDust(), 1e30); + boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); // ensure actor has debt to repay (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol index f60262604..40f205435 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol @@ -6,6 +6,8 @@ import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, + MIN_AMOUNT, + MAX_AMOUNT, BaseHandler } from '../base/BaseHandler.sol'; import { UnboundedLiquidationPoolHandler } from '../base/UnboundedLiquidationPoolHandler.sol'; @@ -63,7 +65,7 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan ) external useRandomActor(actorIndex_) useTimestamps { numberOfCalls['BLiquidationHandler.takeAuction']++; - amount_ = constrictToRange(amount_, 1, 1e30); + amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); @@ -75,6 +77,8 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan if (kickTime == 0) _kickAuction(borrowerIndex_, amount_ * 100, actorIndex_); changePrank(taker); + // skip time to make auction takeable + vm.warp(block.timestamp + 2 hours); _takeAuction(borrower, amount_, taker); } @@ -97,6 +101,8 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); changePrank(taker); + // skip time to make auction takeable + vm.warp(block.timestamp + 2 hours); _bucketTake(taker, borrower, depositTake_, bucketIndex_); } @@ -137,13 +143,13 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan uint256 borrowerIndex_, uint256 amount_, uint256 kickerIndex_ - ) internal useTimestamps useRandomActor(kickerIndex_) { + ) internal useRandomActor(kickerIndex_) { numberOfCalls['BLiquidationHandler.kickAuction']++; borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); address borrower = actors[borrowerIndex_]; address kicker = _actor; - amount_ = constrictToRange(amount_, 1, 1e30); + amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); @@ -155,14 +161,14 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan _actor = borrower; uint256 drawDebtAmount = _preDrawDebt(amount_); _drawDebt(drawDebtAmount); + + // skip to make borrower undercollateralized + vm.warp(block.timestamp + 200 days); } changePrank(kicker); _actor = kicker; _kickAuction(borrower); } - - // skip some time for more interest - vm.warp(block.timestamp + 2 hours); } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol index e649cea6c..0238148b5 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol @@ -7,6 +7,7 @@ import 'src/libraries/internal/Maths.sol'; import { UnboundedReservePoolHandler } from '../base/UnboundedReservePoolHandler.sol'; import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; +import { MIN_AMOUNT } from '../base/BaseHandler.sol'; contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandler { @@ -50,7 +51,7 @@ contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandl function _preTakeReserves( uint256 amountToTake_ - ) internal useTimestamps returns (uint256 boundedAmount_) { + ) internal returns (uint256 boundedAmount_) { (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); if (claimableReservesRemaining == 0) _startClaimableReserveAuction(); @@ -58,7 +59,7 @@ contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandl skip(24 hours); (, , claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - boundedAmount_ = constrictToRange(amountToTake_, 0, Maths.min(100_000 * 1e18, claimableReservesRemaining)); + boundedAmount_ = constrictToRange(amountToTake_, 0, Maths.min(MIN_AMOUNT, claimableReservesRemaining)); } } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol index d5691de40..465f1a360 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol @@ -225,8 +225,8 @@ contract RegressionTestBasic is BasicInvariants { function test_regression_fenwick_index_2() external { uint256 depositAt2570 = 570036521745120847917211; - uint256 depositAt2571 = _basicPoolHandler.constrictToRange(2578324552477056269186646552413, 1, 1000000000000000000000000000000); - uint256 depositAt2572 = 1212; + uint256 depositAt2571 = _basicPoolHandler.constrictToRange(2578324552477056269186646552413, 1e6, 1e28); + uint256 depositAt2572 = _basicPoolHandler.constrictToRange(1212, 1e6, 1e28); _basicPoolHandler.addQuoteToken(1, depositAt2570, 2570); _basicPoolHandler.addQuoteToken(1, depositAt2571, 2571); _basicPoolHandler.addQuoteToken(1, depositAt2572, 2572); diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 1a97734d0..343b33b11 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -177,6 +177,28 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_fenwick_depositsTillIndex_F2(); } + function test_regression_invariant_settle_F1_4() external { + _liquidationPoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); + _liquidationPoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); + _liquidationPoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); + _liquidationPoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); + _liquidationPoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); + _liquidationPoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F2_3() external { + _liquidationPoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); + _liquidationPoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); + _liquidationPoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); + _liquidationPoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); + _liquidationPoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); + _liquidationPoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_depositsTillIndex_F2(); + } + function test_regression_invariant_F3_1() external { _liquidationPoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932); _liquidationPoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935); @@ -201,4 +223,54 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_fenwick_bucket_index_F3(); } + function test_regression_invariant_F4_1() external { + _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 127546297848367334892478587751, 723921922395815633171615243621131242188407029895233162931857565302); + _liquidationPoolHandler.removeQuoteToken(2, 2, 7361820555); + _liquidationPoolHandler.takeAuction(85885591922376805486065427318859822458293427950603, 8526258315228761831408142393759013524255378290706574861831877477, 1267004887455971938409309909682740381503049590444968840223); + _liquidationPoolHandler.drawDebt(663777721413606329209923101072, 946300054291644291801213511570); + _liquidationPoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); + _liquidationPoolHandler.addQuoteToken(9360900796482582322800, 694431436637841996793959397509, 553923154643858021986449189292); + _liquidationPoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 34469655866078951331675076928366708920312931751567797); + _liquidationPoolHandler.bucketTake(0, 1, false, 3); + _liquidationPoolHandler.bucketTake(1190209291225920034207711400729307351194726, 2492241351445208059551299524117408972943752042954, false, 3385052658235853990473420226123930971); + _liquidationPoolHandler.settleAuction(2693191148227658159823862814074, 44032195641927234172430384447, 2992758194960713897487381207167); + _liquidationPoolHandler.removeQuoteToken(3, 34308174710409047450205135565, 2); + _liquidationPoolHandler.takeAuction(235062105582030911119033338, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_2() external { + _liquidationPoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); + _liquidationPoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); + _liquidationPoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); + _liquidationPoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_3() external { + _liquidationPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 88); + _liquidationPoolHandler.kickWithDeposit(454046303796091226235, 1); + _liquidationPoolHandler.addQuoteToken(22366532024867500041595597535594488494092956872779970834638, 2056702511, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationPoolHandler.takeAuction(7409458575819003489055485098, 19999999999999999999998047232, 160427188541373972791114); + _liquidationPoolHandler.drawDebt(54, 1078707919809097500728008); + _liquidationPoolHandler.takeAuction(2, 11014481, 0); + _liquidationPoolHandler.kickWithDeposit(6261145081390052923416, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationPoolHandler.repayDebt(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationPoolHandler.repayDebt(19522111312004366551699434321235702562902449, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationPoolHandler.removeQuoteToken(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationPoolHandler.kickAuction(1, 2109173590696846176713716365608775182694735853511202473079, 1); + _liquidationPoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_4() external { + _liquidationPoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_prefixSumIndex_F4(); + } + } \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index 533989bc4..679830c12 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -117,6 +117,29 @@ contract RegressionTestReserve is ReserveInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } + // FIXME: Seems to be issue with rounding to nearest in Desposits.unscaledAdd() in addQuoteToken + function _test_regression_reserve_16() external { + _reservePoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); + _reservePoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); + _reservePoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231); + _reservePoolHandler.takeReserves(3052809529665022333893308239466671666604242469878272137069, 2); + _reservePoolHandler.settleAuction(56829802927206056542134152487104, 1, 16551256); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 4559892907266199616760, true, 92132592320410512639572628067656882480659844625060229234412683145); + _reservePoolHandler.addQuoteToken(26659, 27252796304289191617124780530313880584663397025838797405583704016009646047240, 8174069071114126926049883726727); + _reservePoolHandler.settleAuction(7416752279321695807446009676282848840713503167567654621163487831711306738, 42429259698839522507819580090756, 4353185348715295869540288672); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.takeReserves(7414584624540108578389380660398591567646816233407392320795021351932076518, 119186585263660671065239170291646549528129172578); + _reservePoolHandler.takeReserves(14604452466686952199052773378, 15308); + _reservePoolHandler.moveQuoteToken(2, 7113439765, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 101839127799233627783); + _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); + _reservePoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 175006273713916823228319530732179, 3); + _reservePoolHandler.kickAuction(999999999999999989948035804259829580593704779, 2999999999999999995605838724439103323477035837, 567178035339127142779327214); + _reservePoolHandler.kickWithDeposit(17028734043909648834002499445, 9578925065330517200577552073309); + _reservePoolHandler.addQuoteToken(6672165, 3776221923932077947607417775990788567, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + function test_regression_fenwick_deposits_1() external { _reservePoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); _reservePoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617); @@ -336,4 +359,39 @@ contract RegressionTestReserve is ReserveInvariants { invariant_fenwick_depositsTillIndex_F2(); } + function test_regression_invariant_takeAuction_F3() external { + _reservePoolHandler.drawDebt(66012189296213, 3501011380219996136241089195497); + _reservePoolHandler.kickAuction(5022297903775350684886398975, 20526, 2902853749630275072725962069); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 4391496802861267555764811220); + _reservePoolHandler.moveQuoteToken(26018560, 3192, 25995484456155391449642016017, 22537); + _reservePoolHandler.transferLps(10763986310328530217005920827655704540417291683469924162879658, 4634, 8842, 3); + _reservePoolHandler.settleAuction(2913861884801667469428509650, 17685440748964982730500143988068465999241920952718023027278539889735696458314, 744860398079104642573120377479575543713282684535849403581932752660396046); + _reservePoolHandler.takeReserves(9546428924610247071820016, 1); + _reservePoolHandler.kickAuction(1021712469506287128291988, 470273052888220, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reservePoolHandler.kickWithDeposit(21372131561480654576901520848, 583255095299263976575486908); + _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 219682941, 6398456408984021365251851328837461998816613070677747503909692892499751257833); + _reservePoolHandler.moveQuoteToken(8413969458442105899430554342773, 42973831423907508485458560352, 14483994975746621772566970294, 27693669185946254354714892761); + _reservePoolHandler.bucketTake(0, 1, false, 1); + _reservePoolHandler.takeAuction(2760306433008897416497, 35178760526536102733112750779, 307455027758822287663945712); + + invariant_fenwick_bucket_index_F3(); + invariant_fenwick_prefixSumIndex_F4(); + } + + // FIXME: Seems to be an issue with Deposits.mult() in accrue interest or some issue with timestamp in invariant setup + function _test_regression_kick_F1_f2() external { + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); + _reservePoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); + _reservePoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); + _reservePoolHandler.takeAuction(296258719633565160185329, 490859840095298219320862, 16604700944401714968833692676); + _reservePoolHandler.kickAuction(1007024558278734662013991074770, 12316238, 8522190612260582802728723964891359810344750053801981528212387048); + _reservePoolHandler.takeAuction(999999999999999990212662818220103017885508577, 13644265990130681739980240101, 365402912996683431395427167362586262781607554542513822722975820380813222232); + _reservePoolHandler.takeAuction(999999999999999990000000000000000000000993018, 31506548945590221240114018464, 1016963456957222995035464545); + _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, false, 30294494991681513847857232418933803770638682537); + _reservePoolHandler.kickAuction(2324631542950979206383056100280239271207523734887421, 1, 23494016960770235530146856844201861803189848725938507629); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + } From 0c98c6785a7ea2aad750fa107830e1736f3bfa8c Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Wed, 29 Mar 2023 17:18:09 +0530 Subject: [PATCH 28/70] Add variable token precision for invariant tests (#709) * Add variable token precision for invariant tests * Fix CI build * Fixed reserve calculation in take auction with multi precision tokens * Add script to run 9 precision combinations for invariant tests * Update README * Pr feedback --------- Co-authored-by: grandizzy --- .github/workflows/forge-test.yml | 3 +++ Makefile | 17 +++++++++++------ test-invariant-erc20-precision.sh | 7 +++++++ tests/README.md | 12 ++++++++++-- .../ERC20Pool/invariants/BasicInvariants.t.sol | 6 ++++-- .../ERC20Pool/invariants/base/BaseHandler.sol | 10 +++++----- .../invariants/base/InvariantsTestBase.sol | 10 +++++----- .../base/UnboundedLiquidationPoolHandler.sol | 4 ++-- 8 files changed, 47 insertions(+), 22 deletions(-) create mode 100755 test-invariant-erc20-precision.sh diff --git a/.github/workflows/forge-test.yml b/.github/workflows/forge-test.yml index 5d5446cfa..094753398 100644 --- a/.github/workflows/forge-test.yml +++ b/.github/workflows/forge-test.yml @@ -8,6 +8,9 @@ env: jobs: check: + env: + QUOTE_PRECISION: 18 + COLLATERAL_PRECISION: 18 strategy: fail-fast: true diff --git a/Makefile b/Makefile index 326902781..c19f65ac6 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,10 @@ # (-include to ignore error if it does not exist) -include .env +# Default token precisions for invariant testing +QUOTE_PRECISION = 18 +COLLATERAL_PRECISION = 18 + all: clean install build # Clean the repo @@ -14,12 +18,13 @@ install :; git submodule update --init --recursive build :; forge clean && forge build # Tests -test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM -test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM -test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report -test-invariant :; forge t --mt invariant --nmc RegressionTest -test-regression :; forge t --mt test_regression -coverage :; forge coverage --no-match-test "testLoad|invariant" +test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM +test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM +test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report +test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest +test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression +coverage :; forge coverage --no-match-test "testLoad|invariant" +test-invariant-erc20-precision :; ./test-invariant-erc20-precision.sh # Generate Gas Snapshots snapshot :; forge clean && forge snapshot diff --git a/test-invariant-erc20-precision.sh b/test-invariant-erc20-precision.sh new file mode 100755 index 000000000..5ef95d260 --- /dev/null +++ b/test-invariant-erc20-precision.sh @@ -0,0 +1,7 @@ +for quote_precision in 6 8 18 +do + for collateral_precision in 6 8 18 + do + make test-invariant-erc20 QUOTE_PRECISION=${quote_precision} COLLATERAL_PRECISION=${collateral_precision} + done +done \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 2c5668b17..77fe0ae2b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,9 +12,17 @@ make test-with-gas-report ```bash make test-load ``` -- run invariant tests: +- run ERC20 Pool invariant tests: ```bash -make test-invariant +make test-invariant-erc20 +``` +- run ERC20 Pool invariant tests for specific quote and collateral token precision, default values (18, 18): +```bash +make test-invariant-erc20 QUOTE_PRECISION= COLLATERAL_PRECISION= +``` +- run ERC20 Pool invariant tests for most popular token precision combinations(6,8 and 18): +```bash +make test-invariant-erc20-precision ``` - generate code coverage report: ```bash diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 1f943135b..36c4fde06 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -164,7 +164,8 @@ contract BasicInvariants is InvariantsTestBase { // checks pool quote token balance is greater than equals total deposits in pool function invariant_quoteTokenBalance_QT1() public useCurrentTimestamp { - uint256 poolBalance = _quote.balanceOf(address(_pool)); + // convert pool quote balance into WAD + uint256 poolBalance = _quote.balanceOf(address(_pool)) * 10**(18 - _quote.decimals()); (uint256 poolDebt, , ) = _pool.debtInfo(); ( @@ -202,7 +203,8 @@ contract BasicInvariants is InvariantsTestBase { assertEq(_pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); - uint256 collateralBalance = _collateral.balanceOf(address(_pool)); + // convert pool collateral balance into WAD + uint256 collateralBalance = _collateral.balanceOf(address(_pool)) * 10**(18 - _collateral.decimals()); uint256 bucketCollateral; for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index 1a33a9767..d6f9245f4 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -16,7 +16,7 @@ import { MIN_PRICE } from 'src/libraries/helpers/PoolHelper.sol'; -import { Token, BurnableToken } from '../../../utils/Tokens.sol'; +import { TokenWithNDecimals, BurnableToken } from '../../../utils/Tokens.sol'; import 'src/libraries/internal/Maths.sol'; import '../interfaces/ITestBase.sol'; @@ -33,8 +33,8 @@ uint256 constant MAX_AMOUNT = 1e28; abstract contract BaseHandler is Test { // Tokens - Token internal _quote; - Token internal _collateral; + TokenWithNDecimals internal _quote; + TokenWithNDecimals internal _collateral; BurnableToken internal _ajna; @@ -82,8 +82,8 @@ abstract contract BaseHandler is Test { ) { // Tokens _ajna = BurnableToken(ajna_); - _quote = Token(quote_); - _collateral = Token(collateral_); + _quote = TokenWithNDecimals(quote_); + _collateral = TokenWithNDecimals(collateral_); // Pool _pool = ERC20Pool(pool_); diff --git a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol index 7ae7e4c60..b5f8751fe 100644 --- a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol +++ b/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol @@ -8,13 +8,13 @@ import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { Token, BurnableToken } from '../../../utils/Tokens.sol'; +import { TokenWithNDecimals, BurnableToken } from '../../../utils/Tokens.sol'; import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol'; abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { - Token internal _quote; - Token internal _collateral; + TokenWithNDecimals internal _quote; + TokenWithNDecimals internal _collateral; BurnableToken internal _ajna; @@ -35,8 +35,8 @@ abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { function setUp() public virtual { // Tokens _ajna = new BurnableToken("Ajna", "A"); - _quote = new Token("Quote", "Q"); - _collateral = new Token("Collateral", "C"); + _quote = new TokenWithNDecimals("Quote", "Q", uint8(vm.envUint("QUOTE_PRECISION"))); + _collateral = new TokenWithNDecimals("Collateral", "C", uint8(vm.envUint("COLLATERAL_PRECISION"))); // Pool _poolFactory = new ERC20PoolFactory(address(_ajna)); diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol index 3d15dd529..f1b639512 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol @@ -86,13 +86,13 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { (uint256 borrowerDebtBeforeTake, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); uint256 totalBondBeforeTake = _getKickerBond(kicker); - uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)); + uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)) * 10**(18 - _quote.decimals()); try _pool.take(borrower_, amount_, taker_, bytes("")) { (uint256 borrowerDebtAfterTake, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); uint256 totalBondAfterTake = _getKickerBond(kicker); - uint256 totalBalanceAfterTake = _quote.balanceOf(address(_pool)); + uint256 totalBalanceAfterTake = _quote.balanceOf(address(_pool)) * 10**(18 - _quote.decimals()); if (borrowerDebtBeforeTake > borrowerDebtAfterTake) { // **RE7**: Reserves decrease with debt covered by take. From accd51f9b01c897979aa98b6e8cbd8af1790fdb7 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 29 Mar 2023 15:28:57 +0300 Subject: [PATCH 29/70] Cleanup FIXMEs --- .../ERC20Pool/regression/RegressionTestLiquidation.t.sol | 6 ++---- .../forge/ERC20Pool/regression/RegressionTestReserves.t.sol | 2 +- tests/forge/ERC721Pool/ERC721PoolFactory.t.sol | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 343b33b11..3d70b75fb 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -102,8 +102,7 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_auction_taken_A6(); } - // FIXME: bucket take with 0 auction price - function _test_regression_invariant_exchange_rate_bucket_take_1() external { + function test_regression_invariant_exchange_rate_bucket_take_1() external { _liquidationPoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); _liquidationPoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); _liquidationPoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); @@ -112,8 +111,7 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // FIXME: bucket take with 0 auction price - function _test_regression_invariant_exchange_rate_bucket_take_2() external { + function test_regression_invariant_exchange_rate_bucket_take_2() external { _liquidationPoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); _liquidationPoolHandler.settleAuction(186120755740, 2, 59199623628501455128); _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index 679830c12..0f1ce00eb 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -379,7 +379,7 @@ contract RegressionTestReserve is ReserveInvariants { } // FIXME: Seems to be an issue with Deposits.mult() in accrue interest or some issue with timestamp in invariant setup - function _test_regression_kick_F1_f2() external { + function _test_regression_kick_F1_F2() external { _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); _reservePoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); _reservePoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); diff --git a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol index 942e59ef4..5947697a2 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol @@ -132,7 +132,6 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { assertEq(_factory.getNumberOfDeployedPools(), 3); } - // FIXME: This test is exceeding block gas limit function testDeployERC721CollectionPoolWithNonNFTAddress() external { // should revert if trying to deploy with non NFT _assertDeployWithNonNFTRevert({ From ec64742a1d80ac694ac27147269475fb7f63d881 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Wed, 29 Mar 2023 09:10:09 -0400 Subject: [PATCH 30/70] Added Precision to ENV file --- .env.example | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 39485e123..6dc2cf5ab 100644 --- a/.env.example +++ b/.env.example @@ -25,4 +25,8 @@ SOLC_VERSION=0.8.14 AJNA_TOKEN=0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5 # path to the JSON keystore file for your deployment account -DEPLOY_KEY= \ No newline at end of file +DEPLOY_KEY= + +# Default token precisions for (invariant) testing +QUOTE_PRECISION = 18 +COLLATERAL_PRECISION = 18 From 0889a4c8a0cc843c054bad93f8b9f1e7980750aa Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 30 Mar 2023 07:22:30 +0300 Subject: [PATCH 31/70] Update OZ contracts library to 4.8.2 (#711) --- lib/openzeppelin-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index ec825d899..d00acef40 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit ec825d8999538f110e572605dc56ef7bf44cc574 +Subproject commit d00acef4059807535af0bd0dd0ddf619747a044b From bd2d7c5d31afe27dbe772eb7a7f8357f6fb53636 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Thu, 30 Mar 2023 10:53:01 -0400 Subject: [PATCH 32/70] Cleaned up RewardManager contract (#710) # Description of change ## High level * Split `RewardsManager.t.sol` into -> `Rewards/RewardsManager.t.sol` & `Rewards/RewardDSTestPlus.sol` * No changes to `src` were made --------- Co-authored-by: Ian Harvey --- tests/forge/Rewards/RewardsDSTestPlus.sol | 504 +++++ tests/forge/Rewards/RewardsManager.t.sol | 1894 +++++++++++++++++++ tests/forge/RewardsManager.t.sol | 2048 --------------------- tests/forge/utils/DSTestPlus.sol | 8 +- 4 files changed, 2401 insertions(+), 2053 deletions(-) create mode 100644 tests/forge/Rewards/RewardsDSTestPlus.sol create mode 100644 tests/forge/Rewards/RewardsManager.t.sol delete mode 100644 tests/forge/RewardsManager.t.sol diff --git a/tests/forge/Rewards/RewardsDSTestPlus.sol b/tests/forge/Rewards/RewardsDSTestPlus.sol new file mode 100644 index 000000000..b282895c6 --- /dev/null +++ b/tests/forge/Rewards/RewardsDSTestPlus.sol @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import 'src/RewardsManager.sol'; +import 'src/PoolInfoUtils.sol'; +import 'src/PositionManager.sol'; + +import 'src/interfaces/rewards/IRewardsManager.sol'; +import 'src/interfaces/position/IPositionManager.sol'; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +import { Token } from '../utils/Tokens.sol'; +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { PositionManager } from 'src/PositionManager.sol'; + +import { ERC20HelperContract } from '../ERC20Pool/ERC20DSTestPlus.sol'; +import { IRewardsManagerEvents } from 'src/interfaces/rewards/IRewardsManagerEvents.sol'; + +abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContract { + + address internal _minterOne; + address internal _minterTwo; + address internal _minterThree; + address internal _minterFour; + address internal _minterFive; + + ERC20 internal _ajnaToken; + + IPool internal _poolTwo; + IRewardsManager internal _rewardsManager; + IPositionManager internal _positionManager; + + struct MintAndMemorializeParams { + uint256[] indexes; + address minter; + uint256 mintAmount; + IPool pool; + } + + struct TriggerReserveAuctionParams { + address borrower; + uint256 borrowAmount; + uint256 limitIndex; + IPool pool; + } + + function _stakeToken(address pool, address owner, uint256 tokenId) internal { + changePrank(owner); + + // approve and deposit NFT into rewards contract + PositionManager(address(_positionManager)).approve(address(_rewardsManager), tokenId); + vm.expectEmit(true, true, true, true); + emit Stake(owner, address(pool), tokenId); + _rewardsManager.stake(tokenId); + + // check token was transferred to rewards contract + (address ownerInf, address poolInf, ) = _rewardsManager.getStakeInfo(tokenId); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); + assertEq(ownerInf, owner); + assertEq(poolInf, pool); + } + + function _unstakeToken( + address owner, + address pool, + uint256[] memory claimedArray, + uint256 tokenId, + uint256 reward, + uint256 updateRatesReward + ) internal { + + changePrank(owner); + + vm.expectEmit(true, true, true, true); + emit ClaimRewards(owner, pool, tokenId, claimedArray, reward); + vm.expectEmit(true, true, true, true); + emit Unstake(owner, address(pool), tokenId); + _rewardsManager.unstake(tokenId); + return; + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); + + // check token was transferred from rewards contract to minter + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); + + // invariant: all bucket snapshots are removed for the token id that was unstaken + for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); + assertEq(lps, 0); + assertEq(rate, 0); + } + + (address owner, address pool, uint256 interactionBlock) = _rewardsManager.getStakeInfo(tokenId); + assertEq(owner, address(0)); + assertEq(pool, address(0)); + assertEq(interactionBlock, 0); + } + + function _assertBurn( + address pool, + uint256 epoch, + uint256 timestamp, + uint256 interest, + uint256 burned, + uint256 tokensToBurn, + uint256 rewardsToClaimer, + uint256 rewardsToUpdater + ) internal { + + (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); + + assertEq(bETimestamp, timestamp); + assertEq(bEInterest, interest); + assertEq(bEBurned, burned); + assertEq(burned, tokensToBurn); + assertEq(Maths.wmul(burned, 0.8 * 1e18), rewardsToClaimer); + assertEq(Maths.wmul(burned, 0.05 * 1e18), rewardsToUpdater); + } + + + function _updateExchangeRates( + address updater, + address pool, + uint256[] memory indexes, + uint256 reward + ) internal { + changePrank(updater); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(updater, pool, indexes, reward); + _rewardsManager.updateBucketExchangeRatesAndClaim(pool, indexes); + } + + + function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { + epochsClaimed_ = new uint256[](numberOfAuctions_); + uint256 claimEpoch = lastClaimed_; // starting index, not inclusive + + for (uint256 i = 0; i < numberOfAuctions_; i++) { + epochsClaimed_[i] = claimEpoch + 1; + claimEpoch += 1; + } + } + + function _claimRewards( + address from, + address pool, + uint256 tokenId, + uint256 reward, + uint256[] memory epochsClaimed + ) internal { + changePrank(from); + uint256 fromAjnaBal = _ajnaToken.balanceOf(from); + + uint256 currentBurnEpoch = IPool(pool).currentBurnEpoch(); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(from, pool, tokenId, epochsClaimed, reward); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + + assertEq(_ajnaToken.balanceOf(from), fromAjnaBal + reward); + } + + function _moveStakedLiquidity( + address from, + uint256 tokenId, + uint256[] memory fromIndexes, + bool fromIndStaked, + uint256[] memory toIndexes, + uint256 expiry + ) internal { + + changePrank(from); + + // check MoveLiquidity emits + for (uint256 i = 0; i < fromIndexes.length; ++i) { + vm.expectEmit(true, true, true, true); + emit MoveLiquidity(address(_rewardsManager), tokenId, fromIndexes[i], toIndexes[i]); + } + + vm.expectEmit(true, true, true, true); + emit MoveStakedLiquidity(tokenId, fromIndexes, toIndexes); + + if (fromIndStaked) { + // check exchange rates are updated + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_pool), toIndexes, 0); + } + _rewardsManager.moveStakedLiquidity(tokenId, fromIndexes, toIndexes, expiry); + + } + + function _assertNotOwnerOfDepositRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertNotOwnerOfDepositUnstakeRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertAlreadyClaimedRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertStake( + address owner, + address pool, + uint256 tokenId, + uint256 burnEvent, + uint256 rewardsEarned + ) internal { + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + (address ownerInf, address poolInf, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenId); + uint256 rewardsEarnedInf = _rewardsManager.calculateRewards(tokenId, currentBurnEpoch); + + assertEq(owner, ownerInf); + assertEq(pool, poolInf); + assertEq(burnEvent, interactionBurnEvent); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); + } + + +} + + + + +abstract contract RewardsHelperContract is RewardsDSTestPlus { + + address internal _bidder; + address internal _updater; + address internal _updater2; + + Token internal _collateralOne; + Token internal _quoteOne; + Token internal _collateralTwo; + Token internal _quoteTwo; + + constructor() { + vm.makePersistent(_ajna); + + _ajnaToken = ERC20(_ajna); + _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); + _rewardsManager = new RewardsManager(_ajna, _positionManager); + + _collateralOne = new Token("Collateral 1", "C1"); + _quoteOne = new Token("Quote 1", "Q1"); + _collateralTwo = new Token("Collateral 2", "C2"); + _quoteTwo = new Token("Quote 2", "Q2"); + + _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); + + // provide initial ajna tokens to staking rewards contract + deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); + } + + // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt + function _createTestBorrower(address pool, address borrower, uint256 borrowAmount, uint256 limitIndex) internal returns (uint256 collateralToPledge_) { + + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + // deal twice as much quote so the borrower has sufficient quote to repay the loan + deal(address(quote), borrower, Maths.wmul(borrowAmount, Maths.wad(2))); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + collateralToPledge_ = _requiredCollateral(borrowAmount, limitIndex); + deal(address(collateral), borrower, collateralToPledge_); + } + + function _triggerReserveAuctionsNoTake( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex + ) internal { + // create a new borrower to write state required for reserve auctions + uint256 collateralToPledge = _createTestBorrower(address(pool), borrower, borrowAmount, limitIndex); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, Maths.wdiv(borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + _startClaimableReserveAuction(address(pool), _bidder); + } + + function _startClaimableReserveAuction( + address pool, + address bidder + ) internal { + changePrank(bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + } + + function _mintAndMemorializePositionNFT( + address minter, + uint256 mintAmount, + address pool, + uint256[] memory indexes + ) internal returns (uint256 tokenId_) { + changePrank(minter); + + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + + // deal tokens to the minter + deal(address(collateral), minter, 250_000 * 1e18); + deal(address(quote), minter, mintAmount * indexes.length); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(minter, address(pool), keccak256("ERC20_NON_SUBSET_HASH")); + tokenId_ = _positionManager.mint(mintParams); + + uint256[] memory lpBalances = new uint256[](indexes.length); + + for (uint256 i = 0; i < indexes.length; i++) { + ERC20Pool(address(pool)).addQuoteToken(mintAmount, indexes[i], type(uint256).max); + (lpBalances[i], ) = ERC20Pool(address(pool)).lenderInfo(indexes[i], minter); + } + + ERC20Pool(address(pool)).increaseLPsAllowance(address(_positionManager), indexes, lpBalances); + + // construct memorialize params struct + IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( + tokenId_, indexes + ); + + _positionManager.memorializePositions(memorializeParams); + + // register position manager as lender at memorialized indexes (for LP test assertions) + _registerLender(address(_positionManager), indexes); + } + + function _triggerReserveAuctions( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex, + uint256 tokensToBurn + ) internal returns (uint256 tokensBurned_) { + + // fund borrower to write state required for reserve auctions + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + deal(address(quote), borrower, borrowAmount); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); + deal(address(_collateral), borrower, collateralToPledge); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // Can't trigger reserve auction if less than two weeks have passed since last auction + vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves(pool, _bidder); + + (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); + assertEq(tokensBurned_, tokensToBurn); + + return tokensBurned_; + } + + function _triggerReserveAuctionsBurnUnknown( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex + ) internal returns (uint256 tokensBurned_) { + + // fund borrower to write state required for reserve auctions + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + deal(address(quote), borrower, borrowAmount); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); + deal(address(_collateral), borrower, collateralToPledge); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // Can't trigger reserve auction if less than two weeks have passed since last auction + vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves(pool, _bidder); + + (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); + + return tokensBurned_; + } + + function _takeReserves(address pool, address from) internal { + changePrank(from); + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(pool); + + ERC20Pool(pool).takeReserves(curClaimableReservesRemaining); + } + + function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { + // calculate the required collateral based upon the borrow amount and index price + (uint256 interestRate, ) = pool_.interestRateInfo(); + uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient + uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); + requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; + } + + // // Helper function that returns a random subset from array + function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { + uint256[] memory copyOfArray = new uint256[](array.length); + for(uint j = 0; j < copyOfArray.length; j++){ + copyOfArray[j] = array[j]; + } + uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); + subsetArray = new uint256[](randomNoOfNfts); + for(uint256 i = 0; i < randomNoOfNfts; i++) { + uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); + subsetArray[i] = copyOfArray[randomIndex]; + copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; + } + } + + // Returns N addresses array + function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { + addresses_ = new address[](noOfAddress); + for(uint i = 0; i < noOfAddress; i++) { + addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); + } + } +} diff --git a/tests/forge/Rewards/RewardsManager.t.sol b/tests/forge/Rewards/RewardsManager.t.sol new file mode 100644 index 000000000..1759e84eb --- /dev/null +++ b/tests/forge/Rewards/RewardsManager.t.sol @@ -0,0 +1,1894 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import 'src/PoolInfoUtils.sol'; +import 'src/PositionManager.sol'; +import 'src/interfaces/rewards/IRewardsManager.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { RewardsHelperContract } from './RewardsDSTestPlus.sol'; + +contract RewardsManagerTest is RewardsHelperContract { + + address internal _borrower; + address internal _borrower2; + address internal _borrower3; + address internal _lender; + address internal _lender1; + + uint256 constant BLOCKS_IN_DAY = 7200; + mapping (uint256 => address) internal tokenIdToMinter; + mapping (address => uint256) internal minterToBalance; + + function setUp() external { + + // borrowers + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _borrower3 = makeAddr("borrower3"); + + _lender = makeAddr("lender"); + _lender1 = makeAddr("lender1"); + + // instantiate test minters + _minterOne = makeAddr("minterOne"); + _minterTwo = makeAddr("minterTwo"); + _minterThree = makeAddr("minterThree"); + _minterFour = makeAddr("minterFour"); + _minterFive = makeAddr("minterFive"); + + // instantiate test bidder + _bidder = makeAddr("bidder"); + deal(address(_ajna), _bidder, 900_000_000 * 10**18); + + vm.prank(_bidder); + _ajnaToken.approve(address(_pool), type(uint256).max); + vm.prank(_bidder); + ERC20(address(_quoteOne)).approve(address(_pool), type(uint256).max); + ERC20(address(_quoteTwo)).approve(address(_pool), type(uint256).max); + + // instantiate test updater + _updater = makeAddr("updater"); + _updater2 = makeAddr("updater2"); + + _mintCollateralAndApproveTokens(_borrower, 100 * 1e18); + _mintQuoteAndApproveTokens(_borrower, 200_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower2, 1_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower2, 200_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower3, 1_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower3, 200_000 * 1e18); + + _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); + _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); + + _mintQuoteAndApproveTokens(_minterOne, 500_000_000 * 1e18); + _mintQuoteAndApproveTokens(_minterTwo, 500_000_000 * 1e18); + } + + + function testStakeToken() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure NFT position two + depositIndexes = new uint256[](4); + depositIndexes[0] = 5; + depositIndexes[1] = 1; + depositIndexes[2] = 3; + depositIndexes[3] = 12; + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_poolTwo) + }); + + // check only owner of an NFT can deposit it into the rewards contract + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_poolTwo), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + } + + function testUnstakeToken() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // check only owner of an NFT can deposit it into the rewards contract + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // only owner should be able to withdraw the NFT + _assertNotOwnerOfDepositUnstakeRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + uint256[] memory claimedArray = new uint256[](0); + + _unstakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne, + claimedArray: claimedArray, // no rewards as no reserve auctions have occured + reward: 0, + updateRatesReward: 0 + }); + } + + function testUpdateExchangeRatesAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool), + tokensToBurn: 81.799378162662704349 * 1e18 + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + // check only deposit owner can claim rewards + _assertNotOwnerOfDepositRevert({ + from: _updater, + tokenId: tokenIdOne + }); + + // claim rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: 40.899689081331351737 * 1e18, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + + // check can't claim rewards twice + _assertAlreadyClaimedRevert({ + from: _minterOne, + tokenId: tokenIdOne + }); + + _assertStake({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + burnEvent: 1, + rewardsEarned: 40.899689081331351737 * 1e18 + }); + + // assertEq(_ajnaToken.balanceOf(_minterOne), 0); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18, + rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 4.089968908133135217 * 1e18 + }); + + skip(2 weeks); + + // check can't call update exchange rate after the update period has elapsed + uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + assertEq(updateRewards, 0); + } + + function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // epoch 0 - 1 is checked for rewards + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // first reserve auction happens successfully -> epoch 1 + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2_555, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18, + rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 4.089968908133135217 * 1e18 + }); + + + // second reserve auction happens successfully -> epoch 2 + tokensToBurn += _triggerReserveAuctions({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool), + tokensToBurn: 150.804205428530300439 * 1e18 + }); + + // check owner can withdraw the NFT and rewards will be automatically claimed + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(2, 0), + reward: 78.852344077558527015 * 1e18, + updateRatesReward: 4.173624213367915345 * 1e18 + }); + } + + function testWithdrawAndClaimRewardsNoReserveTake() external { + + // healthy epoch, bad epoch + + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // epoch 0 - 1 is checked for rewards + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + //call update exchange rate to enable claiming rewards for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + skip(2 weeks); + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + _triggerReserveAuctionsNoTake({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18, + rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 4.089968908133135217 * 1e18 + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 3.399633583097821167 * 1e18 + }); + } + + // two lenders stake their positions in the pool + // staker one bucket bankrupt, staker two bucket active + // interest accrued to both buckets, but staker one receives no rewards + function testClaimRewardsBankruptBucket() external { + + address[] memory transferors = new address[](1); + transferors[0] = address(_positionManager); + + changePrank(_minterOne); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + changePrank(_minterTwo); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + /*****************************/ + /*** Initialize Pool State ***/ + /*****************************/ + + // MinterOne adds Quote token accross 5 prices + _addInitialLiquidity({ + from: _minterOne, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 30_000 * 1e18, + index: _i9_52 + }); + + // first borrower adds collateral token and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); + + // second borrower adds collateral token and borrows + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 9_710 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); + + /*****************************/ + /*** Lenders Deposits NFTs ***/ + /*****************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](1); + uint256[] memory depositIndexes2 = new uint256[](1); + depositIndexes[0] = _i9_91; + depositIndexes2[0] = _i9_81; + + // ERC20Pool pool = ERC20Pool(address(_pool)); + + // stake NFT position one + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 2_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + + // stake NFT position two + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes2, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: address(_pool) + }); + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + + /***********************************/ + /*** Borrower Bankrupts A Bucket ***/ + /***********************************/ + + // Skip to make borrower two undercollateralized + skip(100 days); + + // all QT was inserted when minting NFT, provide more to kick + deal(address(_quote), _minterTwo, 10_000 * 1e18); + + _kick({ + from: _minterTwo, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + + // skip ahead so take can be called on the loan + skip(10 hours); + + // take entire collateral + _take({ + from: _minterTwo, + borrower: _borrower2, + maxCollateral: 1_000 * 1e18, + bondChange: 6.531114528261135360 * 1e18, + givenAmount: 653.111452826113536000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); + + _settle({ + from: _minterTwo, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 9_891.935520844277346922 * 1e18 + }); + + // bucket is insolvent, balances are reset + _assertBucket({ + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lower priced bucket isn't bankrupt, but exchange rate has decreased + _assertBucket({ + index: _i9_81, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 4_936.865619773958011818 * 1e18, + exchangeRate: 0.493686561977395801 * 1e18 + }); + + /***********************/ + /*** Reserve Auction ***/ + /***********************/ + + // skip some time to accumulate reserves + skip(50 days); + + // update pool reserves + _pool.updateInterest(); + + // start reserve auction + _startClaimableReserveAuction({ + pool: address(_pool), + bidder: _bidder + }); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(address(_pool)); + + // take claimable reserves + changePrank(_bidder); + _pool.takeReserves(curClaimableReservesRemaining); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + + // _minterOne withdraws and claims rewards, rewards should be 0 + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + updateRatesReward: 0 + }); + + // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + updateRatesReward: 0 + }); + } + + function testClaimRewardsCap() external { + skip(10); + + /***************************/ + /*** Lender Deposits NFT ***/ + /***************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](2); + uint256[] memory depositIndex1 = new uint256[](1); + uint256[] memory depositIndex2 = new uint256[](1); + depositIndexes[0] = 2770; + depositIndexes[1] = 2771; + depositIndex1[0] = 2771; + depositIndex2[0] = 2770; + + // configure NFT position one + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 10_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /************************************/ + /*** Borrower One Accrue Interest ***/ + /************************************/ + + // borrower borrows + (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower, 10_000 * 1e18, 2770); + + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 5 * 1e18, + limitIndex: 2770, + collateralToPledge: collateralToPledge, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // pass time to allow interest to accrue + skip(2 hours); + + // borrower repays their loan + (uint256 debt, , ) = _pool.borrowerInfo(_borrower); + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: debt, + amountRepaid: 5.004807692307692310 * 1e18, + collateralToPull: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + // start reserve auction + _startClaimableReserveAuction({ + pool: address(_pool), + bidder: _bidder + }); + + // _borrower now takes out more debt to accumulate more interest + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 2_000 * 1e18, + limitIndex: 2770, + collateralToPledge: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves({ + pool: address(_pool), + from: _bidder + }); + + // recorder updates the change in exchange rates in the first index + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndex1, + reward: 0.007104600671645296 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); + + _assertBurn({ + pool: address(_pool), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0, + rewardsToClaimer: 0, + rewardsToUpdater: 0, + tokensToBurn: 0 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 0.284184026893324971 * 1e18, + interest: 0.000048562908902619 * 1e18, + tokensToBurn: 0.284184026893324971 * 1e18, + rewardsToClaimer: 0.227347221514659977 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 0.014209201344666249 * 1e18 + }); + + // skip more time to allow more interest to accrue + skip(10 days); + + // borrowe_r repays their loan again + (debt, , ) = _pool.borrowerInfo(_borrower); + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: debt, + amountRepaid: 2001.900281182536528586 * 1e18, + collateralToPull: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // recorder updates the change in exchange rates in the second index + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndex2, + reward: 0.021313802017687201 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); + + + /*******************************************/ + /*** Lender Withdraws And Claims Rewards ***/ + /*******************************************/ + + // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0.298393228234161298 * 1e18, + updateRatesReward: 0 + }); + } + + function testMultiPeriodRewardsSingleClaim() external { + skip(10); + + uint256 totalTokensBurned; + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](10); + depositIndexes[0] = 5995; + depositIndexes[1] = 5996; + depositIndexes[2] = 5997; + depositIndexes[3] = 5998; + depositIndexes[4] = 5999; + depositIndexes[5] = 6000; + depositIndexes[6] = 6001; + depositIndexes[7] = 6002; + depositIndexes[8] = 6003; + depositIndexes[9] = 6004; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 408.996890813313521802 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 20.449844540665683990 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); + + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 204.498445406656758711 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + // trigger second reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 753.761937534761336922 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 17.238252336072314416 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /*****************************/ + /*** Third Reserve Auction ***/ + /*****************************/ + + // trigger third reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1_034.145224534232837796 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // skip updating exchange rates and check available rewards + uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // snapshot calling update exchange rate + uint256 snapshot = vm.snapshot(); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 14.019164349973606335 * 1e18 + }); + + assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); // assertEq(rewardsEarned, 517.072612267115797118 * 1e18); + assertGt(rewardsEarned, rewardsEarnedNoUpdate); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // revert to no update state + vm.revertTo(snapshot); + + /******************************/ + /*** Fourth Reserve Auction ***/ + /******************************/ + + // triger fourth reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1_289.513636917170056104 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // check rewards earned + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 0 + }); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + + // check rewards earned won't increase since previous update was missed + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + + /*****************************/ + /*** Fifth Reserve Auction ***/ + /*****************************/ + + // triger fifth reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1521.830620022508618175 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 11.615849155266905357 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); + + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 493.039460320049732206 * 1e18); + + // claim all rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(5,0), + reward: 493.039460320049732206 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + } + + function testMoveStakedLiquidity() external { + skip(10); + + /*****************/ + /*** Stake NFT ***/ + /*****************/ + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + // configure NFT position + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // stake nft + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + _updateExchangeRates({ + updater: _minterOne, + pool: address(_pool), + indexes: depositIndexes, + reward: 0 + }); + + + uint256[] memory secondIndexes = new uint256[](5); + secondIndexes[0] = 2556; + secondIndexes[1] = 2557; + secondIndexes[2] = 2558; + secondIndexes[3] = 2559; + secondIndexes[4] = 2560; + + _moveStakedLiquidity({ + from: _minterOne, + tokenId: tokenIdOne, + fromIndexes: depositIndexes, + fromIndStaked: false, + toIndexes: secondIndexes, + expiry: block.timestamp + 1000 + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // first reserve auction happens successfully -> epoch 1 + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 462.029442387557878852 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2560, + pool: address(_pool) + }); + + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets + //secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); //TODO: investigate why commenting this out or keeping it doesn't do anything + + _moveStakedLiquidity({ + from: _minterOne, + tokenId: tokenIdOne, + fromIndexes: secondIndexes, + fromIndStaked: true, + toIndexes: depositIndexes, + expiry: block.timestamp + 1000 + }); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + + // second reserve auction happens successfully -> epoch 1 + _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 529.505418197285009563 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + /******************************/ + /*** Exchange Rates Updated ***/ + /******************************/ + + // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets + // depositIndexes = _positionManager.getPositionIndexes(tokenIdOne); //TODO: investigate why commenting this out or keeping it doesn't do anything + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 3.373278067656485050 * 1e18 + }); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,1), + reward: 33.732780676564838905 * 1e18 + }); + } + + function testEarlyAndLateStakerRewards() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + // configure NFT position two + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // bucket exchange rates are not changed at the time minter two stakes + assertEq(_pool.bucketExchangeRate(2550), 1e18); + assertEq(_pool.bucketExchangeRate(2551), 1e18); + assertEq(_pool.bucketExchangeRate(2552), 1e18); + assertEq(_pool.bucketExchangeRate(2553), 1e18); + assertEq(_pool.bucketExchangeRate(2555), 1e18); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + + (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower2, 10_000 * 1e18, 2770); + + // borrower borrows and change the exchange rates of buckets + _drawDebt({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 5 * 1e18, + limitIndex: 2770, + collateralToPledge: collateralToPledge, + newLup: 3_010.892022197881557845 * 1e18 + }); + + skip(1 days); + + // configure NFT position three one day after early minter + uint256 tokenIdThree = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // bucket exchange rates are higher at the time minter three stakes + assertEq(_pool.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterThree, + tokenId: tokenIdThree + }); + + skip(1 days); + + _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 602.051131214993198872 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + // unstake and compare rewards and balances of minter two and minter three + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 266.615874256912407614 * 1e18, + updateRatesReward: 44.433744918714141439 * 1e18 + }); + + uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); + assertEq(minterTwoBalance, 266.615874256912407614 * 1e18); + + _unstakeToken({ + owner: _minterThree, + pool: address(_pool), + tokenId: tokenIdThree, + claimedArray: _epochsClaimedArray(1, 0), + reward: 78.843436266764514308 * 1e18, + updateRatesReward: 0 + }); + uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); + assertEq(minterThreeBalance, 78.843436266764514308 * 1e18); + + assertGt(minterTwoBalance, minterThreeBalance); + } + + // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves + function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { + skip(10); + + /*****************************/ + /*** First Lender Deposits ***/ + /*****************************/ + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + uint256 firstTokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + /******************************/ + /*** Second Lender Deposits ***/ + /******************************/ + + // second depositor deposits an NFT representing the same positions into the rewards contract + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // second depositor stakes NFT, generating an update reward + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.154797961459423073 * 1e18); + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtOne, firstTokensToBurn); + assertGt(idOneRewardsAtOne, 1); + + // minter one claims rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,0), + reward: idOneRewardsAtOne + }); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + // // borrower takes actions providing reserves enabling additional reserve auctions + // conduct second reserve auction + uint256 secondTokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 176.189760225502880454 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + /*****************************/ + /*** Third Lender Deposits ***/ + /*****************************/ + + // third depositor deposits an NFT representing the same positions into the rewards contract + uint256 tokenIdThree = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterThree, + tokenId: tokenIdThree + }); + + /***********************/ + /*** Rewards Claimed ***/ + /***********************/ + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo, secondTokensToBurn); + assertGt(idOneRewardsAtTwo, 0); + assertEq(idOneRewardsAtTwo, 23.615632136163281168 * 1e18); + + uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, secondTokensToBurn); + assertEq(idTwoRewardsAtTwo, 23.583081554200477722 * 1e18); + assertGt(idTwoRewardsAtTwo, 0); + + // minter one claims rewards accrued after second auction + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,1), + reward: 23.615632136163281168 * 1e18 + }); + + assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); + + // minter two claims rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterTwo, + tokenId: tokenIdTwo, + epochsClaimed: _epochsClaimedArray(1,1), + reward: idTwoRewardsAtTwo + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 31.737879515659900795 * 1e18); + + // check there are no remaining rewards available after claiming + uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + } + + function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { + // configure _minterOne's NFT position + uint256[] memory depositIndexesMinterOne = new uint256[](5); + depositIndexesMinterOne[0] = 2550; + depositIndexesMinterOne[1] = 2551; + depositIndexesMinterOne[2] = 2552; + depositIndexesMinterOne[3] = 2553; + depositIndexesMinterOne[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexesMinterOne, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure _minterTwo's NFT position + uint256[] memory depositIndexesMinterTwo = new uint256[](5); + depositIndexesMinterTwo[0] = 2550; + depositIndexesMinterTwo[1] = 2551; + depositIndexesMinterTwo[2] = 2200; + depositIndexesMinterTwo[3] = 2221; + depositIndexesMinterTwo[4] = 2222; + + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexesMinterTwo, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: address(_pool) + }); + + // lenders stake their NFTs + _stakeToken(address(_pool), _minterOne, tokenIdOne); + _stakeToken(address(_pool), _minterTwo, tokenIdTwo); + + uint256[] memory depositIndexes = new uint256[](8); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + depositIndexes[5] = 2200; + depositIndexes[6] = 2221; + depositIndexes[7] = 2222; + + // borrower takes actions providing reserves enabling three reserve auctions + // proof of burn events (burn epoch 0) + _assertBurn({ + pool: address(_pool), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0, + rewardsToClaimer: 0, + rewardsToUpdater: 0, + tokensToBurn: 0 + }); + + // auction one + uint256 tokensToBurnE1 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162663471460 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133195708 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162663471460 * 1e18, + tokensToBurn: tokensToBurnE1, + interest: 6.443638300196908069 * 1e18, + rewardsToClaimer: 65.439502530130777168 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 4.089968908133173573 * 1e18 + }); + + // auction two + uint256 tokensToBurnE2 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 308.672122067616182565 * 1e18, + borrowAmount: 1_000 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 11.343637195247653990 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 2, + timestamp: block.timestamp - 24 hours, + burned: 308.672122067616182565 * 1e18, + tokensToBurn: tokensToBurnE2, + interest: 23.936044239893182713 * 1e18, + rewardsToClaimer: 246.937697654092946052 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 15.433606103380809128 * 1e18 + }); + + // auction three + uint256 tokensToBurnE3 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 676.658832743043390869 * 1e18, + borrowAmount: 2_000 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 18.399335533771072810 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 3, + timestamp: block.timestamp - 24 hours, + burned: 676.658832743043390869 * 1e18, + tokensToBurn: tokensToBurnE3, + interest: 52.421031459480795903 * 1e18, + rewardsToClaimer: 541.327066194434712695 * 1e18, // FIXME: These don't add up to total burned, is that a problem? + rewardsToUpdater: 33.832941637152169543 * 1e18 + }); + + // both stakers claim rewards + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(3, 0), + reward: 51.511621814050026070 * 1e18, + updateRatesReward: 0 + }); + + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(3, 0), + reward: 286.817794557471160699 * 1e18, + updateRatesReward: 0 + }); + } + + + function testWithdrawAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // stake nft + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + // check owner can withdraw the NFT and rewards will be automatically claimed + + uint256 snapshot = vm.snapshot(); + + // claimed rewards amount is greater than available tokens in rewards manager contract + + // burn rewards manager tokens and leave only 5 tokens available + changePrank(address(_rewardsManager)); + IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); + + uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); + assertEq(managerBalance, 4.931444746461913452 * 1e18); + + // _minterOne unstakes staked position + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 40.899689081331351737 * 1e18, + updateRatesReward: 0 + }); + + // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 + assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); + // all 5 tokens available in manager balance were used to reward minter one + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); + + vm.revertTo(snapshot); + + // test when enough tokens in rewards manager contracts + // _minterOne unstakes staked position + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 40.899689081331351737 * 1e18, + updateRatesReward: 0 + }); + + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenIdOne), _minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331351737 * 1e18); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + + // check can't claim rewards twice + _assertNotOwnerOfDepositRevert({ + from: _minterOne, + tokenId: tokenIdOne + }); + } + + function testMultiplePools() external { + skip(10); + + // configure NFT position one + uint256[] memory firstIndexes = new uint256[](5); + firstIndexes[0] = 9; + firstIndexes[1] = 1; + firstIndexes[2] = 2; + firstIndexes[3] = 3; + firstIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: firstIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure NFT position two + uint256[] memory secondIndexes = new uint256[](4); + secondIndexes[0] = 5; + secondIndexes[1] = 1; + secondIndexes[2] = 3; + secondIndexes[3] = 12; + + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: secondIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_poolTwo) + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + // check only deposit owner can claim rewards + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // check rewards earned in one pool shouldn't be claimable by depositors from another pool + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + _claimRewards({ + pool: address(_poolTwo), + from: _minterTwo, + tokenId: tokenIdTwo, + reward: 0, + epochsClaimed: _epochsClaimedArray(0, 0) + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _minterOne, + pool: address(_pool), + indexes: firstIndexes, + reward: 4.089968908133134138 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133134138 * 1e18); + + // check owner in pool with accrued interest can properly claim rewards + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: 40.899689081331351737 * 1e18, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + + } + + /********************/ + /*** FUZZ TESTING ***/ + /********************/ + + function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { + indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to + mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](indexes); + for (uint256 i = 0; i < indexes; ++i) { + depositIndexes[i] = _randomIndex(); + } + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: mintAmount, + pool: address(_pool) + }); + + // stake NFT + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + // calculates a limit index leaving one index above the htp to accrue interest + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + + // start and end new reserve auction + uint256 tokensToBurn= _triggerReserveAuctionsBurnUnknown({ + borrower: _borrower, + borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), + limitIndex: limitIndex, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + assertGt(_ajnaToken.balanceOf(_updater), 0); + + // calculate rewards earned and compare to percentages for updating and claiming + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertGt(rewardsEarned, 0); + + // claim rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: rewardsEarned, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + + // assert rewards claimed is less than ajna tokens burned cap + assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); + } + + function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { + deposits = bound(deposits, 1, 25); // number of deposits to make + reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete + + uint256[] memory tokenIds = new uint256[](deposits); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](3); + for (uint256 j = 0; j < 3; ++j) { + depositIndexes[j] = _randomIndex(); + vm.roll(block.number + 1); // advance block to ensure that the index price is different + } + + address[] memory minters = _getAddresses(deposits); + + // stake variable no of deposits + for(uint256 i = 0; i < deposits; ++i) { + // mint and memorilize Positions + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: minters[i], + mintAmount: 1_000_000_000 * 1e18, + pool: _pool + }); + + tokenIds[i] = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: minters[i], + mintAmount: 1_000_000_000 * 1e18, + pool: address(_pool) + }); + tokenIdToMinter[tokenIds[i]] = minters[i]; + _stakeToken(address(_pool), minters[i], tokenIds[i]); + } + + uint256 updaterBalance = _ajnaToken.balanceOf(_updater); + + for(uint i = 0; i < deposits; i++) { + minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); + } + + // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch + for(uint i = 0; i < reserveAuctions; ++i) { + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + + // start and end new reserve auction + uint256 tokensBurned = _triggerReserveAuctionsBurnUnknown({ + borrower: _borrower, + borrowAmount: 10_000 * 1e18, + limitIndex: limitIndex, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); + + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + + // ensure updater gets reward for updating exchange rate + assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); + + // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned + assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); + + updaterBalance = _ajnaToken.balanceOf(_updater); + + // pick random NFTs from all NFTs to claim rewards + uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); + + for(uint j = 0; j < randomNfts.length; j++) { + address minterAddress = tokenIdToMinter[randomNfts[j]]; + changePrank(minterAddress); + + (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); + + // select random epoch to claim reward + uint256 epochToClaim = lastInteractionEpoch < _pool.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _pool.currentBurnEpoch()) : lastInteractionEpoch; + + uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); + assertGt(rewardsEarned, 0); + + _rewardsManager.claimRewards(randomNfts[j], _pool.currentBurnEpoch()); + + // ensure user gets reward + assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); + minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); + } + } + } + + function testClaimRewardsFreezeUnclaimedYield() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + + changePrank(_minterOne); + // should revert if the epoch to claim is not available yet + vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); + + // user should be able to claim rewards for current epoch + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + } + +} diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol deleted file mode 100644 index 82ff3b40b..000000000 --- a/tests/forge/RewardsManager.t.sol +++ /dev/null @@ -1,2048 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; - -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; - -import 'src/RewardsManager.sol'; -import 'src/interfaces/rewards/IRewardsManager.sol'; - -import 'src/interfaces/position/IPositionManager.sol'; -import 'src/PositionManager.sol'; -import 'src/PoolInfoUtils.sol'; -import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; - -import { _borrowFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; - -import { Token } from './utils/Tokens.sol'; -import { ERC20HelperContract } from './ERC20Pool/ERC20DSTestPlus.sol'; - -contract RewardsManagerTest is ERC20HelperContract { - - address internal _bidder; - address internal _minterOne; - address internal _minterTwo; - address internal _minterThree; - address internal _minterFour; - address internal _minterFive; - address internal _updater; - address internal _updater2; - - ERC20 internal _ajnaToken; - - RewardsManager internal _rewardsManager; - PositionManager internal _positionManager; - - Token internal _collateralOne; - Token internal _quoteOne; - ERC20Pool internal _poolOne; - Token internal _collateralTwo; - Token internal _quoteTwo; - ERC20Pool internal _poolTwo; - - event ClaimRewards(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId, uint256[] epochsClaimed, uint256 amount); - event Stake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); - event UpdateExchangeRates(address indexed caller, address indexed ajnaPool, uint256[] indexesUpdated, uint256 rewardsClaimed); - event Unstake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); - event MoveStakedLiquidity( - uint256 tokenId, - uint256[] fromIndexes, - uint256[] toIndexes - ); - - uint256 constant BLOCKS_IN_DAY = 7200; - mapping (uint256 => address) internal tokenIdToMinter; - mapping (address => uint256) internal minterToBalance; - - struct MintAndMemorializeParams { - uint256[] indexes; - address minter; - uint256 mintAmount; - ERC20Pool pool; - } - - struct TriggerReserveAuctionParams { - uint256 borrowAmount; - uint256 limitIndex; - ERC20Pool pool; - } - - function setUp() external { - vm.makePersistent(_ajna); - - _ajnaToken = ERC20(_ajna); - _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); - _rewardsManager = new RewardsManager(_ajna, _positionManager); - _poolUtils = new PoolInfoUtils(); - - _collateralOne = new Token("Collateral 1", "C1"); - _quoteOne = new Token("Quote 1", "Q1"); - _poolOne = ERC20Pool(_poolFactory.deployPool(address(_collateralOne), address(_quoteOne), 0.05 * 10**18)); - - _collateralTwo = new Token("Collateral 2", "C2"); - _quoteTwo = new Token("Quote 2", "Q2"); - _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); - - // provide initial ajna tokens to staking rewards contract - deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); - - // instantiate test minters - _minterOne = makeAddr("minterOne"); - _minterTwo = makeAddr("minterTwo"); - _minterThree = makeAddr("minterThree"); - _minterFour = makeAddr("minterFour"); - _minterFive = makeAddr("minterFive"); - - // instantiate test bidder - _bidder = makeAddr("bidder"); - changePrank(_bidder); - deal(_ajna, _bidder, 900_000_000 * 10**18); - - // instantiate test updater - _updater = makeAddr("updater"); - _updater2 = makeAddr("updater2"); - } - - // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt - function _createTestBorrower(ERC20Pool pool_, string memory borrowerName_, uint256 borrowAmount_, uint256 limitIndex_) internal returns (address borrower_, uint256 collateralToPledge_) { - borrower_ = makeAddr(borrowerName_); - - changePrank(borrower_); - - Token collateral = Token(pool_.collateralAddress()); - Token quote = Token(pool_.quoteTokenAddress()); - - // deal twice as much quote so the borrower has sufficient quote to repay the loan - deal(address(quote), borrower_, Maths.wmul(borrowAmount_, Maths.wad(2))); - - // approve tokens - collateral.approve(address(pool_), type(uint256).max); - quote.approve(address(pool_), type(uint256).max); - - collateralToPledge_ = _requiredCollateral(pool_, borrowAmount_, limitIndex_); - deal(address(collateral), borrower_, collateralToPledge_); - } - - function _stakeToken(address pool_, address owner_, uint256 tokenId_) internal { - changePrank(owner_); - - // approve and deposit NFT into rewards contract - _positionManager.approve(address(_rewardsManager), tokenId_); - vm.expectEmit(true, true, true, true); - emit Stake(owner_, address(pool_), tokenId_); - _rewardsManager.stake(tokenId_); - - // check token was transferred to rewards contract - assertEq(_positionManager.ownerOf(tokenId_), address(_rewardsManager)); - } - - function _unstakeToken( - address minter, - address pool, - uint256[] memory claimedArray, - uint256 tokenId, - uint256 reward, - uint256 updateRatesReward - ) internal { - - changePrank(minter); - - if (updateRatesReward != 0) { - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), _positionManager.getPositionIndexes(tokenId), updateRatesReward); - } - - vm.expectEmit(true, true, true, true); - emit ClaimRewards(minter, pool, tokenId, claimedArray, reward); - vm.expectEmit(true, true, true, true); - emit Unstake(minter, address(pool), tokenId); - _rewardsManager.unstake(tokenId); - assertEq(_positionManager.ownerOf(tokenId), minter); - - // check token was transferred from rewards contract to minter - assertEq(_positionManager.ownerOf(tokenId), address(minter)); - - // invariant: all bucket snapshots are removed for the token id that was unstaken - for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { - (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); - assertEq(lps, 0); - assertEq(rate, 0); - } - } - - function _triggerReserveAuctionsNoTake(TriggerReserveAuctionParams memory params_) internal { - // create a new borrower to write state required for reserve auctions - ( - address borrower, - uint256 collateralToPledge - ) = _createTestBorrower(params_.pool, string("borrower"), params_.borrowAmount, params_.limitIndex); - - // borrower drawsDebt from the pool - params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, Maths.wdiv(params_.borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(params_.pool), type(uint256).max); - params_.pool.startClaimableReserveAuction(); - } - - function _assertBurn( - address pool, - uint256 epoch, - uint256 timestamp, - uint256 interest, - uint256 burned - ) internal { - - (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); - - assertEq(bETimestamp, timestamp); - assertEq(bEInterest, interest); - assertEq(bEBurned, burned); - } - - - function _updateExchangeRates(address updater, address pool, uint256[] memory depositIndexes, uint256 reward) internal { - uint256 initialUpdaterTokenBalance = _ajnaToken.balanceOf(updater); - - changePrank(updater); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(updater, pool, depositIndexes, reward); - _rewardsManager.updateBucketExchangeRatesAndClaim(pool, depositIndexes); - assertEq(_ajnaToken.balanceOf(updater), initialUpdaterTokenBalance + reward); - } - - - function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { - epochsClaimed_ = new uint256[](numberOfAuctions_); - uint256 claimEpoch = lastClaimed_; // starting index, not inclusive - - for (uint256 i = 0; i < numberOfAuctions_; i++) { - epochsClaimed_[i] = claimEpoch + 1; - claimEpoch += 1; - } - } - - function _mintAndMemorializePositionNFT(MintAndMemorializeParams memory params_) internal returns (uint256 tokenId_) { - changePrank(params_.minter); - - Token collateral = Token(params_.pool.collateralAddress()); - Token quote = Token(params_.pool.quoteTokenAddress()); - - // deal tokens to the minter - deal(address(collateral), params_.minter, 250_000 * 1e18); - deal(address(quote), params_.minter, params_.mintAmount * params_.indexes.length); - - // approve tokens - collateral.approve(address(params_.pool), type(uint256).max); - quote.approve(address(params_.pool), type(uint256).max); - - IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(params_.minter, address(params_.pool), keccak256("ERC20_NON_SUBSET_HASH")); - tokenId_ = _positionManager.mint(mintParams); - - uint256[] memory lpBalances = new uint256[](params_.indexes.length); - - for (uint256 i = 0; i < params_.indexes.length; i++) { - params_.pool.addQuoteToken(params_.mintAmount, params_.indexes[i], type(uint256).max); - (lpBalances[i], ) = params_.pool.lenderInfo(params_.indexes[i], params_.minter); - } - - params_.pool.increaseLPsAllowance(address(_positionManager), params_.indexes, lpBalances); - - // construct memorialize params struct - IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( - tokenId_, params_.indexes - ); - - _positionManager.memorializePositions(memorializeParams); - - // register position manager as lender at memorialized indexes (for LP test assertions) - _registerLender(address(_positionManager), params_.indexes); - } - - function _triggerReserveAuctions(TriggerReserveAuctionParams memory params_) internal returns (uint256 tokensBurned_) { - // create a new borrower to write state required for reserve auctions - address borrower = makeAddr("borrower"); - - changePrank(borrower); - - Token collateral = Token(params_.pool.collateralAddress()); - Token quote = Token(params_.pool.quoteTokenAddress()); - - deal(address(quote), borrower, params_.borrowAmount); - - // approve tokens - collateral.approve(address(params_.pool), type(uint256).max); - quote.approve(address(params_.pool), type(uint256).max); - - uint256 collateralToPledge = _requiredCollateral(params_.pool, params_.borrowAmount, params_.limitIndex); - deal(address(collateral), borrower, collateralToPledge); - - // borrower drawsDebt from the pool - params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, params_.borrowAmount, 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(params_.pool), type(uint256).max); - params_.pool.startClaimableReserveAuction(); - - // Can't trigger reserve auction if less than two weeks have passed since last auction - vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - params_.pool.startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(params_.pool)); - - // take claimable reserves - params_.pool.takeReserves(curClaimableReservesRemaining); - - (,, tokensBurned_) = IPool(params_.pool).burnInfo(IPool(params_.pool).currentBurnEpoch()); - - return tokensBurned_; - } - - function testStakeToken() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // configure NFT position two - depositIndexes = new uint256[](4); - depositIndexes[0] = 5; - depositIndexes[1] = 1; - depositIndexes[2] = 3; - depositIndexes[3] = 12; - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolTwo - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // check only owner of an NFT can deposit it into the rewards contract - changePrank(_minterTwo); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.stake(tokenIdOne); - - // minterOne deposits their NFT into the rewards contract - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - // check deposit state - (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, _minterOne); - assertEq(pool, address(_poolOne)); - assertEq(interactionBurnEvent, 0); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); - // check deposit state - (owner, pool, interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdTwo); - assertEq(owner, _minterTwo); - assertEq(pool, address(_poolTwo)); - assertEq(interactionBurnEvent, 0); - } - - function testUpdateExchangeRatesAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 4.089968908133149070 * 1e18); - - // check only deposit owner can claim rewards - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - - // check rewards earned - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); - assertEq(rewardsEarned, 40.899689081331421210 * 1e18); - - // claim rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - - // check can't claim rewards twice - vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - - // check deposit state - (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, _minterOne); - assertEq(pool, address(_poolOne)); - assertEq(interactionBurnEvent, 1); - assertEq(_positionManager.ownerOf(tokenIdOne), address(_rewardsManager)); - - // assert rewards claimed is less than ajna tokens burned cap - assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); - - // check can't call update exchange rate after the update period has elapsed - skip(2 weeks); - - // Although an `UpdateExchangeRates` is emmited the rates are not updated, as demonstrated by updateRewards == 0 - uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(updateRewards, 0); - } - - function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // epoch 0 - 1 is checked for rewards - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - // first reserve auction happens successfully -> epoch 1 - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133149070 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 81.799378162662881374 * 1e18, - interest: 6.443638300196908069 * 1e18 - }); - - // second reserve auction happens successfully -> epoch 2 - tokensToBurn += _triggerReserveAuctions(triggerReserveAuctionParams); - - // check owner can withdraw the NFT and rewards will be automatically claimed - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(2, 0), - reward: 86.809555428378605150 * 1e18, - updateRatesReward: 4.173624213367915365 * 1e18 - }); - } - - function testWithdrawAndClaimRewardsNoReserveTake() external { - - // healthy epoch, bad epoch - - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // epoch 0 - 1 is checked for rewards - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - //call update exchange rate to enable claiming rewards for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133149070 * 1e18 - }); - - skip(2 weeks); - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - _triggerReserveAuctionsNoTake(triggerReserveAuctionParams); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), - burned: 81.799378162662881374 * 1e18, - interest: 6.443638300196908069 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 2, - timestamp: block.timestamp, - burned: 0, - interest: 0 - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.206490995172302285 * 1e18 - }); - } - - // two lenders stake their positions in the pool - // staker one bucket bankrupt, staker two bucket active - // interest accrued to both buckets, but staker one receives no rewards - function testClaimRewardsBankruptBucket() external { - - address borrower = makeAddr("borrower"); - address borrowerTwo = makeAddr("borrowerTwo"); - - deal(address(_collateral), borrower, 4 * 1e18); - changePrank(borrower); - _collateral.approve(address(_pool), type(uint256).max); - _quote.approve(address(_pool), type(uint256).max); - - deal(address(_collateral), borrowerTwo, 1_000 * 1e18); - changePrank(borrowerTwo); - _collateral.approve(address(_pool), type(uint256).max); - _quote.approve(address(_pool), type(uint256).max); - - address[] memory transferors = new address[](1); - transferors[0] = address(_positionManager); - - changePrank(_minterOne); - deal(address(_quote), _minterOne, 500_000_000 * 1e18); - _quote.approve(address(_pool), type(uint256).max); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - changePrank(_minterTwo); - deal(address(_quote), _minterTwo, 500_000_000 * 1e18); - _quote.approve(address(_pool), type(uint256).max); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - /*****************************/ - /*** Initialize Pool State ***/ - /*****************************/ - - // Lender adds Quote token accross 5 prices - _addInitialLiquidity({ - from: _minterOne, - amount: 2_000 * 1e18, - index: _i9_91 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 5_000 * 1e18, - index: _i9_81 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 11_000 * 1e18, - index: _i9_72 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 25_000 * 1e18, - index: _i9_62 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 30_000 * 1e18, - index: _i9_52 - }); - - // first borrower adds collateral token and borrows - _pledgeCollateral({ - from: borrower, - borrower: borrower, - amount: 2 * 1e18 - }); - _borrow({ - from: borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - }); - - // second borrower adds collateral token and borrows - _pledgeCollateral({ - from: borrowerTwo, - borrower: borrowerTwo, - amount: 1_000 * 1e18 - }); - _borrow({ - from: borrowerTwo, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - }); - - _borrow({ - from: borrowerTwo, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - }); - - /*****************************/ - /*** Lenders Deposits NFTs ***/ - /*****************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](1); - uint256[] memory depositIndexes2 = new uint256[](1); - depositIndexes[0] = _i9_91; - depositIndexes2[0] = _i9_81; - - ERC20Pool pool = ERC20Pool(address(_pool)); - - // stake NFT position one - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 2_000 * 1e18, - pool: pool - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterOne); - _stakeToken(address(pool), _minterOne, tokenIdOne); - - // stake NFT position two - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes2, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: pool - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterTwo); - _stakeToken(address(pool), _minterTwo, tokenIdTwo); - - /***********************************/ - /*** Borrower Bankrupts A Bucket ***/ - /***********************************/ - - // Skip to make borrower two undercollateralized - skip(100 days); - - deal(address(_quote), _minterTwo, 500_000_000 * 1e18); - - _kick({ - from: _minterTwo, - borrower: borrowerTwo, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - }); - - // skip ahead so take can be called on the loan - skip(10 hours); - - // take entire collateral - _take({ - from: _minterTwo, - borrower: borrowerTwo, - maxCollateral: 1_000 * 1e18, - bondChange: 6.531114528261135360 * 1e18, - givenAmount: 653.111452826113536000 * 1e18, - collateralTaken: 1_000 * 1e18, - isReward: true - }); - - _settle({ - from: _minterTwo, - borrower: borrowerTwo, - maxDepth: 10, - settledDebt: 9_891.935520844277346922 * 1e18 - }); - - // bucket is insolvent, balances are reset - _assertBucket({ - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e18 - }); - - // lower priced bucket isn't bankrupt, but exchange rate has decreased - _assertBucket({ - index: _i9_81, - lpBalance: 10_000 * 1e18, - collateral: 0, - deposit: 4_936.865619773958011818 * 1e18, - exchangeRate: 0.493686561977395801 * 1e18 - }); - - /***********************/ - /*** Reserve Auction ***/ - /***********************/ - - // skip some time to accumulate reserves - skip(50 days); - - // update pool reserves - _pool.updateInterest(); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(_pool), type(uint256).max); - _pool.startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(_pool)); - - // take claimable reserves - changePrank(_bidder); - _pool.takeReserves(curClaimableReservesRemaining); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - // _minterOne withdraws and claims rewards, rewards should be 0 - _unstakeToken({ - minter: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - - // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased - _unstakeToken({ - minter: _minterTwo, - pool: address(_pool), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - } - - function testClaimRewardsCap() external { - skip(10); - - /***************************/ - /*** Lender Deposits NFT ***/ - /***************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](2); - uint256[] memory depositIndex1 = new uint256[](1); - uint256[] memory depositIndex2 = new uint256[](1); - depositIndexes[0] = 2770; - depositIndexes[1] = 2771; - depositIndex1[0] = 2771; - depositIndex2[0] = 2770; - - // configure NFT position one - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 10_000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterOne); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /************************************/ - /*** Borrower One Accrue Interest ***/ - /************************************/ - - // borrower1 borrows - (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); - changePrank(borrower1); - - _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); - - // pass time to allow interest to accrue - skip(2 hours); - - // borrower1 repays their loan - (uint256 debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(_poolOne), type(uint256).max); - _poolOne.startClaimableReserveAuction(); - - // borrower1 now takes out more debt to accumulate more interest - changePrank(borrower1); - _poolOne.drawDebt(borrower1, 2_000 * 1e18, 2770, 0); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(_poolOne)); - - // take claimable reserves - changePrank(_bidder); - _poolOne.takeReserves(curClaimableReservesRemaining); - - // recorder updates the change in exchange rates in the first index - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndex1, - reward: 0.007104600671645296 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); - - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 0.284184026893324971 * 1e18, - interest: 0.000048562908902619 * 1e18 - }); - - // skip more time to allow more interest to accrue - skip(10 days); - - // borrower1 repays their loan again - changePrank(borrower1); - (debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); - - // recorder updates the change in exchange rates in the second index - _updateExchangeRates({ - updater: _updater2, - pool: address(_poolOne), - depositIndexes: depositIndex2, - reward: 0.021313802017687201 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); - - /*******************************************/ - /*** Lender Withdraws And Claims Rewards ***/ - /*******************************************/ - - // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0.298393228234161298 * 1e18, - updateRatesReward: 0 - }); - } - - function testMultiPeriodRewardsSingleClaim() external { - skip(10); - - uint256 totalTokensBurned; - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](10); - depositIndexes[0] = 5995; - depositIndexes[1] = 5996; - depositIndexes[2] = 5997; - depositIndexes[3] = 5998; - depositIndexes[4] = 5999; - depositIndexes[5] = 6000; - depositIndexes[6] = 6001; - depositIndexes[7] = 6002; - depositIndexes[8] = 6003; - depositIndexes[9] = 6004; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 20.449844540665683990 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); - - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 204.498445406656758711 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - // trigger second reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314416 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /*****************************/ - /*** Third Reserve Auction ***/ - /*****************************/ - - // trigger third reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // skip updating exchange rates and check available rewards - uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // snapshot calling update exchange rate - uint256 snapshot = vm.snapshot(); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606335 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 517.072612267116262774 * 1e18); - assertGt(rewardsEarned, rewardsEarnedNoUpdate); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // revert to no update state - vm.revertTo(snapshot); - - /******************************/ - /*** Fourth Reserve Auction ***/ - /******************************/ - - // triger fourth reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // check rewards earned - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 0); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - - // check rewards earned won't increase since previous update was missed - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - - /*****************************/ - /*** Fifth Reserve Auction ***/ - /*****************************/ - - // triger fourth reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905357 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); - - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049732206 * 1e18); - - // claim all rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(5, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - } - - function testMoveStakedLiquidity() external { - skip(10); - - /*****************/ - /*** Stake NFT ***/ - /*****************/ - - uint256[] memory firstIndexes = new uint256[](5); - firstIndexes[0] = 2550; - firstIndexes[1] = 2551; - firstIndexes[2] = 2552; - firstIndexes[3] = 2553; - firstIndexes[4] = 2555; - - // configure NFT position - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: firstIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenId = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // stake nft - _stakeToken(address(_poolOne), _minterOne, tokenId); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - uint256 expiry = block.timestamp + 1000; - uint256[] memory secondIndexes = new uint256[](5); - secondIndexes[0] = 2556; - secondIndexes[1] = 2557; - secondIndexes[2] = 2558; - secondIndexes[3] = 2559; - secondIndexes[4] = 2560; - - // check no rewards are claimed on first move - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(0, 0), 0); - - // check MoveLiquidity emits - for (uint256 i = 0; i < firstIndexes.length; ++i) { - vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, firstIndexes[i], secondIndexes[i]); - } - - vm.expectEmit(true, true, true, true); - emit MoveStakedLiquidity(tokenId, firstIndexes, secondIndexes); - _rewardsManager.moveStakedLiquidity(tokenId, firstIndexes, secondIndexes, expiry); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2560, - pool: _poolOne - }); - // first reserve auction happens successfully -> epoch 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - expiry = block.timestamp + 1000; - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - secondIndexes = _positionManager.getPositionIndexes(tokenId); - - // check rewards are claimed from the indexes that the staker is moving away from - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133149070 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570280 * 1e18); - // check MoveLiquidity emits - for (uint256 i = 0; i < firstIndexes.length; ++i) { - vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, secondIndexes[i], firstIndexes[i]); - } - vm.expectEmit(true, true, true, true); - emit MoveStakedLiquidity(tokenId, secondIndexes, firstIndexes); - - // check exchange rates are updated - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); - - changePrank(_minterOne); - _rewardsManager.moveStakedLiquidity(tokenId, secondIndexes, firstIndexes, expiry); - - // check that no rewards are available yet in the indexes that the staker moved to - vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - // first reserve auction happens successfully -> epoch 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - currentBurnEpoch = _poolOne.currentBurnEpoch(); - - /******************************/ - /*** Exchange Rates Updated ***/ - /******************************/ - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - firstIndexes = _positionManager.getPositionIndexes(tokenId); - - _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045926578320550 * 1e18); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - // claim rewards accrued since second movement of lps - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249745 * 1e18); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - } - - function testEarlyAndLateStakerRewards() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - // configure NFT position two - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - // bucket exchange rates are not changed at the time minter two stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1e18); - assertEq(_poolOne.bucketExchangeRate(2551), 1e18); - assertEq(_poolOne.bucketExchangeRate(2552), 1e18); - assertEq(_poolOne.bucketExchangeRate(2553), 1e18); - assertEq(_poolOne.bucketExchangeRate(2555), 1e18); - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - - // borrower borrows and change the exchange rates of buckets - (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); - changePrank(borrower1); - - _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); - - skip(1 days); - - // configure NFT position three one day after early minter - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); - // bucket exchange rates are higher at the time minter three stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); - _stakeToken(address(_poolOne), _minterThree, tokenIdThree); - - skip(1 days); - - // trigger reserve auction and update rates - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - _triggerReserveAuctions(triggerReserveAuctionParams); - - // unstake and compare rewards and balances of minter two and minter three - _unstakeToken({ - minter: _minterTwo, - pool: address(_poolOne), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 39.908019526547891790 * 1e18, - updateRatesReward: 0 - }); - uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 39.908019526547891790 * 1e18); - _unstakeToken({ - minter: _minterThree, - pool: address(_poolOne), - tokenId: tokenIdThree, - claimedArray: _epochsClaimedArray(1, 0), - reward: 33.248129642902710516 * 1e18, - updateRatesReward: 0 - }); - uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 33.248129642902710516 * 1e18); - - assertGt(minterTwoBalance, minterThreeBalance); - } - - // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves - function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { - skip(10); - - /*****************************/ - /*** First Lender Deposits ***/ - /*****************************/ - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - uint256 auctionOneTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - /******************************/ - /*** Second Lender Deposits ***/ - /******************************/ - - // second depositor deposits an NFT representing the same positions into the rewards contract - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - // second depositor stakes NFT, generating an update reward - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtOne, auctionOneTokensToBurn); - assertGt(idOneRewardsAtOne, 0); - - // minter one claims rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), idOneRewardsAtOne); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - // borrower takes actions providing reserves enabling additional reserve auctions - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - - // conduct second reserve auction - uint256 auctionTwoTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - /*****************************/ - /*** Third Lender Deposits ***/ - /*****************************/ - - // third depositor deposits an NFT representing the same positions into the rewards contract - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterThree, tokenIdThree); - - /***********************/ - /*** Rewards Claimed ***/ - /***********************/ - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo, auctionTwoTokensToBurn); - assertGt(idOneRewardsAtTwo, 0); - - uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, auctionTwoTokensToBurn); - assertGt(idTwoRewardsAtTwo, 0); - - // minter one claims rewards accrued after second auction - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 1), idOneRewardsAtTwo); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); - - // minter two claims rewards accrued since deposit - changePrank(_minterTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterTwo, address(_poolOne), tokenIdTwo, _epochsClaimedArray(1, 1), idTwoRewardsAtTwo); - _rewardsManager.claimRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterTwo), idTwoRewardsAtTwo + 8.175422393077328665 * 1e18); - - // check there are no remaining rewards available after claiming - uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - } - - function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { - // configure _minterOne's NFT position - uint256[] memory depositIndexesMinterOne = new uint256[](5); - depositIndexesMinterOne[0] = 2550; - depositIndexesMinterOne[1] = 2551; - depositIndexesMinterOne[2] = 2552; - depositIndexesMinterOne[3] = 2553; - depositIndexesMinterOne[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParamsMinterOne = MintAndMemorializeParams({ - indexes: depositIndexesMinterOne, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: _poolOne - }); - - // configure _minterTwo's NFT position - uint256[] memory depositIndexesMinterTwo = new uint256[](5); - depositIndexesMinterTwo[0] = 2550; - depositIndexesMinterTwo[1] = 2551; - depositIndexesMinterTwo[2] = 2200; - depositIndexesMinterTwo[3] = 2221; - depositIndexesMinterTwo[4] = 2222; - MintAndMemorializeParams memory mintMemorializeParamsMinterTwo = MintAndMemorializeParams({ - indexes: depositIndexesMinterTwo, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: _poolOne - }); - - uint256[] memory depositIndexes = new uint256[](8); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - depositIndexes[5] = 2200; - depositIndexes[6] = 2221; - depositIndexes[7] = 2222; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterOne); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterTwo); - - // lenders stake their NFTs - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - - // borrower takes actions providing reserves enabling three reserve auctions - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133202125 * 1e18 - }); - - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 1_000 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 13.717705175494265742 * 1e18 - }); - - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 2_000 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 27.568516982211953592 * 1e18 - }); - - // proof of burn events - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - (52 weeks + 72 hours), - interest: 6.443638300196908069 * 1e18, - burned: 81.799378162664356589 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 2, - timestamp: block.timestamp - (26 weeks + 48 hours), - burned: 356.153481672547831475 * 1e18, - interest: 28.092564949680668737 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 3, - timestamp: block.timestamp - 24 hours, - burned: 907.523821316786357044 * 1e18, - interest: 71.814132054505950833 * 1e18 - }); - - // both stakers claim rewards - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(3, 0), - reward: 75.626985109732100715 * 1e18, - updateRatesReward: 0 - }); - - _unstakeToken({ - minter: _minterTwo, - pool: address(_poolOne), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(3, 0), - reward: 378.134925548660503565 * 1e18, - updateRatesReward: 0 - }); - } - - function testUnstakeToken() external { - skip(10); - - address nonOwner = makeAddr("nonOwner"); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // only owner should be able to withdraw the NFT - changePrank(nonOwner); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.unstake(tokenIdOne); - - // check owner can withdraw the NFT - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - - // deposit information should have been deleted on withdrawal - (address owner, address pool, uint256 interactionBlock) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, address(0)); - assertEq(pool, address(0)); - assertEq(interactionBlock, 0); - } - - function testWithdrawAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertGt(_ajnaToken.balanceOf(_updater), 0); - - // check owner can withdraw the NFT and rewards will be automatically claimed - - uint256 snapshot = vm.snapshot(); - - // claimed rewards amount is greater than available tokens in rewards manager contract - - // burn rewards manager tokens and leave only 5 tokens available - changePrank(address(_rewardsManager)); - IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); - - uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); - assertEq(managerBalance, 4.931444746461898520 * 1e18); - - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - - // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 - assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); - // all 5 tokens available in manager balance were used to reward minter one - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); - - vm.revertTo(snapshot); - - // test when enough tokens in rewards manager contracts - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421210 * 1e18); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - // check can't claim rewards twice - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - } - - function testMultiplePools() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexesOne = new uint256[](5); - depositIndexesOne[0] = 9; - depositIndexesOne[1] = 1; - depositIndexesOne[2] = 2; - depositIndexesOne[3] = 3; - depositIndexesOne[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexesOne, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // configure NFT position two - uint256[] memory depositIndexesTwo = new uint256[](4); - depositIndexesTwo[0] = 5; - depositIndexesTwo[1] = 1; - depositIndexesTwo[2] = 3; - depositIndexesTwo[3] = 12; - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexesTwo, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolTwo - }); - - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // minterOne deposits their NFT into the rewards contract - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - uint256 currentBurnEpochPoolOne = _poolOne.currentBurnEpoch(); - - // check only deposit owner can claim rewards - changePrank(_minterTwo); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - - // check rewards earned in one pool shouldn't be claimable by depositors from another pool - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - _rewardsManager.claimRewards(tokenIdTwo, _poolTwo.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - - // call update exchange rate to enable claiming rewards - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), depositIndexesOne, 4.089968908133149070 * 1e18); - uint256 updateReward = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexesOne); - assertEq(_ajnaToken.balanceOf(_minterOne), updateReward); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); - - // check owner in pool with accrued interest can properly claim rewards - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - } - - /********************/ - /*** FUZZ TESTING ***/ - /********************/ - - function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { - // calculate the required collateral based upon the borrow amount and index price - (uint256 interestRate, ) = pool_.interestRateInfo(); - uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient - uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); - requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; - } - - // Helper function that returns a random subset from array - function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { - uint256[] memory copyOfArray = new uint256[](array.length); - for(uint j = 0; j < copyOfArray.length; j++){ - copyOfArray[j] = array[j]; - } - uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); - subsetArray = new uint256[](randomNoOfNfts); - for(uint256 i = 0; i < randomNoOfNfts; i++) { - uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); - subsetArray[i] = copyOfArray[randomIndex]; - copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; - } - } - - // Returns N addresses array - function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { - addresses_ = new address[](noOfAddress); - for(uint i = 0; i < noOfAddress; i++) { - addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); - } - } - - function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { - indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to - mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](indexes); - for (uint256 i = 0; i < indexes; ++i) { - depositIndexes[i] = _randomIndex(); - } - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: mintAmount, - pool: _poolOne - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // stake NFT - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // calculates a limit index leaving one index above the htp to accrue interest - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), - limitIndex: limitIndex, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertGt(_ajnaToken.balanceOf(_updater), 0); - - // calculate rewards earned and compare to percentages for updating and claiming - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertGt(rewardsEarned, 0); - - // claim rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - - // assert rewards claimed is less than ajna tokens burned cap - assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); - } - - function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { - deposits = bound(deposits, 1, 25); // number of deposits to make - reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete - - uint256[] memory tokenIds = new uint256[](deposits); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](3); - for (uint256 j = 0; j < 3; ++j) { - depositIndexes[j] = _randomIndex(); - vm.roll(block.number + 1); // advance block to ensure that the index price is different - } - - address[] memory minters = _getAddresses(deposits); - - // stake variable no of deposits - for(uint256 i = 0; i < deposits; ++i) { - // mint and memorilize Positions - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: minters[i], - mintAmount: 1_000_000_000 * 1e18, - pool: _poolOne - }); - - tokenIds[i] = _mintAndMemorializePositionNFT(mintMemorializeParams); - tokenIdToMinter[tokenIds[i]] = minters[i]; - _stakeToken(address(_poolOne), minters[i], tokenIds[i]); - } - - uint256 updaterBalance = _ajnaToken.balanceOf(_updater); - - for(uint i = 0; i < deposits; i++) { - minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); - } - - // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch - for(uint i = 0; i < reserveAuctions; ++i) { - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 10_000 * 1e18, - limitIndex: limitIndex, - pool: _poolOne - }); - - // start and end new reserve auction - uint256 tokensBurned = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - - // ensure updater gets reward for updating exchange rate - assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); - - // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned - assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); - - updaterBalance = _ajnaToken.balanceOf(_updater); - - // pick random NFTs from all NFTs to claim rewards - uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); - - for(uint j = 0; j < randomNfts.length; j++) { - address minterAddress = tokenIdToMinter[randomNfts[j]]; - changePrank(minterAddress); - - (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); - - // select random epoch to claim reward - uint256 epochToClaim = lastInteractionEpoch < _poolOne.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _poolOne.currentBurnEpoch()) : lastInteractionEpoch; - - uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); - assertGt(rewardsEarned, 0); - - _rewardsManager.claimRewards(randomNfts[j], _poolOne.currentBurnEpoch()); - - // ensure user gets reward - assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); - minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); - } - } - } - - function testClaimRewardsFreezeUnclaimedYield() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - changePrank(_minterOne); - // should revert if the epoch to claim is not available yet - vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); - - // user should be able to claim rewards for current epoch - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - } - -} diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 0187edb90..9c0aa5236 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -10,11 +10,9 @@ import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import 'src/interfaces/pool/IPool.sol'; import 'src/interfaces/pool/commons/IPoolEvents.sol'; import 'src/interfaces/pool/IERC3156FlashBorrower.sol'; - import 'src/PoolInfoUtils.sol'; import 'src/libraries/external/Auctions.sol'; -import 'src/libraries/internal/Maths.sol'; abstract contract DSTestPlus is Test, IPoolEvents { @@ -31,9 +29,9 @@ abstract contract DSTestPlus is Test, IPoolEvents { /*** Pools ***/ /*************/ - IPool internal _pool; - PoolInfoUtils internal _poolUtils; - uint256 internal _startTime; + IPool internal _pool; + PoolInfoUtils internal _poolUtils; + uint256 internal _startTime; uint256 internal _p1505_26 = 1_505.263728469068226832 * 1e18; uint256 internal _p1004_98 = 1_004.989662429170775094 * 1e18; From 10f6c1682fc65a5d33fde4da409857ac006b139e Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 30 Mar 2023 18:52:30 +0300 Subject: [PATCH 33/70] Revert "Cleaned up RewardManager contract (#710)" This reverts commit bd2d7c5d31afe27dbe772eb7a7f8357f6fb53636. --- tests/forge/Rewards/RewardsDSTestPlus.sol | 504 ----- tests/forge/Rewards/RewardsManager.t.sol | 1894 ------------------- tests/forge/RewardsManager.t.sol | 2048 +++++++++++++++++++++ tests/forge/utils/DSTestPlus.sol | 8 +- 4 files changed, 2053 insertions(+), 2401 deletions(-) delete mode 100644 tests/forge/Rewards/RewardsDSTestPlus.sol delete mode 100644 tests/forge/Rewards/RewardsManager.t.sol create mode 100644 tests/forge/RewardsManager.t.sol diff --git a/tests/forge/Rewards/RewardsDSTestPlus.sol b/tests/forge/Rewards/RewardsDSTestPlus.sol deleted file mode 100644 index b282895c6..000000000 --- a/tests/forge/Rewards/RewardsDSTestPlus.sol +++ /dev/null @@ -1,504 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import 'src/RewardsManager.sol'; -import 'src/PoolInfoUtils.sol'; -import 'src/PositionManager.sol'; - -import 'src/interfaces/rewards/IRewardsManager.sol'; -import 'src/interfaces/position/IPositionManager.sol'; - -import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; - -import { Token } from '../utils/Tokens.sol'; -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; - -import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { PositionManager } from 'src/PositionManager.sol'; - -import { ERC20HelperContract } from '../ERC20Pool/ERC20DSTestPlus.sol'; -import { IRewardsManagerEvents } from 'src/interfaces/rewards/IRewardsManagerEvents.sol'; - -abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContract { - - address internal _minterOne; - address internal _minterTwo; - address internal _minterThree; - address internal _minterFour; - address internal _minterFive; - - ERC20 internal _ajnaToken; - - IPool internal _poolTwo; - IRewardsManager internal _rewardsManager; - IPositionManager internal _positionManager; - - struct MintAndMemorializeParams { - uint256[] indexes; - address minter; - uint256 mintAmount; - IPool pool; - } - - struct TriggerReserveAuctionParams { - address borrower; - uint256 borrowAmount; - uint256 limitIndex; - IPool pool; - } - - function _stakeToken(address pool, address owner, uint256 tokenId) internal { - changePrank(owner); - - // approve and deposit NFT into rewards contract - PositionManager(address(_positionManager)).approve(address(_rewardsManager), tokenId); - vm.expectEmit(true, true, true, true); - emit Stake(owner, address(pool), tokenId); - _rewardsManager.stake(tokenId); - - // check token was transferred to rewards contract - (address ownerInf, address poolInf, ) = _rewardsManager.getStakeInfo(tokenId); - assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); - assertEq(ownerInf, owner); - assertEq(poolInf, pool); - } - - function _unstakeToken( - address owner, - address pool, - uint256[] memory claimedArray, - uint256 tokenId, - uint256 reward, - uint256 updateRatesReward - ) internal { - - changePrank(owner); - - vm.expectEmit(true, true, true, true); - emit ClaimRewards(owner, pool, tokenId, claimedArray, reward); - vm.expectEmit(true, true, true, true); - emit Unstake(owner, address(pool), tokenId); - _rewardsManager.unstake(tokenId); - return; - assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); - - // check token was transferred from rewards contract to minter - assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); - - // invariant: all bucket snapshots are removed for the token id that was unstaken - for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { - (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); - assertEq(lps, 0); - assertEq(rate, 0); - } - - (address owner, address pool, uint256 interactionBlock) = _rewardsManager.getStakeInfo(tokenId); - assertEq(owner, address(0)); - assertEq(pool, address(0)); - assertEq(interactionBlock, 0); - } - - function _assertBurn( - address pool, - uint256 epoch, - uint256 timestamp, - uint256 interest, - uint256 burned, - uint256 tokensToBurn, - uint256 rewardsToClaimer, - uint256 rewardsToUpdater - ) internal { - - (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); - - assertEq(bETimestamp, timestamp); - assertEq(bEInterest, interest); - assertEq(bEBurned, burned); - assertEq(burned, tokensToBurn); - assertEq(Maths.wmul(burned, 0.8 * 1e18), rewardsToClaimer); - assertEq(Maths.wmul(burned, 0.05 * 1e18), rewardsToUpdater); - } - - - function _updateExchangeRates( - address updater, - address pool, - uint256[] memory indexes, - uint256 reward - ) internal { - changePrank(updater); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(updater, pool, indexes, reward); - _rewardsManager.updateBucketExchangeRatesAndClaim(pool, indexes); - } - - - function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { - epochsClaimed_ = new uint256[](numberOfAuctions_); - uint256 claimEpoch = lastClaimed_; // starting index, not inclusive - - for (uint256 i = 0; i < numberOfAuctions_; i++) { - epochsClaimed_[i] = claimEpoch + 1; - claimEpoch += 1; - } - } - - function _claimRewards( - address from, - address pool, - uint256 tokenId, - uint256 reward, - uint256[] memory epochsClaimed - ) internal { - changePrank(from); - uint256 fromAjnaBal = _ajnaToken.balanceOf(from); - - uint256 currentBurnEpoch = IPool(pool).currentBurnEpoch(); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(from, pool, tokenId, epochsClaimed, reward); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - - assertEq(_ajnaToken.balanceOf(from), fromAjnaBal + reward); - } - - function _moveStakedLiquidity( - address from, - uint256 tokenId, - uint256[] memory fromIndexes, - bool fromIndStaked, - uint256[] memory toIndexes, - uint256 expiry - ) internal { - - changePrank(from); - - // check MoveLiquidity emits - for (uint256 i = 0; i < fromIndexes.length; ++i) { - vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, fromIndexes[i], toIndexes[i]); - } - - vm.expectEmit(true, true, true, true); - emit MoveStakedLiquidity(tokenId, fromIndexes, toIndexes); - - if (fromIndStaked) { - // check exchange rates are updated - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_pool), toIndexes, 0); - } - _rewardsManager.moveStakedLiquidity(tokenId, fromIndexes, toIndexes, expiry); - - } - - function _assertNotOwnerOfDepositRevert(address from , uint256 tokenId) internal { - // check only deposit owner can claim rewards - changePrank(from); - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - } - - function _assertNotOwnerOfDepositUnstakeRevert(address from , uint256 tokenId) internal { - // check only deposit owner can claim rewards - changePrank(from); - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - } - - function _assertAlreadyClaimedRevert(address from , uint256 tokenId) internal { - // check only deposit owner can claim rewards - changePrank(from); - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - } - - function _assertStake( - address owner, - address pool, - uint256 tokenId, - uint256 burnEvent, - uint256 rewardsEarned - ) internal { - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - (address ownerInf, address poolInf, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenId); - uint256 rewardsEarnedInf = _rewardsManager.calculateRewards(tokenId, currentBurnEpoch); - - assertEq(owner, ownerInf); - assertEq(pool, poolInf); - assertEq(burnEvent, interactionBurnEvent); - assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); - } - - -} - - - - -abstract contract RewardsHelperContract is RewardsDSTestPlus { - - address internal _bidder; - address internal _updater; - address internal _updater2; - - Token internal _collateralOne; - Token internal _quoteOne; - Token internal _collateralTwo; - Token internal _quoteTwo; - - constructor() { - vm.makePersistent(_ajna); - - _ajnaToken = ERC20(_ajna); - _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); - _rewardsManager = new RewardsManager(_ajna, _positionManager); - - _collateralOne = new Token("Collateral 1", "C1"); - _quoteOne = new Token("Quote 1", "Q1"); - _collateralTwo = new Token("Collateral 2", "C2"); - _quoteTwo = new Token("Quote 2", "Q2"); - - _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); - - // provide initial ajna tokens to staking rewards contract - deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); - } - - // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt - function _createTestBorrower(address pool, address borrower, uint256 borrowAmount, uint256 limitIndex) internal returns (uint256 collateralToPledge_) { - - changePrank(borrower); - Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); - Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); - // deal twice as much quote so the borrower has sufficient quote to repay the loan - deal(address(quote), borrower, Maths.wmul(borrowAmount, Maths.wad(2))); - - // approve tokens - collateral.approve(address(pool), type(uint256).max); - quote.approve(address(pool), type(uint256).max); - - collateralToPledge_ = _requiredCollateral(borrowAmount, limitIndex); - deal(address(collateral), borrower, collateralToPledge_); - } - - function _triggerReserveAuctionsNoTake( - address borrower, - address pool, - uint256 borrowAmount, - uint256 limitIndex - ) internal { - // create a new borrower to write state required for reserve auctions - uint256 collateralToPledge = _createTestBorrower(address(pool), borrower, borrowAmount, limitIndex); - - // borrower drawsDebt from the pool - ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - ERC20Pool(address(pool)).repayDebt(borrower, Maths.wdiv(borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - _startClaimableReserveAuction(address(pool), _bidder); - } - - function _startClaimableReserveAuction( - address pool, - address bidder - ) internal { - changePrank(bidder); - _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); - } - - function _mintAndMemorializePositionNFT( - address minter, - uint256 mintAmount, - address pool, - uint256[] memory indexes - ) internal returns (uint256 tokenId_) { - changePrank(minter); - - Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); - Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); - - // deal tokens to the minter - deal(address(collateral), minter, 250_000 * 1e18); - deal(address(quote), minter, mintAmount * indexes.length); - - // approve tokens - collateral.approve(address(pool), type(uint256).max); - quote.approve(address(pool), type(uint256).max); - - IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(minter, address(pool), keccak256("ERC20_NON_SUBSET_HASH")); - tokenId_ = _positionManager.mint(mintParams); - - uint256[] memory lpBalances = new uint256[](indexes.length); - - for (uint256 i = 0; i < indexes.length; i++) { - ERC20Pool(address(pool)).addQuoteToken(mintAmount, indexes[i], type(uint256).max); - (lpBalances[i], ) = ERC20Pool(address(pool)).lenderInfo(indexes[i], minter); - } - - ERC20Pool(address(pool)).increaseLPsAllowance(address(_positionManager), indexes, lpBalances); - - // construct memorialize params struct - IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( - tokenId_, indexes - ); - - _positionManager.memorializePositions(memorializeParams); - - // register position manager as lender at memorialized indexes (for LP test assertions) - _registerLender(address(_positionManager), indexes); - } - - function _triggerReserveAuctions( - address borrower, - address pool, - uint256 borrowAmount, - uint256 limitIndex, - uint256 tokensToBurn - ) internal returns (uint256 tokensBurned_) { - - // fund borrower to write state required for reserve auctions - changePrank(borrower); - Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); - Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); - deal(address(quote), borrower, borrowAmount); - - // approve tokens - collateral.approve(address(pool), type(uint256).max); - quote.approve(address(pool), type(uint256).max); - - uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); - deal(address(_collateral), borrower, collateralToPledge); - - // borrower drawsDebt from the pool - ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); - - // Can't trigger reserve auction if less than two weeks have passed since last auction - vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - ERC20Pool(address(pool)).startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - _takeReserves(pool, _bidder); - - (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); - assertEq(tokensBurned_, tokensToBurn); - - return tokensBurned_; - } - - function _triggerReserveAuctionsBurnUnknown( - address borrower, - address pool, - uint256 borrowAmount, - uint256 limitIndex - ) internal returns (uint256 tokensBurned_) { - - // fund borrower to write state required for reserve auctions - changePrank(borrower); - Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); - Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); - deal(address(quote), borrower, borrowAmount); - - // approve tokens - collateral.approve(address(pool), type(uint256).max); - quote.approve(address(pool), type(uint256).max); - - uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); - deal(address(_collateral), borrower, collateralToPledge); - - // borrower drawsDebt from the pool - ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); - - // Can't trigger reserve auction if less than two weeks have passed since last auction - vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - ERC20Pool(address(pool)).startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - _takeReserves(pool, _bidder); - - (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); - - return tokensBurned_; - } - - function _takeReserves(address pool, address from) internal { - changePrank(from); - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(pool); - - ERC20Pool(pool).takeReserves(curClaimableReservesRemaining); - } - - function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { - // calculate the required collateral based upon the borrow amount and index price - (uint256 interestRate, ) = pool_.interestRateInfo(); - uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient - uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); - requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; - } - - // // Helper function that returns a random subset from array - function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { - uint256[] memory copyOfArray = new uint256[](array.length); - for(uint j = 0; j < copyOfArray.length; j++){ - copyOfArray[j] = array[j]; - } - uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); - subsetArray = new uint256[](randomNoOfNfts); - for(uint256 i = 0; i < randomNoOfNfts; i++) { - uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); - subsetArray[i] = copyOfArray[randomIndex]; - copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; - } - } - - // Returns N addresses array - function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { - addresses_ = new address[](noOfAddress); - for(uint i = 0; i < noOfAddress; i++) { - addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); - } - } -} diff --git a/tests/forge/Rewards/RewardsManager.t.sol b/tests/forge/Rewards/RewardsManager.t.sol deleted file mode 100644 index 1759e84eb..000000000 --- a/tests/forge/Rewards/RewardsManager.t.sol +++ /dev/null @@ -1,1894 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import 'src/PoolInfoUtils.sol'; -import 'src/PositionManager.sol'; -import 'src/interfaces/rewards/IRewardsManager.sol'; - -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { RewardsHelperContract } from './RewardsDSTestPlus.sol'; - -contract RewardsManagerTest is RewardsHelperContract { - - address internal _borrower; - address internal _borrower2; - address internal _borrower3; - address internal _lender; - address internal _lender1; - - uint256 constant BLOCKS_IN_DAY = 7200; - mapping (uint256 => address) internal tokenIdToMinter; - mapping (address => uint256) internal minterToBalance; - - function setUp() external { - - // borrowers - _borrower = makeAddr("borrower"); - _borrower2 = makeAddr("borrower2"); - _borrower3 = makeAddr("borrower3"); - - _lender = makeAddr("lender"); - _lender1 = makeAddr("lender1"); - - // instantiate test minters - _minterOne = makeAddr("minterOne"); - _minterTwo = makeAddr("minterTwo"); - _minterThree = makeAddr("minterThree"); - _minterFour = makeAddr("minterFour"); - _minterFive = makeAddr("minterFive"); - - // instantiate test bidder - _bidder = makeAddr("bidder"); - deal(address(_ajna), _bidder, 900_000_000 * 10**18); - - vm.prank(_bidder); - _ajnaToken.approve(address(_pool), type(uint256).max); - vm.prank(_bidder); - ERC20(address(_quoteOne)).approve(address(_pool), type(uint256).max); - ERC20(address(_quoteTwo)).approve(address(_pool), type(uint256).max); - - // instantiate test updater - _updater = makeAddr("updater"); - _updater2 = makeAddr("updater2"); - - _mintCollateralAndApproveTokens(_borrower, 100 * 1e18); - _mintQuoteAndApproveTokens(_borrower, 200_000 * 1e18); - - _mintCollateralAndApproveTokens(_borrower2, 1_000 * 1e18); - _mintQuoteAndApproveTokens(_borrower2, 200_000 * 1e18); - - _mintCollateralAndApproveTokens(_borrower3, 1_000 * 1e18); - _mintQuoteAndApproveTokens(_borrower3, 200_000 * 1e18); - - _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); - _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); - - _mintQuoteAndApproveTokens(_minterOne, 500_000_000 * 1e18); - _mintQuoteAndApproveTokens(_minterTwo, 500_000_000 * 1e18); - } - - - function testStakeToken() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // configure NFT position two - depositIndexes = new uint256[](4); - depositIndexes[0] = 5; - depositIndexes[1] = 1; - depositIndexes[2] = 3; - depositIndexes[3] = 12; - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1_000 * 1e18, - pool: address(_poolTwo) - }); - - // check only owner of an NFT can deposit it into the rewards contract - _assertNotOwnerOfDepositRevert({ - from: _minterTwo, - tokenId: tokenIdOne - }); - - // minterOne deposits their NFT into the rewards contract - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken({ - pool: address(_poolTwo), - owner: _minterTwo, - tokenId: tokenIdTwo - }); - } - - function testUnstakeToken() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // check only owner of an NFT can deposit it into the rewards contract - _assertNotOwnerOfDepositRevert({ - from: _minterTwo, - tokenId: tokenIdOne - }); - - // minterOne deposits their NFT into the rewards contract - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - // only owner should be able to withdraw the NFT - _assertNotOwnerOfDepositUnstakeRevert({ - from: _minterTwo, - tokenId: tokenIdOne - }); - - uint256[] memory claimedArray = new uint256[](0); - - _unstakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne, - claimedArray: claimedArray, // no rewards as no reserve auctions have occured - reward: 0, - updateRatesReward: 0 - }); - } - - function testUpdateExchangeRatesAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: address(_pool), - tokensToBurn: 81.799378162662704349 * 1e18 - }); - - // call update exchange rate to enable claiming rewards - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 4.089968908133134138 * 1e18 - }); - - // check only deposit owner can claim rewards - _assertNotOwnerOfDepositRevert({ - from: _updater, - tokenId: tokenIdOne - }); - - // claim rewards accrued since deposit - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - reward: 40.899689081331351737 * 1e18, - epochsClaimed: _epochsClaimedArray(1, 0) - }); - - // check can't claim rewards twice - _assertAlreadyClaimedRevert({ - from: _minterOne, - tokenId: tokenIdOne - }); - - _assertStake({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - burnEvent: 1, - rewardsEarned: 40.899689081331351737 * 1e18 - }); - - // assertEq(_ajnaToken.balanceOf(_minterOne), 0); - - _assertBurn({ - pool: address(_pool), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 81.799378162662704349 * 1e18, - tokensToBurn: tokensToBurn, - interest: 6.443638300196908069 * 1e18, - rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 4.089968908133135217 * 1e18 - }); - - skip(2 weeks); - - // check can't call update exchange rate after the update period has elapsed - uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); - assertEq(updateRewards, 0); - } - - function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // epoch 0 - 1 is checked for rewards - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - // first reserve auction happens successfully -> epoch 1 - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162662704349 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2_555, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 4.089968908133134138 * 1e18 - }); - - _assertBurn({ - pool: address(_pool), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 81.799378162662704349 * 1e18, - tokensToBurn: tokensToBurn, - interest: 6.443638300196908069 * 1e18, - rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 4.089968908133135217 * 1e18 - }); - - - // second reserve auction happens successfully -> epoch 2 - tokensToBurn += _triggerReserveAuctions({ - borrower: _borrower, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool), - tokensToBurn: 150.804205428530300439 * 1e18 - }); - - // check owner can withdraw the NFT and rewards will be automatically claimed - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(2, 0), - reward: 78.852344077558527015 * 1e18, - updateRatesReward: 4.173624213367915345 * 1e18 - }); - } - - function testWithdrawAndClaimRewardsNoReserveTake() external { - - // healthy epoch, bad epoch - - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: address(_pool) - }); - - // epoch 0 - 1 is checked for rewards - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162662704349 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - //call update exchange rate to enable claiming rewards for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 4.089968908133134138 * 1e18 - }); - - skip(2 weeks); - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - _triggerReserveAuctionsNoTake({ - borrower: _borrower, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - _assertBurn({ - pool: address(_pool), - epoch: 1, - timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), - burned: 81.799378162662704349 * 1e18, - tokensToBurn: tokensToBurn, - interest: 6.443638300196908069 * 1e18, - rewardsToClaimer: 65.439502530130163479 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 4.089968908133135217 * 1e18 - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 3.399633583097821167 * 1e18 - }); - } - - // two lenders stake their positions in the pool - // staker one bucket bankrupt, staker two bucket active - // interest accrued to both buckets, but staker one receives no rewards - function testClaimRewardsBankruptBucket() external { - - address[] memory transferors = new address[](1); - transferors[0] = address(_positionManager); - - changePrank(_minterOne); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - changePrank(_minterTwo); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - /*****************************/ - /*** Initialize Pool State ***/ - /*****************************/ - - // MinterOne adds Quote token accross 5 prices - _addInitialLiquidity({ - from: _minterOne, - amount: 2_000 * 1e18, - index: _i9_91 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 5_000 * 1e18, - index: _i9_81 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 11_000 * 1e18, - index: _i9_72 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 25_000 * 1e18, - index: _i9_62 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 30_000 * 1e18, - index: _i9_52 - }); - - // first borrower adds collateral token and borrows - _pledgeCollateral({ - from: _borrower, - borrower: _borrower, - amount: 2 * 1e18 - }); - _borrow({ - from: _borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - }); - - // second borrower adds collateral token and borrows - _pledgeCollateral({ - from: _borrower2, - borrower: _borrower2, - amount: 1_000 * 1e18 - }); - _borrow({ - from: _borrower2, - amount: 9_710 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - }); - - /*****************************/ - /*** Lenders Deposits NFTs ***/ - /*****************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](1); - uint256[] memory depositIndexes2 = new uint256[](1); - depositIndexes[0] = _i9_91; - depositIndexes2[0] = _i9_81; - - // ERC20Pool pool = ERC20Pool(address(_pool)); - - // stake NFT position one - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 2_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - - // stake NFT position two - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: depositIndexes2, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: address(_pool) - }); - _stakeToken({ - pool: address(_pool), - owner: _minterTwo, - tokenId: tokenIdTwo - }); - - /***********************************/ - /*** Borrower Bankrupts A Bucket ***/ - /***********************************/ - - // Skip to make borrower two undercollateralized - skip(100 days); - - // all QT was inserted when minting NFT, provide more to kick - deal(address(_quote), _minterTwo, 10_000 * 1e18); - - _kick({ - from: _minterTwo, - borrower: _borrower2, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - }); - - // skip ahead so take can be called on the loan - skip(10 hours); - - // take entire collateral - _take({ - from: _minterTwo, - borrower: _borrower2, - maxCollateral: 1_000 * 1e18, - bondChange: 6.531114528261135360 * 1e18, - givenAmount: 653.111452826113536000 * 1e18, - collateralTaken: 1_000 * 1e18, - isReward: true - }); - - _settle({ - from: _minterTwo, - borrower: _borrower2, - maxDepth: 10, - settledDebt: 9_891.935520844277346922 * 1e18 - }); - - // bucket is insolvent, balances are reset - _assertBucket({ - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e18 - }); - - // lower priced bucket isn't bankrupt, but exchange rate has decreased - _assertBucket({ - index: _i9_81, - lpBalance: 10_000 * 1e18, - collateral: 0, - deposit: 4_936.865619773958011818 * 1e18, - exchangeRate: 0.493686561977395801 * 1e18 - }); - - /***********************/ - /*** Reserve Auction ***/ - /***********************/ - - // skip some time to accumulate reserves - skip(50 days); - - // update pool reserves - _pool.updateInterest(); - - // start reserve auction - _startClaimableReserveAuction({ - pool: address(_pool), - bidder: _bidder - }); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(_pool)); - - // take claimable reserves - changePrank(_bidder); - _pool.takeReserves(curClaimableReservesRemaining); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - // _minterOne withdraws and claims rewards, rewards should be 0 - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - - // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased - _unstakeToken({ - owner: _minterTwo, - pool: address(_pool), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - } - - function testClaimRewardsCap() external { - skip(10); - - /***************************/ - /*** Lender Deposits NFT ***/ - /***************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](2); - uint256[] memory depositIndex1 = new uint256[](1); - uint256[] memory depositIndex2 = new uint256[](1); - depositIndexes[0] = 2770; - depositIndexes[1] = 2771; - depositIndex1[0] = 2771; - depositIndex2[0] = 2770; - - // configure NFT position one - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 10_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - /************************************/ - /*** Borrower One Accrue Interest ***/ - /************************************/ - - // borrower borrows - (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower, 10_000 * 1e18, 2770); - - _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 5 * 1e18, - limitIndex: 2770, - collateralToPledge: collateralToPledge, - newLup: 1_004.989662429170775094 * 1e18 - }); - - // pass time to allow interest to accrue - skip(2 hours); - - // borrower repays their loan - (uint256 debt, , ) = _pool.borrowerInfo(_borrower); - _repayDebt({ - from: _borrower, - borrower: _borrower, - amountToRepay: debt, - amountRepaid: 5.004807692307692310 * 1e18, - collateralToPull: 0, - newLup: 1_004.989662429170775094 * 1e18 - }); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - // start reserve auction - _startClaimableReserveAuction({ - pool: address(_pool), - bidder: _bidder - }); - - // _borrower now takes out more debt to accumulate more interest - _drawDebt({ - from: _borrower, - borrower: _borrower, - amountToBorrow: 2_000 * 1e18, - limitIndex: 2770, - collateralToPledge: 0, - newLup: 1_004.989662429170775094 * 1e18 - }); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - _takeReserves({ - pool: address(_pool), - from: _bidder - }); - - // recorder updates the change in exchange rates in the first index - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndex1, - reward: 0.007104600671645296 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); - - _assertBurn({ - pool: address(_pool), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0, - rewardsToClaimer: 0, - rewardsToUpdater: 0, - tokensToBurn: 0 - }); - - _assertBurn({ - pool: address(_pool), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 0.284184026893324971 * 1e18, - interest: 0.000048562908902619 * 1e18, - tokensToBurn: 0.284184026893324971 * 1e18, - rewardsToClaimer: 0.227347221514659977 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 0.014209201344666249 * 1e18 - }); - - // skip more time to allow more interest to accrue - skip(10 days); - - // borrowe_r repays their loan again - (debt, , ) = _pool.borrowerInfo(_borrower); - _repayDebt({ - from: _borrower, - borrower: _borrower, - amountToRepay: debt, - amountRepaid: 2001.900281182536528586 * 1e18, - collateralToPull: 0, - newLup: 1_004.989662429170775094 * 1e18 - }); - - // recorder updates the change in exchange rates in the second index - _updateExchangeRates({ - updater: _updater2, - pool: address(_pool), - indexes: depositIndex2, - reward: 0.021313802017687201 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); - - - /*******************************************/ - /*** Lender Withdraws And Claims Rewards ***/ - /*******************************************/ - - // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0.298393228234161298 * 1e18, - updateRatesReward: 0 - }); - } - - function testMultiPeriodRewardsSingleClaim() external { - skip(10); - - uint256 totalTokensBurned; - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](10); - depositIndexes[0] = 5995; - depositIndexes[1] = 5996; - depositIndexes[2] = 5997; - depositIndexes[3] = 5998; - depositIndexes[4] = 5999; - depositIndexes[5] = 6000; - depositIndexes[6] = 6001; - depositIndexes[7] = 6002; - depositIndexes[8] = 6003; - depositIndexes[9] = 6004; - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - totalTokensBurned += _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 408.996890813313521802 * 1e18, - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming rewards - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 20.449844540665683990 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); - - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 204.498445406656758711 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - // trigger second reserve auction - totalTokensBurned += _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 753.761937534761336922 * 1e18, - borrowAmount: 1_500 * 1e18, - limitIndex: 6_000, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming rewards - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 17.238252336072314416 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /*****************************/ - /*** Third Reserve Auction ***/ - /*****************************/ - - // trigger third reserve auction - totalTokensBurned += _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 1_034.145224534232837796 * 1e18, - borrowAmount: 1_500 * 1e18, - limitIndex: 6_000, - pool: address(_pool) - }); - - // skip updating exchange rates and check available rewards - uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // snapshot calling update exchange rate - uint256 snapshot = vm.snapshot(); - - // call update exchange rate - _updateExchangeRates({ - updater: _updater2, - pool: address(_pool), - indexes: depositIndexes, - reward: 14.019164349973606335 * 1e18 - }); - - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); // assertEq(rewardsEarned, 517.072612267115797118 * 1e18); - assertGt(rewardsEarned, rewardsEarnedNoUpdate); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // revert to no update state - vm.revertTo(snapshot); - - /******************************/ - /*** Fourth Reserve Auction ***/ - /******************************/ - - // triger fourth reserve auction - totalTokensBurned += _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 1_289.513636917170056104 * 1e18, - borrowAmount: 1_500 * 1e18, - limitIndex: 6_000, - pool: address(_pool) - }); - - // check rewards earned - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - - // call update exchange rate - _updateExchangeRates({ - updater: _updater2, - pool: address(_pool), - indexes: depositIndexes, - reward: 0 - }); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - - // check rewards earned won't increase since previous update was missed - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); - - /*****************************/ - /*** Fifth Reserve Auction ***/ - /*****************************/ - - // triger fifth reserve auction - totalTokensBurned += _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 1521.830620022508618175 * 1e18, - borrowAmount: 1_500 * 1e18, - limitIndex: 6_000, - pool: address(_pool) - }); - - // call update exchange rate - _updateExchangeRates({ - updater: _updater2, - pool: address(_pool), - indexes: depositIndexes, - reward: 11.615849155266905357 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); - - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049732206 * 1e18); - - // claim all rewards accrued since deposit - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - epochsClaimed: _epochsClaimedArray(5,0), - reward: 493.039460320049732206 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - } - - function testMoveStakedLiquidity() external { - skip(10); - - /*****************/ - /*** Stake NFT ***/ - /*****************/ - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - // configure NFT position - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: address(_pool) - }); - - // stake nft - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - _updateExchangeRates({ - updater: _minterOne, - pool: address(_pool), - indexes: depositIndexes, - reward: 0 - }); - - - uint256[] memory secondIndexes = new uint256[](5); - secondIndexes[0] = 2556; - secondIndexes[1] = 2557; - secondIndexes[2] = 2558; - secondIndexes[3] = 2559; - secondIndexes[4] = 2560; - - _moveStakedLiquidity({ - from: _minterOne, - tokenId: tokenIdOne, - fromIndexes: depositIndexes, - fromIndStaked: false, - toIndexes: secondIndexes, - expiry: block.timestamp + 1000 - }); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // first reserve auction happens successfully -> epoch 1 - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 462.029442387557878852 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2560, - pool: address(_pool) - }); - - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - //secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); //TODO: investigate why commenting this out or keeping it doesn't do anything - - _moveStakedLiquidity({ - from: _minterOne, - tokenId: tokenIdOne, - fromIndexes: secondIndexes, - fromIndStaked: true, - toIndexes: depositIndexes, - expiry: block.timestamp + 1000 - }); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - // second reserve auction happens successfully -> epoch 1 - _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 529.505418197285009563 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - /******************************/ - /*** Exchange Rates Updated ***/ - /******************************/ - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - // depositIndexes = _positionManager.getPositionIndexes(tokenIdOne); //TODO: investigate why commenting this out or keeping it doesn't do anything - - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 3.373278067656485050 * 1e18 - }); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - epochsClaimed: _epochsClaimedArray(1,1), - reward: 33.732780676564838905 * 1e18 - }); - } - - function testEarlyAndLateStakerRewards() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - // configure NFT position two - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // bucket exchange rates are not changed at the time minter two stakes - assertEq(_pool.bucketExchangeRate(2550), 1e18); - assertEq(_pool.bucketExchangeRate(2551), 1e18); - assertEq(_pool.bucketExchangeRate(2552), 1e18); - assertEq(_pool.bucketExchangeRate(2553), 1e18); - assertEq(_pool.bucketExchangeRate(2555), 1e18); - - // stake NFT - _stakeToken({ - pool: address(_pool), - owner: _minterTwo, - tokenId: tokenIdTwo - }); - - (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower2, 10_000 * 1e18, 2770); - - // borrower borrows and change the exchange rates of buckets - _drawDebt({ - from: _borrower2, - borrower: _borrower2, - amountToBorrow: 5 * 1e18, - limitIndex: 2770, - collateralToPledge: collateralToPledge, - newLup: 3_010.892022197881557845 * 1e18 - }); - - skip(1 days); - - // configure NFT position three one day after early minter - uint256 tokenIdThree = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // bucket exchange rates are higher at the time minter three stakes - assertEq(_pool.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); - assertEq(_pool.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); - assertEq(_pool.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); - assertEq(_pool.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); - assertEq(_pool.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); - - // stake NFT - _stakeToken({ - pool: address(_pool), - owner: _minterThree, - tokenId: tokenIdThree - }); - - skip(1 days); - - _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 602.051131214993198872 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - // unstake and compare rewards and balances of minter two and minter three - _unstakeToken({ - owner: _minterTwo, - pool: address(_pool), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 266.615874256912407614 * 1e18, - updateRatesReward: 44.433744918714141439 * 1e18 - }); - - uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 266.615874256912407614 * 1e18); - - _unstakeToken({ - owner: _minterThree, - pool: address(_pool), - tokenId: tokenIdThree, - claimedArray: _epochsClaimedArray(1, 0), - reward: 78.843436266764514308 * 1e18, - updateRatesReward: 0 - }); - uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 78.843436266764514308 * 1e18); - - assertGt(minterTwoBalance, minterThreeBalance); - } - - // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves - function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { - skip(10); - - /*****************************/ - /*** First Lender Deposits ***/ - /*****************************/ - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // stake NFT - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - uint256 firstTokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162662704349 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: address(_pool) - }); - - /******************************/ - /*** Second Lender Deposits ***/ - /******************************/ - - // second depositor deposits an NFT representing the same positions into the rewards contract - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: address(_pool) - }); - - // second depositor stakes NFT, generating an update reward - _stakeToken({ - pool: address(_pool), - owner: _minterTwo, - tokenId: tokenIdTwo - }); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.154797961459423073 * 1e18); - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertLt(idOneRewardsAtOne, firstTokensToBurn); - assertGt(idOneRewardsAtOne, 1); - - // minter one claims rewards accrued since deposit - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - epochsClaimed: _epochsClaimedArray(1,0), - reward: idOneRewardsAtOne - }); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - // // borrower takes actions providing reserves enabling additional reserve auctions - // conduct second reserve auction - uint256 secondTokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 176.189760225502880454 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: address(_pool) - }); - - /*****************************/ - /*** Third Lender Deposits ***/ - /*****************************/ - - // third depositor deposits an NFT representing the same positions into the rewards contract - uint256 tokenIdThree = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken({ - pool: address(_pool), - owner: _minterThree, - tokenId: tokenIdThree - }); - - /***********************/ - /*** Rewards Claimed ***/ - /***********************/ - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo, secondTokensToBurn); - assertGt(idOneRewardsAtTwo, 0); - assertEq(idOneRewardsAtTwo, 23.615632136163281168 * 1e18); - - uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, secondTokensToBurn); - assertEq(idTwoRewardsAtTwo, 23.583081554200477722 * 1e18); - assertGt(idTwoRewardsAtTwo, 0); - - // minter one claims rewards accrued after second auction - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - epochsClaimed: _epochsClaimedArray(1,1), - reward: 23.615632136163281168 * 1e18 - }); - - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); - - // minter two claims rewards accrued since deposit - _claimRewards({ - pool: address(_pool), - from: _minterTwo, - tokenId: tokenIdTwo, - epochsClaimed: _epochsClaimedArray(1,1), - reward: idTwoRewardsAtTwo - }); - assertEq(_ajnaToken.balanceOf(_minterTwo), 31.737879515659900795 * 1e18); - - // check there are no remaining rewards available after claiming - uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _pool.currentBurnEpoch()); - assertEq(remainingRewards, 0); - } - - function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { - // configure _minterOne's NFT position - uint256[] memory depositIndexesMinterOne = new uint256[](5); - depositIndexesMinterOne[0] = 2550; - depositIndexesMinterOne[1] = 2551; - depositIndexesMinterOne[2] = 2552; - depositIndexesMinterOne[3] = 2553; - depositIndexesMinterOne[4] = 2555; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexesMinterOne, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // configure _minterTwo's NFT position - uint256[] memory depositIndexesMinterTwo = new uint256[](5); - depositIndexesMinterTwo[0] = 2550; - depositIndexesMinterTwo[1] = 2551; - depositIndexesMinterTwo[2] = 2200; - depositIndexesMinterTwo[3] = 2221; - depositIndexesMinterTwo[4] = 2222; - - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: depositIndexesMinterTwo, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: address(_pool) - }); - - // lenders stake their NFTs - _stakeToken(address(_pool), _minterOne, tokenIdOne); - _stakeToken(address(_pool), _minterTwo, tokenIdTwo); - - uint256[] memory depositIndexes = new uint256[](8); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - depositIndexes[5] = 2200; - depositIndexes[6] = 2221; - depositIndexes[7] = 2222; - - // borrower takes actions providing reserves enabling three reserve auctions - // proof of burn events (burn epoch 0) - _assertBurn({ - pool: address(_pool), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0, - rewardsToClaimer: 0, - rewardsToUpdater: 0, - tokensToBurn: 0 - }); - - // auction one - uint256 tokensToBurnE1 = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162663471460 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 4.089968908133195708 * 1e18 - }); - - _assertBurn({ - pool: address(_pool), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 81.799378162663471460 * 1e18, - tokensToBurn: tokensToBurnE1, - interest: 6.443638300196908069 * 1e18, - rewardsToClaimer: 65.439502530130777168 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 4.089968908133173573 * 1e18 - }); - - // auction two - uint256 tokensToBurnE2 = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 308.672122067616182565 * 1e18, - borrowAmount: 1_000 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 11.343637195247653990 * 1e18 - }); - - _assertBurn({ - pool: address(_pool), - epoch: 2, - timestamp: block.timestamp - 24 hours, - burned: 308.672122067616182565 * 1e18, - tokensToBurn: tokensToBurnE2, - interest: 23.936044239893182713 * 1e18, - rewardsToClaimer: 246.937697654092946052 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 15.433606103380809128 * 1e18 - }); - - // auction three - uint256 tokensToBurnE3 = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 676.658832743043390869 * 1e18, - borrowAmount: 2_000 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 18.399335533771072810 * 1e18 - }); - - _assertBurn({ - pool: address(_pool), - epoch: 3, - timestamp: block.timestamp - 24 hours, - burned: 676.658832743043390869 * 1e18, - tokensToBurn: tokensToBurnE3, - interest: 52.421031459480795903 * 1e18, - rewardsToClaimer: 541.327066194434712695 * 1e18, // FIXME: These don't add up to total burned, is that a problem? - rewardsToUpdater: 33.832941637152169543 * 1e18 - }); - - // both stakers claim rewards - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(3, 0), - reward: 51.511621814050026070 * 1e18, - updateRatesReward: 0 - }); - - _unstakeToken({ - owner: _minterTwo, - pool: address(_pool), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(3, 0), - reward: 286.817794557471160699 * 1e18, - updateRatesReward: 0 - }); - } - - - function testWithdrawAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // stake nft - _stakeToken({ - pool: address(_pool), - owner: _minterOne, - tokenId: tokenIdOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162662704349 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming rewards - _updateExchangeRates({ - updater: _updater, - pool: address(_pool), - indexes: depositIndexes, - reward: 4.089968908133134138 * 1e18 - }); - - // check owner can withdraw the NFT and rewards will be automatically claimed - - uint256 snapshot = vm.snapshot(); - - // claimed rewards amount is greater than available tokens in rewards manager contract - - // burn rewards manager tokens and leave only 5 tokens available - changePrank(address(_rewardsManager)); - IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); - - uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); - assertEq(managerBalance, 4.931444746461913452 * 1e18); - - // _minterOne unstakes staked position - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 40.899689081331351737 * 1e18, - updateRatesReward: 0 - }); - - // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 - assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); - // all 5 tokens available in manager balance were used to reward minter one - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); - - vm.revertTo(snapshot); - - // test when enough tokens in rewards manager contracts - // _minterOne unstakes staked position - _unstakeToken({ - owner: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 40.899689081331351737 * 1e18, - updateRatesReward: 0 - }); - - assertEq(PositionManager(address(_positionManager)).ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331351737 * 1e18); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - - // check can't claim rewards twice - _assertNotOwnerOfDepositRevert({ - from: _minterOne, - tokenId: tokenIdOne - }); - } - - function testMultiplePools() external { - skip(10); - - // configure NFT position one - uint256[] memory firstIndexes = new uint256[](5); - firstIndexes[0] = 9; - firstIndexes[1] = 1; - firstIndexes[2] = 2; - firstIndexes[3] = 3; - firstIndexes[4] = 4; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: firstIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - // configure NFT position two - uint256[] memory secondIndexes = new uint256[](4); - secondIndexes[0] = 5; - secondIndexes[1] = 1; - secondIndexes[2] = 3; - secondIndexes[3] = 12; - - uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ - indexes: secondIndexes, - minter: _minterTwo, - mintAmount: 1_000 * 1e18, - pool: address(_poolTwo) - }); - - // minterOne deposits their NFT into the rewards contract - _stakeToken(address(_pool), _minterOne, tokenIdOne); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - uint256 tokensToBurn = _triggerReserveAuctions({ - borrower: _borrower, - tokensToBurn: 81.799378162662704349 * 1e18, - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: address(_pool) - }); - - // check only deposit owner can claim rewards - _assertNotOwnerOfDepositRevert({ - from: _minterTwo, - tokenId: tokenIdOne - }); - - // check rewards earned in one pool shouldn't be claimable by depositors from another pool - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - _claimRewards({ - pool: address(_poolTwo), - from: _minterTwo, - tokenId: tokenIdTwo, - reward: 0, - epochsClaimed: _epochsClaimedArray(0, 0) - }); - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - - // call update exchange rate to enable claiming rewards - _updateExchangeRates({ - updater: _minterOne, - pool: address(_pool), - indexes: firstIndexes, - reward: 4.089968908133134138 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133134138 * 1e18); - - // check owner in pool with accrued interest can properly claim rewards - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - reward: 40.899689081331351737 * 1e18, - epochsClaimed: _epochsClaimedArray(1, 0) - }); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - - } - - /********************/ - /*** FUZZ TESTING ***/ - /********************/ - - function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { - indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to - mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](indexes); - for (uint256 i = 0; i < indexes; ++i) { - depositIndexes[i] = _randomIndex(); - } - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: mintAmount, - pool: address(_pool) - }); - - // stake NFT - _stakeToken(address(_pool), _minterOne, tokenIdOne); - - // calculates a limit index leaving one index above the htp to accrue interest - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - - // start and end new reserve auction - uint256 tokensToBurn= _triggerReserveAuctionsBurnUnknown({ - borrower: _borrower, - borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), - limitIndex: limitIndex, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); - assertGt(_ajnaToken.balanceOf(_updater), 0); - - // calculate rewards earned and compare to percentages for updating and claiming - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertGt(rewardsEarned, 0); - - // claim rewards accrued since deposit - _claimRewards({ - pool: address(_pool), - from: _minterOne, - tokenId: tokenIdOne, - reward: rewardsEarned, - epochsClaimed: _epochsClaimedArray(1, 0) - }); - - // assert rewards claimed is less than ajna tokens burned cap - assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); - } - - function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { - deposits = bound(deposits, 1, 25); // number of deposits to make - reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete - - uint256[] memory tokenIds = new uint256[](deposits); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](3); - for (uint256 j = 0; j < 3; ++j) { - depositIndexes[j] = _randomIndex(); - vm.roll(block.number + 1); // advance block to ensure that the index price is different - } - - address[] memory minters = _getAddresses(deposits); - - // stake variable no of deposits - for(uint256 i = 0; i < deposits; ++i) { - // mint and memorilize Positions - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: minters[i], - mintAmount: 1_000_000_000 * 1e18, - pool: _pool - }); - - tokenIds[i] = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: minters[i], - mintAmount: 1_000_000_000 * 1e18, - pool: address(_pool) - }); - tokenIdToMinter[tokenIds[i]] = minters[i]; - _stakeToken(address(_pool), minters[i], tokenIds[i]); - } - - uint256 updaterBalance = _ajnaToken.balanceOf(_updater); - - for(uint i = 0; i < deposits; i++) { - minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); - } - - // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch - for(uint i = 0; i < reserveAuctions; ++i) { - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - - // start and end new reserve auction - uint256 tokensBurned = _triggerReserveAuctionsBurnUnknown({ - borrower: _borrower, - borrowAmount: 10_000 * 1e18, - limitIndex: limitIndex, - pool: address(_pool) - }); - - // call update exchange rate to enable claiming rewards - assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); - - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); - - // ensure updater gets reward for updating exchange rate - assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); - - // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned - assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); - - updaterBalance = _ajnaToken.balanceOf(_updater); - - // pick random NFTs from all NFTs to claim rewards - uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); - - for(uint j = 0; j < randomNfts.length; j++) { - address minterAddress = tokenIdToMinter[randomNfts[j]]; - changePrank(minterAddress); - - (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); - - // select random epoch to claim reward - uint256 epochToClaim = lastInteractionEpoch < _pool.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _pool.currentBurnEpoch()) : lastInteractionEpoch; - - uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); - assertGt(rewardsEarned, 0); - - _rewardsManager.claimRewards(randomNfts[j], _pool.currentBurnEpoch()); - - // ensure user gets reward - assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); - minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); - } - } - } - - function testClaimRewardsFreezeUnclaimedYield() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: address(_pool) - }); - - _stakeToken(address(_pool), _minterOne, tokenIdOne); - - uint256 currentBurnEpoch = _pool.currentBurnEpoch(); - - changePrank(_minterOne); - // should revert if the epoch to claim is not available yet - vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); - - // user should be able to claim rewards for current epoch - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - } - -} diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol new file mode 100644 index 000000000..82ff3b40b --- /dev/null +++ b/tests/forge/RewardsManager.t.sol @@ -0,0 +1,2048 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; + +import 'src/RewardsManager.sol'; +import 'src/interfaces/rewards/IRewardsManager.sol'; + +import 'src/interfaces/position/IPositionManager.sol'; +import 'src/PositionManager.sol'; +import 'src/PoolInfoUtils.sol'; +import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; + +import { _borrowFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; + +import { Token } from './utils/Tokens.sol'; +import { ERC20HelperContract } from './ERC20Pool/ERC20DSTestPlus.sol'; + +contract RewardsManagerTest is ERC20HelperContract { + + address internal _bidder; + address internal _minterOne; + address internal _minterTwo; + address internal _minterThree; + address internal _minterFour; + address internal _minterFive; + address internal _updater; + address internal _updater2; + + ERC20 internal _ajnaToken; + + RewardsManager internal _rewardsManager; + PositionManager internal _positionManager; + + Token internal _collateralOne; + Token internal _quoteOne; + ERC20Pool internal _poolOne; + Token internal _collateralTwo; + Token internal _quoteTwo; + ERC20Pool internal _poolTwo; + + event ClaimRewards(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId, uint256[] epochsClaimed, uint256 amount); + event Stake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); + event UpdateExchangeRates(address indexed caller, address indexed ajnaPool, uint256[] indexesUpdated, uint256 rewardsClaimed); + event Unstake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); + event MoveStakedLiquidity( + uint256 tokenId, + uint256[] fromIndexes, + uint256[] toIndexes + ); + + uint256 constant BLOCKS_IN_DAY = 7200; + mapping (uint256 => address) internal tokenIdToMinter; + mapping (address => uint256) internal minterToBalance; + + struct MintAndMemorializeParams { + uint256[] indexes; + address minter; + uint256 mintAmount; + ERC20Pool pool; + } + + struct TriggerReserveAuctionParams { + uint256 borrowAmount; + uint256 limitIndex; + ERC20Pool pool; + } + + function setUp() external { + vm.makePersistent(_ajna); + + _ajnaToken = ERC20(_ajna); + _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); + _rewardsManager = new RewardsManager(_ajna, _positionManager); + _poolUtils = new PoolInfoUtils(); + + _collateralOne = new Token("Collateral 1", "C1"); + _quoteOne = new Token("Quote 1", "Q1"); + _poolOne = ERC20Pool(_poolFactory.deployPool(address(_collateralOne), address(_quoteOne), 0.05 * 10**18)); + + _collateralTwo = new Token("Collateral 2", "C2"); + _quoteTwo = new Token("Quote 2", "Q2"); + _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); + + // provide initial ajna tokens to staking rewards contract + deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); + + // instantiate test minters + _minterOne = makeAddr("minterOne"); + _minterTwo = makeAddr("minterTwo"); + _minterThree = makeAddr("minterThree"); + _minterFour = makeAddr("minterFour"); + _minterFive = makeAddr("minterFive"); + + // instantiate test bidder + _bidder = makeAddr("bidder"); + changePrank(_bidder); + deal(_ajna, _bidder, 900_000_000 * 10**18); + + // instantiate test updater + _updater = makeAddr("updater"); + _updater2 = makeAddr("updater2"); + } + + // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt + function _createTestBorrower(ERC20Pool pool_, string memory borrowerName_, uint256 borrowAmount_, uint256 limitIndex_) internal returns (address borrower_, uint256 collateralToPledge_) { + borrower_ = makeAddr(borrowerName_); + + changePrank(borrower_); + + Token collateral = Token(pool_.collateralAddress()); + Token quote = Token(pool_.quoteTokenAddress()); + + // deal twice as much quote so the borrower has sufficient quote to repay the loan + deal(address(quote), borrower_, Maths.wmul(borrowAmount_, Maths.wad(2))); + + // approve tokens + collateral.approve(address(pool_), type(uint256).max); + quote.approve(address(pool_), type(uint256).max); + + collateralToPledge_ = _requiredCollateral(pool_, borrowAmount_, limitIndex_); + deal(address(collateral), borrower_, collateralToPledge_); + } + + function _stakeToken(address pool_, address owner_, uint256 tokenId_) internal { + changePrank(owner_); + + // approve and deposit NFT into rewards contract + _positionManager.approve(address(_rewardsManager), tokenId_); + vm.expectEmit(true, true, true, true); + emit Stake(owner_, address(pool_), tokenId_); + _rewardsManager.stake(tokenId_); + + // check token was transferred to rewards contract + assertEq(_positionManager.ownerOf(tokenId_), address(_rewardsManager)); + } + + function _unstakeToken( + address minter, + address pool, + uint256[] memory claimedArray, + uint256 tokenId, + uint256 reward, + uint256 updateRatesReward + ) internal { + + changePrank(minter); + + if (updateRatesReward != 0) { + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_poolOne), _positionManager.getPositionIndexes(tokenId), updateRatesReward); + } + + vm.expectEmit(true, true, true, true); + emit ClaimRewards(minter, pool, tokenId, claimedArray, reward); + vm.expectEmit(true, true, true, true); + emit Unstake(minter, address(pool), tokenId); + _rewardsManager.unstake(tokenId); + assertEq(_positionManager.ownerOf(tokenId), minter); + + // check token was transferred from rewards contract to minter + assertEq(_positionManager.ownerOf(tokenId), address(minter)); + + // invariant: all bucket snapshots are removed for the token id that was unstaken + for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); + assertEq(lps, 0); + assertEq(rate, 0); + } + } + + function _triggerReserveAuctionsNoTake(TriggerReserveAuctionParams memory params_) internal { + // create a new borrower to write state required for reserve auctions + ( + address borrower, + uint256 collateralToPledge + ) = _createTestBorrower(params_.pool, string("borrower"), params_.borrowAmount, params_.limitIndex); + + // borrower drawsDebt from the pool + params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + params_.pool.repayDebt(borrower, Maths.wdiv(params_.borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(params_.pool), type(uint256).max); + params_.pool.startClaimableReserveAuction(); + } + + function _assertBurn( + address pool, + uint256 epoch, + uint256 timestamp, + uint256 interest, + uint256 burned + ) internal { + + (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); + + assertEq(bETimestamp, timestamp); + assertEq(bEInterest, interest); + assertEq(bEBurned, burned); + } + + + function _updateExchangeRates(address updater, address pool, uint256[] memory depositIndexes, uint256 reward) internal { + uint256 initialUpdaterTokenBalance = _ajnaToken.balanceOf(updater); + + changePrank(updater); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(updater, pool, depositIndexes, reward); + _rewardsManager.updateBucketExchangeRatesAndClaim(pool, depositIndexes); + assertEq(_ajnaToken.balanceOf(updater), initialUpdaterTokenBalance + reward); + } + + + function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { + epochsClaimed_ = new uint256[](numberOfAuctions_); + uint256 claimEpoch = lastClaimed_; // starting index, not inclusive + + for (uint256 i = 0; i < numberOfAuctions_; i++) { + epochsClaimed_[i] = claimEpoch + 1; + claimEpoch += 1; + } + } + + function _mintAndMemorializePositionNFT(MintAndMemorializeParams memory params_) internal returns (uint256 tokenId_) { + changePrank(params_.minter); + + Token collateral = Token(params_.pool.collateralAddress()); + Token quote = Token(params_.pool.quoteTokenAddress()); + + // deal tokens to the minter + deal(address(collateral), params_.minter, 250_000 * 1e18); + deal(address(quote), params_.minter, params_.mintAmount * params_.indexes.length); + + // approve tokens + collateral.approve(address(params_.pool), type(uint256).max); + quote.approve(address(params_.pool), type(uint256).max); + + IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(params_.minter, address(params_.pool), keccak256("ERC20_NON_SUBSET_HASH")); + tokenId_ = _positionManager.mint(mintParams); + + uint256[] memory lpBalances = new uint256[](params_.indexes.length); + + for (uint256 i = 0; i < params_.indexes.length; i++) { + params_.pool.addQuoteToken(params_.mintAmount, params_.indexes[i], type(uint256).max); + (lpBalances[i], ) = params_.pool.lenderInfo(params_.indexes[i], params_.minter); + } + + params_.pool.increaseLPsAllowance(address(_positionManager), params_.indexes, lpBalances); + + // construct memorialize params struct + IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( + tokenId_, params_.indexes + ); + + _positionManager.memorializePositions(memorializeParams); + + // register position manager as lender at memorialized indexes (for LP test assertions) + _registerLender(address(_positionManager), params_.indexes); + } + + function _triggerReserveAuctions(TriggerReserveAuctionParams memory params_) internal returns (uint256 tokensBurned_) { + // create a new borrower to write state required for reserve auctions + address borrower = makeAddr("borrower"); + + changePrank(borrower); + + Token collateral = Token(params_.pool.collateralAddress()); + Token quote = Token(params_.pool.quoteTokenAddress()); + + deal(address(quote), borrower, params_.borrowAmount); + + // approve tokens + collateral.approve(address(params_.pool), type(uint256).max); + quote.approve(address(params_.pool), type(uint256).max); + + uint256 collateralToPledge = _requiredCollateral(params_.pool, params_.borrowAmount, params_.limitIndex); + deal(address(collateral), borrower, collateralToPledge); + + // borrower drawsDebt from the pool + params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + params_.pool.repayDebt(borrower, params_.borrowAmount, 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(params_.pool), type(uint256).max); + params_.pool.startClaimableReserveAuction(); + + // Can't trigger reserve auction if less than two weeks have passed since last auction + vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); + params_.pool.startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(address(params_.pool)); + + // take claimable reserves + params_.pool.takeReserves(curClaimableReservesRemaining); + + (,, tokensBurned_) = IPool(params_.pool).burnInfo(IPool(params_.pool).currentBurnEpoch()); + + return tokensBurned_; + } + + function testStakeToken() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // configure NFT position two + depositIndexes = new uint256[](4); + depositIndexes[0] = 5; + depositIndexes[1] = 1; + depositIndexes[2] = 3; + depositIndexes[3] = 12; + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: _poolTwo + }); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // check only owner of an NFT can deposit it into the rewards contract + changePrank(_minterTwo); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.stake(tokenIdOne); + + // minterOne deposits their NFT into the rewards contract + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + // check deposit state + (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); + assertEq(owner, _minterOne); + assertEq(pool, address(_poolOne)); + assertEq(interactionBurnEvent, 0); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); + // check deposit state + (owner, pool, interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdTwo); + assertEq(owner, _minterTwo); + assertEq(pool, address(_poolTwo)); + assertEq(interactionBurnEvent, 0); + } + + function testUpdateExchangeRatesAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: _poolOne + }); + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater), 4.089968908133149070 * 1e18); + + // check only deposit owner can claim rewards + uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + + // check rewards earned + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); + assertEq(rewardsEarned, 40.899689081331421210 * 1e18); + + // claim rewards accrued since deposit + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 0); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); + + // check can't claim rewards twice + vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + + // check deposit state + (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); + assertEq(owner, _minterOne); + assertEq(pool, address(_poolOne)); + assertEq(interactionBurnEvent, 1); + assertEq(_positionManager.ownerOf(tokenIdOne), address(_rewardsManager)); + + // assert rewards claimed is less than ajna tokens burned cap + assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); + + // check can't call update exchange rate after the update period has elapsed + skip(2 weeks); + + // Although an `UpdateExchangeRates` is emmited the rates are not updated, as demonstrated by updateRewards == 0 + uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(updateRewards, 0); + } + + function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // epoch 0 - 1 is checked for rewards + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + }); + + // first reserve auction happens successfully -> epoch 1 + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 4.089968908133149070 * 1e18 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162662881374 * 1e18, + interest: 6.443638300196908069 * 1e18 + }); + + // second reserve auction happens successfully -> epoch 2 + tokensToBurn += _triggerReserveAuctions(triggerReserveAuctionParams); + + // check owner can withdraw the NFT and rewards will be automatically claimed + _unstakeToken({ + minter: _minterOne, + pool: address(_poolOne), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(2, 0), + reward: 86.809555428378605150 * 1e18, + updateRatesReward: 4.173624213367915365 * 1e18 + }); + } + + function testWithdrawAndClaimRewardsNoReserveTake() external { + + // healthy epoch, bad epoch + + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // epoch 0 - 1 is checked for rewards + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + }); + + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + _triggerReserveAuctions(triggerReserveAuctionParams); + + //call update exchange rate to enable claiming rewards for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 4.089968908133149070 * 1e18 + }); + + skip(2 weeks); + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + _triggerReserveAuctionsNoTake(triggerReserveAuctionParams); + + _assertBurn({ + pool: address(_poolOne), + epoch: 1, + timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), + burned: 81.799378162662881374 * 1e18, + interest: 6.443638300196908069 * 1e18 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 2, + timestamp: block.timestamp, + burned: 0, + interest: 0 + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 4.206490995172302285 * 1e18 + }); + } + + // two lenders stake their positions in the pool + // staker one bucket bankrupt, staker two bucket active + // interest accrued to both buckets, but staker one receives no rewards + function testClaimRewardsBankruptBucket() external { + + address borrower = makeAddr("borrower"); + address borrowerTwo = makeAddr("borrowerTwo"); + + deal(address(_collateral), borrower, 4 * 1e18); + changePrank(borrower); + _collateral.approve(address(_pool), type(uint256).max); + _quote.approve(address(_pool), type(uint256).max); + + deal(address(_collateral), borrowerTwo, 1_000 * 1e18); + changePrank(borrowerTwo); + _collateral.approve(address(_pool), type(uint256).max); + _quote.approve(address(_pool), type(uint256).max); + + address[] memory transferors = new address[](1); + transferors[0] = address(_positionManager); + + changePrank(_minterOne); + deal(address(_quote), _minterOne, 500_000_000 * 1e18); + _quote.approve(address(_pool), type(uint256).max); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + changePrank(_minterTwo); + deal(address(_quote), _minterTwo, 500_000_000 * 1e18); + _quote.approve(address(_pool), type(uint256).max); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + /*****************************/ + /*** Initialize Pool State ***/ + /*****************************/ + + // Lender adds Quote token accross 5 prices + _addInitialLiquidity({ + from: _minterOne, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 30_000 * 1e18, + index: _i9_52 + }); + + // first borrower adds collateral token and borrows + _pledgeCollateral({ + from: borrower, + borrower: borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); + + // second borrower adds collateral token and borrows + _pledgeCollateral({ + from: borrowerTwo, + borrower: borrowerTwo, + amount: 1_000 * 1e18 + }); + _borrow({ + from: borrowerTwo, + amount: 7_980 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); + + _borrow({ + from: borrowerTwo, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); + + /*****************************/ + /*** Lenders Deposits NFTs ***/ + /*****************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](1); + uint256[] memory depositIndexes2 = new uint256[](1); + depositIndexes[0] = _i9_91; + depositIndexes2[0] = _i9_81; + + ERC20Pool pool = ERC20Pool(address(_pool)); + + // stake NFT position one + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 2_000 * 1e18, + pool: pool + }); + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + changePrank(_minterOne); + _stakeToken(address(pool), _minterOne, tokenIdOne); + + // stake NFT position two + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes2, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: pool + }); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); + changePrank(_minterTwo); + _stakeToken(address(pool), _minterTwo, tokenIdTwo); + + /***********************************/ + /*** Borrower Bankrupts A Bucket ***/ + /***********************************/ + + // Skip to make borrower two undercollateralized + skip(100 days); + + deal(address(_quote), _minterTwo, 500_000_000 * 1e18); + + _kick({ + from: _minterTwo, + borrower: borrowerTwo, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + + // skip ahead so take can be called on the loan + skip(10 hours); + + // take entire collateral + _take({ + from: _minterTwo, + borrower: borrowerTwo, + maxCollateral: 1_000 * 1e18, + bondChange: 6.531114528261135360 * 1e18, + givenAmount: 653.111452826113536000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); + + _settle({ + from: _minterTwo, + borrower: borrowerTwo, + maxDepth: 10, + settledDebt: 9_891.935520844277346922 * 1e18 + }); + + // bucket is insolvent, balances are reset + _assertBucket({ + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lower priced bucket isn't bankrupt, but exchange rate has decreased + _assertBucket({ + index: _i9_81, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 4_936.865619773958011818 * 1e18, + exchangeRate: 0.493686561977395801 * 1e18 + }); + + /***********************/ + /*** Reserve Auction ***/ + /***********************/ + + // skip some time to accumulate reserves + skip(50 days); + + // update pool reserves + _pool.updateInterest(); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(_pool), type(uint256).max); + _pool.startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(address(_pool)); + + // take claimable reserves + changePrank(_bidder); + _pool.takeReserves(curClaimableReservesRemaining); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + + // _minterOne withdraws and claims rewards, rewards should be 0 + _unstakeToken({ + minter: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + updateRatesReward: 0 + }); + + // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased + _unstakeToken({ + minter: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + updateRatesReward: 0 + }); + } + + function testClaimRewardsCap() external { + skip(10); + + /***************************/ + /*** Lender Deposits NFT ***/ + /***************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](2); + uint256[] memory depositIndex1 = new uint256[](1); + uint256[] memory depositIndex2 = new uint256[](1); + depositIndexes[0] = 2770; + depositIndexes[1] = 2771; + depositIndex1[0] = 2771; + depositIndex2[0] = 2770; + + // configure NFT position one + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 10_000 * 1e18, + pool: _poolOne + }); + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + changePrank(_minterOne); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + /************************************/ + /*** Borrower One Accrue Interest ***/ + /************************************/ + + // borrower1 borrows + (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); + changePrank(borrower1); + + _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); + + // pass time to allow interest to accrue + skip(2 hours); + + // borrower1 repays their loan + (uint256 debt, , ) = _poolOne.borrowerInfo(borrower1); + _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(_poolOne), type(uint256).max); + _poolOne.startClaimableReserveAuction(); + + // borrower1 now takes out more debt to accumulate more interest + changePrank(borrower1); + _poolOne.drawDebt(borrower1, 2_000 * 1e18, 2770, 0); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(address(_poolOne)); + + // take claimable reserves + changePrank(_bidder); + _poolOne.takeReserves(curClaimableReservesRemaining); + + // recorder updates the change in exchange rates in the first index + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndex1, + reward: 0.007104600671645296 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); + + _assertBurn({ + pool: address(_poolOne), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 0.284184026893324971 * 1e18, + interest: 0.000048562908902619 * 1e18 + }); + + // skip more time to allow more interest to accrue + skip(10 days); + + // borrower1 repays their loan again + changePrank(borrower1); + (debt, , ) = _poolOne.borrowerInfo(borrower1); + _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); + + // recorder updates the change in exchange rates in the second index + _updateExchangeRates({ + updater: _updater2, + pool: address(_poolOne), + depositIndexes: depositIndex2, + reward: 0.021313802017687201 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); + + /*******************************************/ + /*** Lender Withdraws And Claims Rewards ***/ + /*******************************************/ + + // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap + _unstakeToken({ + minter: _minterOne, + pool: address(_poolOne), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0.298393228234161298 * 1e18, + updateRatesReward: 0 + }); + } + + function testMultiPeriodRewardsSingleClaim() external { + skip(10); + + uint256 totalTokensBurned; + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](10); + depositIndexes[0] = 5995; + depositIndexes[1] = 5996; + depositIndexes[2] = 5997; + depositIndexes[3] = 5998; + depositIndexes[4] = 5999; + depositIndexes[5] = 6000; + depositIndexes[6] = 6001; + depositIndexes[7] = 6002; + depositIndexes[8] = 6003; + depositIndexes[9] = 6004; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: _poolOne + }); + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: _poolOne + }); + totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 20.449844540665683990 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); + + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 204.498445406656758711 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + + // trigger second reserve auction + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: _poolOne + }); + totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314416 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /*****************************/ + /*** Third Reserve Auction ***/ + /*****************************/ + + // trigger third reserve auction + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: _poolOne + }); + totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); + + // skip updating exchange rates and check available rewards + uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // snapshot calling update exchange rate + uint256 snapshot = vm.snapshot(); + + // call update exchange rate + changePrank(_updater2); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606335 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 517.072612267116262774 * 1e18); + assertGt(rewardsEarned, rewardsEarnedNoUpdate); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // revert to no update state + vm.revertTo(snapshot); + + /******************************/ + /*** Fourth Reserve Auction ***/ + /******************************/ + + // triger fourth reserve auction + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: _poolOne + }); + totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); + + // check rewards earned + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + + // call update exchange rate + changePrank(_updater2); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 0); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + + // check rewards earned won't increase since previous update was missed + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + + /*****************************/ + /*** Fifth Reserve Auction ***/ + /*****************************/ + + // triger fourth reserve auction + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: _poolOne + }); + totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate + changePrank(_updater2); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905357 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); + + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(rewardsEarned, 493.039460320049732206 * 1e18); + + // claim all rewards accrued since deposit + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 0); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(5, 0), rewardsEarned); + _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + } + + function testMoveStakedLiquidity() external { + skip(10); + + /*****************/ + /*** Stake NFT ***/ + /*****************/ + + uint256[] memory firstIndexes = new uint256[](5); + firstIndexes[0] = 2550; + firstIndexes[1] = 2551; + firstIndexes[2] = 2552; + firstIndexes[3] = 2553; + firstIndexes[4] = 2555; + + // configure NFT position + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: firstIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + uint256 tokenId = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // stake nft + _stakeToken(address(_poolOne), _minterOne, tokenId); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + uint256 expiry = block.timestamp + 1000; + uint256[] memory secondIndexes = new uint256[](5); + secondIndexes[0] = 2556; + secondIndexes[1] = 2557; + secondIndexes[2] = 2558; + secondIndexes[3] = 2559; + secondIndexes[4] = 2560; + + // check no rewards are claimed on first move + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(0, 0), 0); + + // check MoveLiquidity emits + for (uint256 i = 0; i < firstIndexes.length; ++i) { + vm.expectEmit(true, true, true, true); + emit MoveLiquidity(address(_rewardsManager), tokenId, firstIndexes[i], secondIndexes[i]); + } + + vm.expectEmit(true, true, true, true); + emit MoveStakedLiquidity(tokenId, firstIndexes, secondIndexes); + _rewardsManager.moveStakedLiquidity(tokenId, firstIndexes, secondIndexes, expiry); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2560, + pool: _poolOne + }); + // first reserve auction happens successfully -> epoch 1 + _triggerReserveAuctions(triggerReserveAuctionParams); + + uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + expiry = block.timestamp + 1000; + + // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets + secondIndexes = _positionManager.getPositionIndexes(tokenId); + + // check rewards are claimed from the indexes that the staker is moving away from + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133149070 * 1e18); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570280 * 1e18); + // check MoveLiquidity emits + for (uint256 i = 0; i < firstIndexes.length; ++i) { + vm.expectEmit(true, true, true, true); + emit MoveLiquidity(address(_rewardsManager), tokenId, secondIndexes[i], firstIndexes[i]); + } + vm.expectEmit(true, true, true, true); + emit MoveStakedLiquidity(tokenId, secondIndexes, firstIndexes); + + // check exchange rates are updated + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); + + changePrank(_minterOne); + _rewardsManager.moveStakedLiquidity(tokenId, secondIndexes, firstIndexes, expiry); + + // check that no rewards are available yet in the indexes that the staker moved to + vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + }); + // first reserve auction happens successfully -> epoch 1 + _triggerReserveAuctions(triggerReserveAuctionParams); + + currentBurnEpoch = _poolOne.currentBurnEpoch(); + + /******************************/ + /*** Exchange Rates Updated ***/ + /******************************/ + + // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets + firstIndexes = _positionManager.getPositionIndexes(tokenId); + + _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045926578320550 * 1e18); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + + // claim rewards accrued since second movement of lps + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249745 * 1e18); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function testEarlyAndLateStakerRewards() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + // configure NFT position two + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); + // bucket exchange rates are not changed at the time minter two stakes + assertEq(_poolOne.bucketExchangeRate(2550), 1e18); + assertEq(_poolOne.bucketExchangeRate(2551), 1e18); + assertEq(_poolOne.bucketExchangeRate(2552), 1e18); + assertEq(_poolOne.bucketExchangeRate(2553), 1e18); + assertEq(_poolOne.bucketExchangeRate(2555), 1e18); + _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); + + // borrower borrows and change the exchange rates of buckets + (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); + changePrank(borrower1); + + _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); + + skip(1 days); + + // configure NFT position three one day after early minter + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); + // bucket exchange rates are higher at the time minter three stakes + assertEq(_poolOne.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); + assertEq(_poolOne.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); + _stakeToken(address(_poolOne), _minterThree, tokenIdThree); + + skip(1 days); + + // trigger reserve auction and update rates + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + }); + _triggerReserveAuctions(triggerReserveAuctionParams); + + // unstake and compare rewards and balances of minter two and minter three + _unstakeToken({ + minter: _minterTwo, + pool: address(_poolOne), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 39.908019526547891790 * 1e18, + updateRatesReward: 0 + }); + uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); + assertEq(minterTwoBalance, 39.908019526547891790 * 1e18); + _unstakeToken({ + minter: _minterThree, + pool: address(_poolOne), + tokenId: tokenIdThree, + claimedArray: _epochsClaimedArray(1, 0), + reward: 33.248129642902710516 * 1e18, + updateRatesReward: 0 + }); + uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); + assertEq(minterThreeBalance, 33.248129642902710516 * 1e18); + + assertGt(minterTwoBalance, minterThreeBalance); + } + + // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves + function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { + skip(10); + + /*****************************/ + /*** First Lender Deposits ***/ + /*****************************/ + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: _poolOne + }); + uint256 auctionOneTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + /******************************/ + /*** Second Lender Deposits ***/ + /******************************/ + + // second depositor deposits an NFT representing the same positions into the rewards contract + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); + // second depositor stakes NFT, generating an update reward + _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertLt(idOneRewardsAtOne, auctionOneTokensToBurn); + assertGt(idOneRewardsAtOne, 0); + + // minter one claims rewards accrued since deposit + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 0); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), idOneRewardsAtOne); + _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + + // borrower takes actions providing reserves enabling additional reserve auctions + triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: _poolOne + }); + + // conduct second reserve auction + uint256 auctionTwoTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + /*****************************/ + /*** Third Lender Deposits ***/ + /*****************************/ + + // third depositor deposits an NFT representing the same positions into the rewards contract + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterThree, tokenIdThree); + + /***********************/ + /*** Rewards Claimed ***/ + /***********************/ + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo, auctionTwoTokensToBurn); + assertGt(idOneRewardsAtTwo, 0); + + uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, auctionTwoTokensToBurn); + assertGt(idTwoRewardsAtTwo, 0); + + // minter one claims rewards accrued after second auction + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 1), idOneRewardsAtTwo); + _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); + + // minter two claims rewards accrued since deposit + changePrank(_minterTwo); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterTwo, address(_poolOne), tokenIdTwo, _epochsClaimedArray(1, 1), idTwoRewardsAtTwo); + _rewardsManager.claimRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterTwo), idTwoRewardsAtTwo + 8.175422393077328665 * 1e18); + + // check there are no remaining rewards available after claiming + uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _poolOne.currentBurnEpoch()); + assertEq(remainingRewards, 0); + } + + function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { + // configure _minterOne's NFT position + uint256[] memory depositIndexesMinterOne = new uint256[](5); + depositIndexesMinterOne[0] = 2550; + depositIndexesMinterOne[1] = 2551; + depositIndexesMinterOne[2] = 2552; + depositIndexesMinterOne[3] = 2553; + depositIndexesMinterOne[4] = 2555; + MintAndMemorializeParams memory mintMemorializeParamsMinterOne = MintAndMemorializeParams({ + indexes: depositIndexesMinterOne, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: _poolOne + }); + + // configure _minterTwo's NFT position + uint256[] memory depositIndexesMinterTwo = new uint256[](5); + depositIndexesMinterTwo[0] = 2550; + depositIndexesMinterTwo[1] = 2551; + depositIndexesMinterTwo[2] = 2200; + depositIndexesMinterTwo[3] = 2221; + depositIndexesMinterTwo[4] = 2222; + MintAndMemorializeParams memory mintMemorializeParamsMinterTwo = MintAndMemorializeParams({ + indexes: depositIndexesMinterTwo, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: _poolOne + }); + + uint256[] memory depositIndexes = new uint256[](8); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + depositIndexes[5] = 2200; + depositIndexes[6] = 2221; + depositIndexes[7] = 2222; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterOne); + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterTwo); + + // lenders stake their NFTs + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); + + // borrower takes actions providing reserves enabling three reserve auctions + _triggerReserveAuctions(TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + })); + + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 4.089968908133202125 * 1e18 + }); + + _triggerReserveAuctions(TriggerReserveAuctionParams({ + borrowAmount: 1_000 * 1e18, + limitIndex: 2555, + pool: _poolOne + })); + + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 13.717705175494265742 * 1e18 + }); + + _triggerReserveAuctions(TriggerReserveAuctionParams({ + borrowAmount: 2_000 * 1e18, + limitIndex: 2555, + pool: _poolOne + })); + + _updateExchangeRates({ + updater: _updater, + pool: address(_poolOne), + depositIndexes: depositIndexes, + reward: 27.568516982211953592 * 1e18 + }); + + // proof of burn events + _assertBurn({ + pool: address(_poolOne), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 1, + timestamp: block.timestamp - (52 weeks + 72 hours), + interest: 6.443638300196908069 * 1e18, + burned: 81.799378162664356589 * 1e18 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 2, + timestamp: block.timestamp - (26 weeks + 48 hours), + burned: 356.153481672547831475 * 1e18, + interest: 28.092564949680668737 * 1e18 + }); + + _assertBurn({ + pool: address(_poolOne), + epoch: 3, + timestamp: block.timestamp - 24 hours, + burned: 907.523821316786357044 * 1e18, + interest: 71.814132054505950833 * 1e18 + }); + + // both stakers claim rewards + _unstakeToken({ + minter: _minterOne, + pool: address(_poolOne), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(3, 0), + reward: 75.626985109732100715 * 1e18, + updateRatesReward: 0 + }); + + _unstakeToken({ + minter: _minterTwo, + pool: address(_poolOne), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(3, 0), + reward: 378.134925548660503565 * 1e18, + updateRatesReward: 0 + }); + } + + function testUnstakeToken() external { + skip(10); + + address nonOwner = makeAddr("nonOwner"); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + // only owner should be able to withdraw the NFT + changePrank(nonOwner); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.unstake(tokenIdOne); + + // check owner can withdraw the NFT + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_poolOne), tokenIdOne); + _rewardsManager.unstake(tokenIdOne); + assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); + + // deposit information should have been deleted on withdrawal + (address owner, address pool, uint256 interactionBlock) = _rewardsManager.getStakeInfo(tokenIdOne); + assertEq(owner, address(0)); + assertEq(pool, address(0)); + assertEq(interactionBlock, 0); + } + + function testWithdrawAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: _poolOne + }); + + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertGt(_ajnaToken.balanceOf(_updater), 0); + + // check owner can withdraw the NFT and rewards will be automatically claimed + + uint256 snapshot = vm.snapshot(); + + // claimed rewards amount is greater than available tokens in rewards manager contract + + // burn rewards manager tokens and leave only 5 tokens available + changePrank(address(_rewardsManager)); + IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); + + uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); + assertEq(managerBalance, 4.931444746461898520 * 1e18); + + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_poolOne), tokenIdOne); + _rewardsManager.unstake(tokenIdOne); + + // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 + assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); + // all 5 tokens available in manager balance were used to reward minter one + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); + + vm.revertTo(snapshot); + + // test when enough tokens in rewards manager contracts + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_poolOne), tokenIdOne); + _rewardsManager.unstake(tokenIdOne); + assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421210 * 1e18); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + + uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); + + // check can't claim rewards twice + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + } + + function testMultiplePools() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexesOne = new uint256[](5); + depositIndexesOne[0] = 9; + depositIndexesOne[1] = 1; + depositIndexesOne[2] = 2; + depositIndexesOne[3] = 3; + depositIndexesOne[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexesOne, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // configure NFT position two + uint256[] memory depositIndexesTwo = new uint256[](4); + depositIndexesTwo[0] = 5; + depositIndexesTwo[1] = 1; + depositIndexesTwo[2] = 3; + depositIndexesTwo[3] = 12; + mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexesTwo, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: _poolTwo + }); + + uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // minterOne deposits their NFT into the rewards contract + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: _poolOne + }); + + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + uint256 currentBurnEpochPoolOne = _poolOne.currentBurnEpoch(); + + // check only deposit owner can claim rewards + changePrank(_minterTwo); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); + + // check rewards earned in one pool shouldn't be claimable by depositors from another pool + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + _rewardsManager.claimRewards(tokenIdTwo, _poolTwo.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + + // call update exchange rate to enable claiming rewards + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 0); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_poolOne), depositIndexesOne, 4.089968908133149070 * 1e18); + uint256 updateReward = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexesOne); + assertEq(_ajnaToken.balanceOf(_minterOne), updateReward); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); + + // check owner in pool with accrued interest can properly claim rewards + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + } + + /********************/ + /*** FUZZ TESTING ***/ + /********************/ + + function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { + // calculate the required collateral based upon the borrow amount and index price + (uint256 interestRate, ) = pool_.interestRateInfo(); + uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient + uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); + requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; + } + + // Helper function that returns a random subset from array + function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { + uint256[] memory copyOfArray = new uint256[](array.length); + for(uint j = 0; j < copyOfArray.length; j++){ + copyOfArray[j] = array[j]; + } + uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); + subsetArray = new uint256[](randomNoOfNfts); + for(uint256 i = 0; i < randomNoOfNfts; i++) { + uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); + subsetArray[i] = copyOfArray[randomIndex]; + copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; + } + } + + // Returns N addresses array + function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { + addresses_ = new address[](noOfAddress); + for(uint i = 0; i < noOfAddress; i++) { + addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); + } + } + + function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { + indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to + mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](indexes); + for (uint256 i = 0; i < indexes; ++i) { + depositIndexes[i] = _randomIndex(); + } + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: mintAmount, + pool: _poolOne + }); + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + + // stake NFT + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + // calculates a limit index leaving one index above the htp to accrue interest + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), + limitIndex: limitIndex, + pool: _poolOne + }); + + uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + assertGt(_ajnaToken.balanceOf(_updater), 0); + + // calculate rewards earned and compare to percentages for updating and claiming + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertGt(rewardsEarned, 0); + + // claim rewards accrued since deposit + changePrank(_minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 0); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); + _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); + assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); + + // assert rewards claimed is less than ajna tokens burned cap + assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); + } + + function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { + deposits = bound(deposits, 1, 25); // number of deposits to make + reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete + + uint256[] memory tokenIds = new uint256[](deposits); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](3); + for (uint256 j = 0; j < 3; ++j) { + depositIndexes[j] = _randomIndex(); + vm.roll(block.number + 1); // advance block to ensure that the index price is different + } + + address[] memory minters = _getAddresses(deposits); + + // stake variable no of deposits + for(uint256 i = 0; i < deposits; ++i) { + // mint and memorilize Positions + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: minters[i], + mintAmount: 1_000_000_000 * 1e18, + pool: _poolOne + }); + + tokenIds[i] = _mintAndMemorializePositionNFT(mintMemorializeParams); + tokenIdToMinter[tokenIds[i]] = minters[i]; + _stakeToken(address(_poolOne), minters[i], tokenIds[i]); + } + + uint256 updaterBalance = _ajnaToken.balanceOf(_updater); + + for(uint i = 0; i < deposits; i++) { + minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); + } + + // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch + for(uint i = 0; i < reserveAuctions; ++i) { + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ + borrowAmount: 10_000 * 1e18, + limitIndex: limitIndex, + pool: _poolOne + }); + + // start and end new reserve auction + uint256 tokensBurned = _triggerReserveAuctions(triggerReserveAuctionParams); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); + + // ensure updater gets reward for updating exchange rate + assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); + + // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned + assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); + + updaterBalance = _ajnaToken.balanceOf(_updater); + + // pick random NFTs from all NFTs to claim rewards + uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); + + for(uint j = 0; j < randomNfts.length; j++) { + address minterAddress = tokenIdToMinter[randomNfts[j]]; + changePrank(minterAddress); + + (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); + + // select random epoch to claim reward + uint256 epochToClaim = lastInteractionEpoch < _poolOne.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _poolOne.currentBurnEpoch()) : lastInteractionEpoch; + + uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); + assertGt(rewardsEarned, 0); + + _rewardsManager.claimRewards(randomNfts[j], _poolOne.currentBurnEpoch()); + + // ensure user gets reward + assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); + minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); + } + } + } + + function testClaimRewardsFreezeUnclaimedYield() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: _poolOne + }); + + uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); + _stakeToken(address(_poolOne), _minterOne, tokenIdOne); + + uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); + + changePrank(_minterOne); + // should revert if the epoch to claim is not available yet + vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); + + // user should be able to claim rewards for current epoch + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + } + +} diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 9c0aa5236..0187edb90 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -10,9 +10,11 @@ import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import 'src/interfaces/pool/IPool.sol'; import 'src/interfaces/pool/commons/IPoolEvents.sol'; import 'src/interfaces/pool/IERC3156FlashBorrower.sol'; + import 'src/PoolInfoUtils.sol'; import 'src/libraries/external/Auctions.sol'; +import 'src/libraries/internal/Maths.sol'; abstract contract DSTestPlus is Test, IPoolEvents { @@ -29,9 +31,9 @@ abstract contract DSTestPlus is Test, IPoolEvents { /*** Pools ***/ /*************/ - IPool internal _pool; - PoolInfoUtils internal _poolUtils; - uint256 internal _startTime; + IPool internal _pool; + PoolInfoUtils internal _poolUtils; + uint256 internal _startTime; uint256 internal _p1505_26 = 1_505.263728469068226832 * 1e18; uint256 internal _p1004_98 = 1_004.989662429170775094 * 1e18; From 6c7579146ce10744d41202ed1afea177cc53edbd Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Thu, 30 Mar 2023 14:05:15 -0400 Subject: [PATCH 34/70] interest accrue at LUP vs HTP (#696) * Accrue interest at LUP - if lup index > htp index (lup price lower than htp) then accrue interest at LUP * Add accrue interest at LUP in invariants logic, remove FIXME test --------- Co-authored-by: grandizzy --- src/libraries/external/PoolCommons.sol | 29 +-- .../ERC20PoolInterestRateAndEMAs.t.sol | 169 ++++++++++++ .../ERC20PoolLiquidationsArbTake.t.sol | 32 +-- .../ERC20PoolLiquidationsDepositTake.t.sol | 34 +-- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 10 +- .../ERC20PoolLiquidationsSettle.t.sol | 74 +++--- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 76 +++--- .../ERC20Pool/invariants/base/BaseHandler.sol | 21 +- .../regression/RegressionTestReserves.t.sol | 16 +- .../ERC721Pool/ERC721PoolCollateral.t.sol | 242 ++++++++++++------ .../ERC721PoolLiquidationsDepositTake.t.sol | 2 +- .../ERC721PoolLiquidationsKick.t.sol | 2 +- .../ERC721PoolLiquidationsTake.t.sol | 16 +- tests/forge/RewardsManager.t.sol | 36 +-- 14 files changed, 510 insertions(+), 249 deletions(-) diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 1410d4fb6..16b103a58 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -200,19 +200,18 @@ library PoolCommons { newInflator_ = Maths.wmul(poolState_.inflator, pendingFactor); uint256 htp = Maths.wmul(thresholdPrice_, newInflator_); - uint256 htpIndex; - if (htp > MAX_PRICE) - // if HTP is over the highest price bucket then no buckets earn interest - htpIndex = 1; - else if (htp < MIN_PRICE) - // if HTP is under the lowest price bucket then all buckets earn interest - htpIndex = MAX_FENWICK_INDEX; - else - htpIndex = _indexOf(htp); - - uint256 depositAboveHtp = Deposits.prefixSum(deposits_, htpIndex); - - if (depositAboveHtp != 0) { + uint256 accrualIndex; + if (htp > MAX_PRICE) accrualIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest + else if (htp < MIN_PRICE) accrualIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest + else accrualIndex = _indexOf(htp); // else HPT bucket earn interest + + uint256 lupIndex = Deposits.findIndexOfSum(deposits_, poolState_.debt); + // accrual price is less of lup and htp, and prices decrease as index increases + if (lupIndex > accrualIndex) accrualIndex = lupIndex; + + uint256 interestEarningDeposit = Deposits.prefixSum(deposits_, accrualIndex); + + if (interestEarningDeposit != 0) { newInterest_ = Maths.wmul( _lenderInterestMargin(_utilization(emaParams_.debtEma, emaParams_.depositEma)), Maths.wmul(pendingFactor - Maths.WAD, poolState_.debt) @@ -221,8 +220,8 @@ library PoolCommons { // Scale the fenwick tree to update amount of debt owed to lenders Deposits.mult( deposits_, - htpIndex, - (newInterest_ * 1e18) / depositAboveHtp + Maths.WAD // lender factor + accrualIndex, + Maths.floorWdiv(newInterest_, interestEarningDeposit) + Maths.WAD // lender factor ); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index f3b82ddde..937e0e2d4 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -936,4 +936,173 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); } + + function testAccruePoolInterestHtpLup() external tearDown { + _addInitialLiquidity({ + from: _lender, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _lender, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _lender, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _lender, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: _i9_52 + }); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 4000 + }); + _addInitialLiquidity({ + from: _lender, + amount: 30_000 * 1e18, + index: 5000 + }); + + uint256 snapshot = vm.snapshot(); + + // first borrower pledge collateral and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 100 * 1e18, + indexLimit: _i9_72, + newLup: _p9_91 + }); + + + // Assert that HTP < LUP, meaning on accual interest should accrue between HTP - LUP + _assertPool( + PoolParams({ + htp: 0.100096153846153846 * 1e18, + lup: _p9_91, + poolSize: 104_000.00 * 1e18, + pledgedCollateral: 1_000 * 1e18, + encumberedCollateral: 10.093202398300210588 * 1e18, + poolDebt: 100.096153846153846200 * 1e18, + actualUtilization: 0, + targetUtilization: 1 * 1e18, + minDebtAmount: 10.009615384615384620 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + + skip(100 days); + + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: 0.0001 * 1e18, + amountRepaid: 0.0001 * 1e18, + collateralToPull: 0, + newLup: _p9_91 + }); + + // Proof that interest accrued between HTP and LUP + _assertBucket({ + index: _i9_62, + lpBalance: 25_000.00 * 1e18, + collateral: 0, + deposit: 25_000.396460350119775000 * 1e18, + exchangeRate: 1.000015858414004791 * 1e18 + }); + + vm.revertTo(snapshot); + + // first borrower pledge collateral and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 1000 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 9_500 * 1e18, + indexLimit: _i9_72, + newLup: _p9_72 + }); + + // borrower3 pledge collateral and borrows so we have a lower LUP value + _pledgeCollateral({ + from: _borrower3, + borrower: _borrower3, + amount: 10_000_000 * 1e18 + }); + _borrow({ + from: _borrower3, + amount: 65_000 * 1e18, + indexLimit: 7_000, + newLup: 0.014854015662334135 * 1e18 + }); + + // Assert that HTP < LUP, meaning on accual interest should accrue between HTP - LUP + _assertPool( + PoolParams({ + htp: 9.509134615384615389 * 1e18, + lup: 0.014854015662334135 * 1e18, + poolSize: 104_000.00 * 1e18, + pledgedCollateral: 10_001_000.0 * 1e18, + encumberedCollateral: 5_020_301.332014790293374287 * 1e18, + poolDebt: 74_571.634615384615419000 * 1e18, + actualUtilization: 0, + targetUtilization: 1 * 1e18, + minDebtAmount: 3_728.581730769230770950 * 1e18, + loans: 2, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + + skip(100 days); + + // Proof that no interest accrued since no actions have been called against the book + _assertBucket({ + index: 4_000, + lpBalance: 1_000.00 * 1e18, + collateral: 0, + deposit: 1_000.00 * 1e18, + exchangeRate: 1.0 * 1e18 + }); + + _repayDebt({ + from: _borrower3, + borrower: _borrower3, + amountToRepay: 0.0001 * 1e18, + amountRepaid: 0.0001 * 1e18, + collateralToPull: 0, + newLup: 0.014854015662334135 * 1e18 + }); + + // Proof that interest accrued between LUP and HTP + _assertBucket({ + index: 4_000, + lpBalance: 1000.00 * 1e18, + collateral: 0, + deposit: 1_008.406484270040092000 * 1e18, + exchangeRate: 1.008406484270040092 * 1e18 + }); + } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index 1c71d21f7..d26868617 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -212,8 +212,8 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_026.820859853884156000 * 1e18, - exchangeRate: 1.013410429926942078 * 1e18 + deposit: 2_010.430334387621616000 * 1e18, + exchangeRate: 1.005215167193810808 * 1e18 }); _assertBorrower({ borrower: _borrower, @@ -237,11 +237,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_026.827291978286188000 * 1e18, - exchangeRate: 1.013413645989143094 * 1e18 + deposit: 2_010.436714496623126000 * 1e18, + exchangeRate: 1.005218357248311563 * 1e18 }); _assertReserveAuction({ - reserves: 24.540805142364596539 * 1e18, + reserves: 24.540805142364598539 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -282,32 +282,32 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { quoteTokenAmount: 14.503461444385064128 * 1e18, bondChange: 0.145034614443850641 * 1e18, isReward: true, - lpAwardTaker: 5.260347799326822092 * 1e18, - lpAwardKicker: 0.143114921550409463 * 1e18 + lpAwardTaker: 5.303234072524906823 * 1e18, + lpAwardKicker: 0.144281700983723506 * 1e18 }); _assertLenderLpBalance({ lender: _taker, index: _i9_91, - lpBalance: 5.260347799326822092 * 1e18, + lpBalance: 5.303234072524906823 * 1e18, depositTime: _startTime + 100 days + 6.5 hours }); _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.143114921550409463 * 1e18, // rewarded with LPs in bucket + lpBalance: 2_000.144281700983723506 * 1e18, // rewarded with LPs in bucket depositTime: _startTime + 100 days + 6.5 hours }); _assertBucket({ index: _i9_91, - lpBalance: 2_005.403462720877231555 * 1e18, + lpBalance: 2_005.447515773508630329 * 1e18, collateral: 2 * 1e18, - deposit: 2_012.468865148344974514 * 1e18, - exchangeRate: 1.013413645989143094 * 1e18 + deposit: 1_996.078287666681912514 * 1e18, + exchangeRate: 1.005218357248311563 * 1e18 }); // reserves should remain the same after arb take _assertReserveAuction({ - reserves: 25.925343323521950877 * 1e18, + reserves: 25.925343323521952869 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -419,11 +419,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 26_531.986011313779866429 * 1e18, collateral: 1.031812215971460994 * 1e18, - deposit: 24_978.836508020647022416 * 1e18, + deposit: 24_978.836508020647022415 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 26.111530884129151302 * 1e18, + reserves: 26.111530884129153303 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -615,7 +615,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i10016, lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LPs awarded for arb taker collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket + deposit: 978.836725452666849367 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 }); _assertAuction( diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index 490eea54c..e962f1dc7 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -212,8 +212,8 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 2_026.346200779800152000 * 1e18, + exchangeRate: 1.013173100389900076 * 1e18 }); _assertBorrower({ borrower: _borrower, @@ -229,7 +229,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { amount: 1 * 1e18, amountAdded: 0.999876712328767123 * 1e18, index: _i9_52, - lpAward: 0.999873468712229082 * 1e18, + lpAward: 0.999873479213875107 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -237,12 +237,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_000.006488054017912000 * 1e18, - exchangeRate: 1.000003244027008956 * 1e18 + deposit: 2_026.352753018872710000 * 1e18, + exchangeRate: 1.013176376509436355 * 1e18 }); _assertReserveAuction({ - reserves: 286.940599154163873221 * 1e18, - claimableReserves : 245.508462704973141079 * 1e18, + reserves: 49.824792135962495179 * 1e18, + claimableReserves : 8.392655686771763037 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -283,7 +283,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { bondChange: 0.198343696868718241 * 1e18, isReward: true, lpAwardTaker: 0, - lpAwardKicker: 0.198343053438495848 * 1e18 + lpAwardKicker: 0.195764233619466887 * 1e18 }); _assertLenderLpBalance({ @@ -295,20 +295,20 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.198343053438495848 * 1e18, + lpBalance: 2_000.195764233619466887 * 1e18, depositTime: _startTime + 250 days + 6.5 hours }); _assertBucket({ index: _i9_91, - lpBalance: 2_000.198343053438495848 * 1e18, + lpBalance: 2_000.195764233619466887 * 1e18, collateral: 2 * 1e18, - deposit: 1_980.370462064014806094 * 1e18, - exchangeRate: 1.000003244027008956 * 1e18 + deposit: 2_006.716727028869604094 * 1e18, + exchangeRate: 1.013176376509436355 * 1e18 }); // reserves should remain the same after deposit take _assertReserveAuction({ - reserves: 288.353881050812150572 * 1e18, - claimableReserves : 247.012858322088192573 * 1e18, + reserves: 51.238074032610772537 * 1e18, + claimableReserves : 9.897051303886814538 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -426,12 +426,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 25_000 * 1e18, collateral: 0.014351542794629452 * 1e18, - deposit: 24_978.397143183672680230 * 1e18, + deposit: 24_978.397143183672680231 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 288.543944498908261871 * 1e18, - claimableReserves : 247.213075232011850973 * 1e18, + reserves: 51.428137480706929986 * 1e18, + claimableReserves : 10.097268213810519088 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 20b104ffc..1c89bf997 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -179,7 +179,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 8.097846143253778448 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 73_093.873009488594546000 * 1e18, + poolSize: 73_093.873009488594544000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 835.035237319063220561 * 1e18, poolDebt: 8_117.624599705640061721 * 1e18, @@ -232,7 +232,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { locked: 0.195342779771472726 * 1e18 }); _assertReserveAuction({ - reserves: 24.501590217045515721 * 1e18, + reserves: 24.501590217045517721 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -719,7 +719,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 9.721295865031779605 * 1e18, - poolSize: 73_114.174951097528960000 * 1e18, + poolSize: 73_114.174951097528944000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.290450922889736704 * 1e18, poolDebt: 9_996.315708608352095626 * 1e18, @@ -740,7 +740,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { from: _lender1, amount: 1 * 1e18, index: _i9_91, - lpAward: 0.945987267750984917 * 1e18, + lpAward: 0.993688275531219296 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -748,7 +748,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 9.721295865031779605 * 1e18, - poolSize: 73_115.811578712752113362 * 1e18, + poolSize: 73_115.811578712752097363 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.364405977643667984 * 1e18, poolDebt: 9_997.034647576329686631 * 1e18, diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index 562b44c81..459e0f54c 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -198,22 +198,22 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.174951097528960000 * 1e18, - exchangeRate: 1.057087475548764480 * 1e18 + deposit: 2_012.686105677503216000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, collateral: 0, - deposit: 5_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 5_031.715264193758040000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 11_069.773581226267688000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_62, @@ -288,22 +288,22 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998850000 * 1e18, - exchangeRate: 1.057155041798499425 * 1e18 + deposit: 2_012.736630048845584000 * 1e18, + exchangeRate: 1.006368315024422792 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, collateral: 0, - deposit: 5_000.319586842611440000 * 1e18, - exchangeRate: 1.000063917368522288 * 1e18 + deposit: 5_031.841575122113960000 * 1e18, + exchangeRate: 1.006368315024422792 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 11_070.051465268650712000 * 1e18, + exchangeRate: 1.006368315024422792 * 1e18 }); _assertBucket({ index: _i9_62, @@ -327,8 +327,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998850000 * 1e18, - exchangeRate: 1.057155041798499425 * 1e18 + deposit: 2_012.736630048845584000 * 1e18, + exchangeRate: 1.006368315024422792 * 1e18 }); _settle({ @@ -379,7 +379,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_807.556879218687261409 * 1e18, + deposit: 8_807.556879218687263264 * 1e18, exchangeRate: 0.800686989019880660 * 1e18 }); _assertBucket({ @@ -393,7 +393,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { PoolParams({ htp: 9.771304290202671377 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_807.556879218687261409 * 1e18, + poolSize: 63_807.556879218687263264 * 1e18, pledgedCollateral: 2 * 1e18, encumberedCollateral: 2.010288427770370775 * 1e18, poolDebt: 19.542608580405342754 * 1e18, @@ -480,22 +480,22 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.174951097528960000 * 1e18, - exchangeRate: 1.057087475548764480 * 1e18 + deposit: 2_012.686105677503216000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, collateral: 0, - deposit: 5_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 5_031.715264193758040000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 11_000 * 1e18, - exchangeRate: 1 * 1e18 + deposit: 11_069.773581226267688000 * 1e18, + exchangeRate: 1.006343052838751608 * 1e18 }); _assertBucket({ index: _i9_62, @@ -566,23 +566,23 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { _assertBucket({ index: _i9_91, lpBalance: 2_000 * 1e18, - collateral: 213.282459829062995170 * 1e18, + collateral: 202.986535499495858324 * 1e18, deposit: 0, - exchangeRate: 1.057580788993756153 * 1e18 + exchangeRate: 1.006527496638583021 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, - collateral: 509.467337074016312678 * 1e18, + collateral: 512.553688794695752468 * 1e18, deposit: 0, - exchangeRate: 1.000466672301396419 * 1e18 + exchangeRate: 1.006527496638583021 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, - collateral: 277.250203096920692152 * 1e18, - deposit: 8_290.291604705064327151 * 1e18, - exchangeRate: 0.998683896150034595 * 1e18 + collateral: 284.459775705808389208 * 1e18, + deposit: 8_290.291604705064327150 * 1e18, + exchangeRate: 1.005055386003800627 * 1e18 }); _assertBucket({ index: _i9_62, @@ -719,14 +719,14 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { from: _lender1, amount: 100 * 1e18, index: _i9_91, - lpAward: 94.593504307441637789 * 1e18, + lpAward: 99.367198377636894880 * 1e18, newLup: 9.721295865031779605 * 1e18 }); _assertLenderLpBalance({ lender: _lender1, index: _i9_91, - lpBalance: 94.593504307441637789 * 1e18, + lpBalance: 99.367198377636894880 * 1e18, depositTime: _startTime + 100 days + 10 hours }); @@ -867,8 +867,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0 * 1e18, - deposit: 9_036.877948541081298779 * 1e18, - exchangeRate: 0.821534358958280118 * 1e18 + deposit: 9_036.878045597692505047 * 1e18, + exchangeRate: 0.821534367781608410 * 1e18 }); _pool.moveQuoteToken(10000000000 * 1e18, _i9_72, _i9_91, type(uint256).max); @@ -1007,8 +1007,8 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).updateInterest(); _startClaimableReserveAuction({ from: actor1, - remainingReserves: 1_962_000_500.669895903463292555 * 1e18, - price: 1000000000 * 1e18, + remainingReserves: 642_374_224.754246627157382301 * 1e18, + price: 1_000_000_000 * 1e18, epoch: 1 }); @@ -1034,7 +1034,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { assertEq(borrowerDebt, 60_144_029_463.415046012797744619 * 1e18); (uint256 reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); - assertEq(reserves, 1_758_290_868.502349679615580158 * 1e18); + assertEq(reserves, 513_130_796.902799297066317156 * 1e18); // settle auction with reserves _settle({ @@ -1046,6 +1046,6 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { (reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); - assertEq(reserves, 2); + assertEq(reserves, 3); } } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 956f1fc75..96d9712cb 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -297,8 +297,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188305467 * 1e18, - claimableReserves : 83.959896655448330900 * 1e18, + reserves: 179.552281242188325467 * 1e18, + claimableReserves : 83.959896655448350900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -310,7 +310,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806608000 * 1e18, + poolSize: 83_219.674636105806588000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.791200431324241706 * 1e18, poolDebt: 19_119.759164133922414841 * 1e18, @@ -388,7 +388,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281653638 * 1e18, + poolSize: 83_220.780619576281629747 * 1e18, pledgedCollateral: 1_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, @@ -438,7 +438,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281653638 * 1e18, + poolSize: 83_220.780619576281629747 * 1e18, pledgedCollateral: 2_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, @@ -574,8 +574,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188305467 * 1e18, - claimableReserves : 83.959896655448330900 * 1e18, + reserves: 179.552281242188325467 * 1e18, + claimableReserves : 83.959896655448350900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -587,7 +587,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806608000 * 1e18, + poolSize: 83_219.674636105806588000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -782,8 +782,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188305467 * 1e18, - claimableReserves : 83.959896655448330900 * 1e18, + reserves: 179.552281242188325467 * 1e18, + claimableReserves : 83.959896655448350900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -795,7 +795,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806608000 * 1e18, + poolSize: 83_219.674636105806588000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -944,8 +944,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 152.199485178078895491 * 1e18, - claimableReserves : 102.373123280655388094 * 1e18, + reserves: 152.199485178078897491 * 1e18, + claimableReserves : 102.373123280655390094 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -957,7 +957,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.767138988573636287 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 73_113.822894306622584000 * 1e18, + poolSize: 73_113.822894306622582000 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_025.107650389722106875 * 1e18, poolDebt: 9_965.374762946048672276 * 1e18, @@ -1009,7 +1009,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.767239336407496599 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 73_113.913540853182354328 * 1e18, + poolSize: 73_113.913540853182360000 * 1e18, pledgedCollateral: 992.0 * 1e18, encumberedCollateral: 925.265940856763249327 * 1e18, poolDebt: 8_994.783964905591719091 * 1e18, @@ -1170,8 +1170,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.977433325291186371 * 1e18 }); _assertReserveAuction({ - reserves: 179.552281242188305467 * 1e18, - claimableReserves : 83.959896655448330900 * 1e18, + reserves: 179.552281242188325467 * 1e18, + claimableReserves : 83.959896655448350900 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1183,7 +1183,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.154429955928583539 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 83_219.674636105806608000 * 1e18, + poolSize: 83_219.674636105806588000 * 1e18, pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, @@ -1316,8 +1316,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrowerCollateralization: 0.974413448899967463 * 1e18 }); _assertReserveAuction({ - reserves: 152.670996883580228810 * 1e18, - claimableReserves : 102.690517143674682866 * 1e18, + reserves: 152.670996883580244810 * 1e18, + claimableReserves : 102.690517143674698866 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1461,8 +1461,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581150187 * 1e18, - claimableReserves : 797.714616029939978221 * 1e18, + reserves: 851.124981254581185156 * 1e18, + claimableReserves : 797.714616029940013190 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1509,8 +1509,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581149834 * 1e18, - claimableReserves : 800.882859687599454633 * 1e18, + reserves: 851.124981254581184803 * 1e18, + claimableReserves : 800.882859687599489602 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1530,8 +1530,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: 3_696, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_114.310083596998850000 * 1e18, - exchangeRate: 1.057155041798499425 * 1e18 + deposit: 2_012.736630048845584000 * 1e18, + exchangeRate: 1.006368315024422792 * 1e18 }); _settle({ @@ -1599,7 +1599,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_936.865619773958011125 * 1e18, + deposit: 8_936.865619773958012093 * 1e18, exchangeRate: 0.812442329070359819 * 1e18 }); _assertLenderLpBalance({ @@ -1638,8 +1638,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { vm.revertTo(postTakeSnapshot); _assertReserveAuction({ - reserves: 851.124981254581149834 * 1e18, - claimableReserves : 800.882859687599454633 * 1e18, + reserves: 851.124981254581184803 * 1e18, + claimableReserves : 800.882859687599489602 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1649,7 +1649,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 0, - settledDebt: 839.502103169454551025 * 1e18 + settledDebt: 839.502103169454585516 * 1e18 }); _assertReserveAuction({ reserves: 0, @@ -1664,7 +1664,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 1, - settledDebt: 2_085.437275399572352030 * 1e18 + settledDebt: 1_985.250898829861493554 * 1e18 }); _assertAuction( @@ -1678,14 +1678,14 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 105.065056948053351817 * 1e18, auctionPrice: 0.653111452826113536 * 1e18, - debtInAuction: 7_063.453967068653428874 * 1e18, + debtInAuction: 7_165.027420616806659906 * 1e18, thresholdPrice: 0, neutralPrice: 10.449783245217816340 * 1e18 }) ); _assertBorrower({ borrower: _borrower2, - borrowerDebt: 7_063.453967068653428874 * 1e18, + borrowerDebt: 7_165.027420616806659906 * 1e18, borrowerCollateral: 0, borrowert0Np: 10.307611531622595991 * 1e18, borrowerCollateralization: 0 @@ -1701,7 +1701,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 5, - settledDebt: 6_966.996142275250443867 * 1e18 + settledDebt: 7_067.182518844961267852 * 1e18 }); _assertAuction( @@ -1798,8 +1798,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { locked: 98.533942419792216457 * 1e18 }); _assertReserveAuction({ - reserves: 152.670996883580228810 * 1e18, - claimableReserves : 102.690517143674682866 * 1e18, + reserves: 152.670996883580244810 * 1e18, + claimableReserves : 102.690517143674698866 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1944,8 +1944,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_001 * 1e18, collateral: 0, - deposit: 2_115.174951097528960617 * 1e18, - exchangeRate: 1.057058946075726617 * 1e18 + deposit: 2_013.691743633473441469 * 1e18, + exchangeRate: 1.006342700466503469 * 1e18 }); _take({ diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index d6f9245f4..7582ac953 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -282,15 +282,20 @@ abstract contract BaseHandler is Test { // get HTP and deposit above HTP uint256 htp = Maths.wmul(maxThresholdPrice, pendingInflator); - uint256 htpIndex; + uint256 accrualIndex; - if (htp > MAX_PRICE) htpIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest - else if (htp < MIN_PRICE) htpIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest - else htpIndex = _poolInfo.priceToIndex(htp); + if (htp > MAX_PRICE) accrualIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest + else if (htp < MIN_PRICE) accrualIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest + else accrualIndex = _poolInfo.priceToIndex(htp); + + uint256 lupIndex = _pool.depositIndex(poolDebt); + + // accrual price is less of lup and htp, and prices decrease as index increases + if (lupIndex > accrualIndex) accrualIndex = lupIndex; - uint256 depositAboveHtp = fenwickSumTillIndex(htpIndex); + uint256 interestEarningDeposit = fenwickSumTillIndex(accrualIndex); - if (depositAboveHtp != 0) { + if (interestEarningDeposit != 0) { uint256 utilization = _pool.depositUtilization(); uint256 lenderInterestMargin = PoolCommons.lenderInterestMargin(utilization); @@ -299,10 +304,10 @@ abstract contract BaseHandler is Test { Maths.wmul(pendingFactor - Maths.WAD, poolDebt) ); - uint256 scale = (newInterest * 1e18) / depositAboveHtp + Maths.WAD; + uint256 scale = (newInterest * 1e18) / interestEarningDeposit + Maths.WAD; // simulate scale being applied to all deposits above HTP - _fenwickMult(htpIndex, scale); + _fenwickMult(accrualIndex, scale); } } diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol index 0f1ce00eb..f5610b9c6 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol @@ -117,8 +117,7 @@ contract RegressionTestReserve is ReserveInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } - // FIXME: Seems to be issue with rounding to nearest in Desposits.unscaledAdd() in addQuoteToken - function _test_regression_reserve_16() external { + function test_regression_reserve_16() external { _reservePoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); _reservePoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); _reservePoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231); @@ -394,4 +393,17 @@ contract RegressionTestReserve is ReserveInvariants { invariant_fenwick_depositsTillIndex_F2(); } + function test_remove_regression_R1() external { + _reservePoolHandler.takeAuction(1000000000147122258, 3919731510820678131056801, 158441107709132461742605107); + _reservePoolHandler.repayDebt(15097247704276523502490912, 5821681489746654725611665637); + _reservePoolHandler.addQuoteToken(409278183265946161107935122, 13459778251101474251175765782, 17131651646875762675637482511491680925564181440856864512); + _reservePoolHandler.kickWithDeposit(3000000000000000000003060052276861736589117902, 10971651541557993591476169); + _reservePoolHandler.drawDebt(99176811231448450752542388131222351, 4756085816094695387473840); + _reservePoolHandler.transferLps(345464481275697722, 1, 1571, 636770839146216364947817981246144824780203402016795537219680499840300283500); + _reservePoolHandler.takeReserves(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reservePoolHandler.removeQuoteToken(2921676640197348125883567882, 110429299813004951706741973, 5838113258459267571531065497); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + } diff --git a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol index 70f350312..50fe86a7b 100644 --- a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol @@ -615,7 +615,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); } - function testMergeOrRemoveCollateral() external tearDown { + function testMergeOrRemoveERC721Collateral() external tearDown { for (uint256 i = 3060; i < (3060 + 10); i++) { _addLiquidity({ from: _lender, @@ -657,7 +657,32 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); // skip to render borrower undercollateralized - skip(10000 days); + skip(10_000 days); + _assertPool( + PoolParams({ + htp: 75.072115384615384650 * 1e18, + lup: 0.000000099836282890 * 1e18, + poolSize: 200.0 * 1e18, + pledgedCollateral: 2 * 1e18, + encumberedCollateral: 5_917_580_766.197687035361137933 * 1e18, + poolDebt: 590.789267398535232526 * 1e18, + actualUtilization: 0, + targetUtilization: 1.0 * 1e18, + minDebtAmount: 59.078926739853523253 * 1e18, + loans: 1, + maxBorrower: address(_borrower), + interestRate: 0.05 * 1e18, + interestRateUpdate: block.timestamp - 10_000 days + }) + ); + + _assertBucket({ + index: 3061, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 20.0 * 1e18, + exchangeRate: 1.000000000000000000 * 1e18 + }); _kick({ from: _lender, @@ -691,7 +716,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { PoolParams({ htp: 0, lup: 99836282890, - poolSize: 200 * 1e18, + poolSize: 574.548281134908793280 * 1e18, pledgedCollateral: 2 * 1e18, encumberedCollateral: 5_992_754_428.551908353085520210 * 1e18, poolDebt: 598.294326419208615388 * 1e18, @@ -711,8 +736,88 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { index: 3060, lpBalance: 20 * 1e18, collateral: 0.0000000000000000000 * 1e18, - deposit: 20.012019317819169240 * 1e18, - exchangeRate: 1.000600965890958462 * 1e18 + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3061, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3062, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3063, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3064, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3065, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3066, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3067, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 66.832513309346669380 * 1e18, + exchangeRate: 3.341625665467333469 * 1e18 + }); + + _assertBucket({ + index: 3068, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 20.004183919163565300 * 1e18, + exchangeRate: 1.000209195958178265 * 1e18 + }); + + _assertBucket({ + index: 3069, + lpBalance: 20.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 20.004183919163565300 * 1e18, + exchangeRate: 1.000209195958178265 * 1e18 + }); + + _assertBucket({ + index: 3070, + lpBalance: 0.0 * 1e18, + collateral: 0.0 * 1e18, + deposit: 0.0, + exchangeRate: 1.0 * 1e18 }); // Before depositTake: NFTs pledged by liquidated borrower are owned by the borrower in the pool @@ -720,7 +825,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.ownerOf(3), address(_pool)); // exchange collateral for lpb 3060 - 3070, going down in price - for (uint256 i = _i236_59; i < (3060 + 10); i++) { + for (uint256 i = _i236_59; i < (3060 + 3); i++) { _depositTake({ from: _lender, borrower: _borrower, @@ -731,17 +836,17 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBucket({ index: 3060, lpBalance: 20.202020202020202020 * 1e18, - collateral: 0.085438188901566024 * 1e18, + collateral: 0.285330970663516468 * 1e18, deposit: 0, - exchangeRate: 1.000600965890958458 * 1e18 + exchangeRate: 3.341625665467333471 * 1e18 }); _assertBucket({ index: 3061, - lpBalance: 20.202020202020202021 * 1e18, - collateral: 0.085865379846073854 * 1e18, + lpBalance: 20.202020202020202020 * 1e18, + collateral: 0.286757625516834048 * 1e18, deposit: 0, - exchangeRate: 1.000600965890958465 * 1e18 + exchangeRate: 3.341625665467333471 * 1e18 }); _assertBucket({ @@ -763,8 +868,8 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { kickMomp: 0.000000099836282890 * 1e18, totalBondEscrowed: 5.907892673985352325 * 1e18, auctionPrice: 0.000004621809202112 * 1e18, - debtInAuction: 440.054736090361526057 * 1e18, - thresholdPrice: 390.765197584557938167 * 1e18, + debtInAuction: 439.677389340513210329 * 1e18, + thresholdPrice: 385.776675964868418456 * 1e18, neutralPrice: 310.164365384230997074 * 1e18 }) ); @@ -773,10 +878,10 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { PoolParams({ htp: 0, lup: 99836282890, - poolSize: 0, - pledgedCollateral: 1.126135947649580007 * 1e18, - encumberedCollateral: 4407763624.124663422322253088 * 1e18, - poolDebt: 440.054736090361526057 * 1e18, + poolSize: 374.170934385060477538 * 1e18, + pledgedCollateral: 1.139719990175231268 * 1e18, + encumberedCollateral: 4403983968.683524073950025244 * 1e18, + poolDebt: 439.677389340513210329 * 1e18, actualUtilization: 18.990844967339121167 * 1e18, targetUtilization: 2_995_775_262.887499112057200872 * 1e18, minDebtAmount: 0, @@ -787,16 +892,16 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }) ); - // Borrower's collateral is < 2 therefore they have possession of token ID 1 + // Borrower's collateral is 0 therefore they have bad debt borrowerTokenIds = new uint256[](1); borrowerTokenIds[0] = 1; _assertBorrower({ borrower: _borrower, - borrowerDebt: 440.054736090361526057 * 1e18, - borrowerCollateral: 1.126135947649580007 * 1e18, + borrowerDebt: 439.677389340513210329 * 1e18, + borrowerCollateral: 1.139719990175231268 * 1e18, borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000255489188 * 1e18, + borrowerCollateralization: 0.000000000258792947 * 1e18, tokenIds: borrowerTokenIds }); @@ -819,10 +924,10 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 440.054731514770415967 * 1e18, - borrowerCollateral: 0.126135947649580007 * 1e18, + borrowerDebt: 439.677384764922100239 * 1e18, + borrowerCollateral: 0.139719990175231268 * 1e18, borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000028616768 * 1e18, + borrowerCollateralization: 0.000000000031725817 * 1e18, tokenIds: borrowerTokenIds }); @@ -851,8 +956,8 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { kickMomp: 0.000000099836282890 * 1e18, totalBondEscrowed: 5.907892720203444346 * 1e18, auctionPrice: 0 * 1e18, - debtInAuction: 440.054731514770415967 * 1e18, - thresholdPrice: 3_490.424746346802636000 * 1e18, + debtInAuction: 439.677384764922100239 * 1e18, + thresholdPrice: 3_148.371989379999109069 * 1e18, neutralPrice: 310.164365384230997074 * 1e18 }) ); @@ -861,7 +966,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { from: _lender, borrower: _borrower, maxDepth: 10, - settledDebt: 111.813821718021490518 * 1e18 + settledDebt: 111.717941412250938348 * 1e18 }); _assertBorrower({ @@ -897,35 +1002,15 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBucket({ index: 3060, lpBalance: 20.202020202020202020 * 1e18, - collateral: 0.085438188901566024 * 1e18, - deposit: 0, - exchangeRate: 1.000600965890958458 * 1e18 - }); - _assertBucket({ - index: 3069, - lpBalance: 20.202020202020202021 * 1e18, - collateral: 0.089360705635142406 * 1e18, + collateral: 0.285330970663516468 * 1e18, deposit: 0, - exchangeRate: 1.000600965890958463 * 1e18 - }); - _assertLenderLpBalance({ - lender: _lender, - index: 3069, - lpBalance: 20.202020202020202021 * 1e18, - depositTime: _startTime + 10000 days + 32 hours - }); - _assertBucket({ - index: 7388, - lpBalance: 0.000000012592944152 * 1e18, // LPs awarded to borrower for settled collateral - collateral: 0.126135947649580007 * 1e18, // settled collateral amount - deposit: 0, - exchangeRate: 1 * 1e18 + exchangeRate: 3.341625665467333471 * 1e18 }); _assertLenderLpBalance({ lender: _borrower, - index: 7388, - lpBalance: 0.000000012592944152 * 1e18, - depositTime: _startTime + 10000 days + 32 hours + 4210 minutes + index: 3067, + lpBalance: 0, + depositTime: 0 }); assertEq(_collateral.balanceOf(_lender), 1); @@ -952,9 +1037,9 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { from: _lender, toIndex: 3070, noOfNFTsToRemove: 1.0, - collateralMerged: 0.873864052350419993 * 1e18, + collateralMerged: 1 * 1e18, removeCollateralAtIndex: removalIndexes, - toIndexLps: 196.692111240799095591 * 1e18 + toIndexLps: 0 }); _assertBucket({ @@ -973,28 +1058,28 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); _assertBucket({ index: 3070, - lpBalance: 196.692111240799095591 * 1e18, // new LPs amount accounting collateral merged in bucket - collateral: 0.873864052350419993 * 1e18, // reflects collateral merged in the bucket + lpBalance: 0, + collateral: 0, deposit: 0, exchangeRate: 1 * 1e18 }); _assertLenderLpBalance({ lender: _lender, index: 3070, - lpBalance: 196.692111240799095591 * 1e18, - depositTime: _startTime + 10000 days + 32 hours + 4210 minutes + lpBalance: 0, + depositTime: 0 }); _assertBucket({ index: 7388, - lpBalance: 0.000000012592944152 * 1e18, // LPs awarded to borrower for settled collateral - collateral: 0.126135947649580007 * 1e18, // settled collateral amount + lpBalance: 0, + collateral: 0, deposit: 0, exchangeRate: 1 * 1e18 }); - assertEq(_collateral.balanceOf(_lender), 1); + assertEq(_collateral.balanceOf(_lender), 2); assertEq(_collateral.balanceOf(_borrower), 50); - assertEq(_collateral.balanceOf(address(_pool)), 1); + assertEq(_collateral.balanceOf(address(_pool)), 0); // lender deposit quote tokens in bucket 7388 in order to claim and merge settled collateral and to be able to remove entire NFT _addLiquidity({ @@ -1017,15 +1102,6 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { allRemovalIndexes[0] = 3070; allRemovalIndexes[1] = 7388; - _mergeOrRemoveCollateral({ - from: _lender, - toIndex: 7388, - noOfNFTsToRemove: 1, - collateralMerged: 1 * 1e18, - removeCollateralAtIndex: allRemovalIndexes, - toIndexLps: 0 - }); - _assertBucket({ index: 3060, lpBalance: 0, @@ -1057,42 +1133,42 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertLenderLpBalance({ lender: _lender, index: 7388, - lpBalance: 9.999999987407055848 * 1e18, // lender LPs decreased with the amount used to merge NFT + lpBalance: 10 * 1e18, // lender LPs decreased with the amount used to merge NFT depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) }); _assertLenderLpBalance({ lender: _borrower, index: 7388, - lpBalance: 0.000000012592944152 * 1e18, // Borrower LPs remain the same in the bucket - depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) + lpBalance: 0, // Borrower LPs remain the same in the bucket + depositTime: 0 }); _removeAllLiquidity({ from: _lender, - amount: 9.999999987407055848 * 1e18, + amount: 10 * 1e18, index: 7388, newLup: MAX_PRICE, - lpRedeem: 9.999999987407055848 * 1e18 + lpRedeem: 10 * 1e18 }); _assertBucket({ index: 7388, - lpBalance: 0.000000012592944152 * 1e18, // LPs in bucket 7388 diminished when NFT merged and removed - collateral: 0, // no collateral remaining as it was merged and removed - deposit: 0.000000012592944153 * 1e18, - exchangeRate: 1.000000000079409548 * 1e18 + lpBalance: 0, // LPs in bucket 7388 diminished when NFT merged and removed + collateral: 0, // no collateral remaining as it was merged and removed + deposit: 0, + exchangeRate: 1 * 1e18 }); _assertPool( PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 0.000000012592944153 * 1e18, + poolSize: 50.000004575591110080 * 1e18, pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 809.309914318771937839 * 1e18, - targetUtilization: 14_683_125_178.238430800914320081 * 1e18, + actualUtilization: 808.632216044431490433 * 1e18, + targetUtilization: 13_424_722_854.598244140780064966 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol index 744df44af..1aa721c32 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol @@ -159,7 +159,7 @@ contract ERC721PoolLiquidationsDepositTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000 * 1e18, + poolSize: 73_004.346887619919714000 * 1e18, pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol index 540385b18..299e19453 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol @@ -179,7 +179,7 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { PoolParams({ htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000 * 1e18, + poolSize: 73_004.346887619919714000 * 1e18, pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index d8a49491c..b3be0c679 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -185,7 +185,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000 * 1e18, + poolSize: 73_004.346887619919714000 * 1e18, pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, @@ -292,11 +292,11 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 7.749209044755361552 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000.000966222327535000 * 1e18, + poolSize: 73_004.347853842247224958 * 1e18, pledgedCollateral: 4 * 1e18, encumberedCollateral: 2.517692578855560848 * 1e18, poolDebt: 24.968422683457442924 * 1e18, - actualUtilization: 0.000551117205089510 * 1e18, + actualUtilization: 0.000551108273306260 * 1e18, targetUtilization: 0.911373730814237973 * 1e18, minDebtAmount: 1.248421134172872146 * 1e18, loans: 2, @@ -364,11 +364,11 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739737879567360457 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000.000966222327535000 * 1e18, + poolSize: 73_004.347853842247224958 * 1e18, pledgedCollateral: 3.0 * 1e18, encumberedCollateral: 1.736300564176668638 * 1e18, poolDebt: 17.219213638702081372 * 1e18, - actualUtilization: 0.000551117205089510 * 1e18, + actualUtilization: 0.000551108273306260 * 1e18, targetUtilization: 0.911373730814237973 * 1e18, minDebtAmount: 1.721921363870208137 * 1e18, loans: 1, @@ -463,7 +463,7 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739575714606494647 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000 * 1e18, + poolSize: 73_004.346887619919714000 * 1e18, pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, @@ -573,11 +573,11 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739870563397816903 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_000.001756788173660000 * 1e18, + poolSize: 73_004.348644408093384607 * 1e18, pledgedCollateral: 3 * 1e18, encumberedCollateral: 4.070504644882883983 * 1e18, poolDebt: 40.367946969368016673 * 1e18, - actualUtilization: 0.000551117205089510 * 1e18, + actualUtilization: 0.000551102806362854 * 1e18, targetUtilization: 0.911373730814237973 * 1e18, minDebtAmount: 4.036794696936801667 * 1e18, loans: 1, diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index 82ff3b40b..cefd36002 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -782,7 +782,7 @@ contract RewardsManagerTest is ERC20HelperContract { index: _i9_81, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 4_936.865619773958011818 * 1e18, + deposit: 4_936.865619773958005817 * 1e18, exchangeRate: 0.493686561977395801 * 1e18 }); @@ -791,7 +791,7 @@ contract RewardsManagerTest is ERC20HelperContract { /***********************/ // skip some time to accumulate reserves - skip(50 days); + skip(1000 days); // update pool reserves _pool.updateInterest(); @@ -1013,12 +1013,12 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater); assertEq(_ajnaToken.balanceOf(_updater), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 20.449844540665683990 * 1e18); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 20.449844540665688882 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 204.498445406656758711 * 1e18); + assertEq(rewardsEarned, 204.498445406656758712 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /******************************/ @@ -1035,15 +1035,15 @@ contract RewardsManagerTest is ERC20HelperContract { // call update exchange rate to enable claiming rewards changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665683990 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314416 * 1e18); + emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314418 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876737998406 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 37.688096876738003300 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + assertEq(rewardsEarned, 376.880968767380567490 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /*****************************/ @@ -1060,7 +1060,7 @@ contract RewardsManagerTest is ERC20HelperContract { // skip updating exchange rates and check available rewards uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380561039 * 1e18); + assertEq(rewardsEarnedNoUpdate, 376.880968767380567490 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); // snapshot calling update exchange rate @@ -1070,13 +1070,13 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater2); assertEq(_ajnaToken.balanceOf(_updater2), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606335 * 1e18); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606338 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606335 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606338 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 517.072612267116262774 * 1e18); + assertEq(rewardsEarned, 517.072612267116269224 * 1e18); assertGt(rewardsEarned, rewardsEarnedNoUpdate); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -1097,7 +1097,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + assertEq(rewardsEarned, 376.880968767380567490 * 1e18); // call update exchange rate changePrank(_updater2); @@ -1109,7 +1109,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned won't increase since previous update was missed rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380561039 * 1e18); + assertEq(rewardsEarned, 376.880968767380567490 * 1e18); /*****************************/ /*** Fifth Reserve Auction ***/ @@ -1127,12 +1127,12 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_updater2); assertEq(_ajnaToken.balanceOf(_updater2), 0); vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905357 * 1e18); + emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905358 * 1e18); _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905357 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905358 * 1e18); rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049732206 * 1e18); + assertEq(rewardsEarned, 493.039460320049744944 * 1e18); // claim all rewards accrued since deposit changePrank(_minterOne); From 399e463c3d0a4729b06ff337fcb1fde930486e46 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Fri, 31 Mar 2023 09:52:13 -0400 Subject: [PATCH 35/70] Use unscaled remaining amount when remove deposit (#714) * add invariant * added fixme * Fix rounding issue, if the unscaled amount to remove is the entire unscaled deposit then set scaled deposit remaining to 0 * removed logs * Better way to fix this issue, calculate unscaled remaining amount --------- Co-authored-by: Ian Harvey Co-authored-by: grandizzy --- src/libraries/external/LenderActions.sol | 18 +++++++++--------- .../regression/RegressionTestLiquidation.t.sol | 11 +++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index 402c6cb64..d214ef551 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -388,8 +388,8 @@ library LenderActions { removeParams.index = params_.index; removeParams.dustLimit = poolState_.quoteDustLimit; - uint256 scaledRemaining; - (removedAmount_, redeemedLPs_, scaledRemaining) = _removeMaxDeposit( + uint256 unscaledRemaining; + (removedAmount_, redeemedLPs_, unscaledRemaining) = _removeMaxDeposit( deposits_, removeParams ); @@ -403,7 +403,7 @@ library LenderActions { uint256 lpsRemaining = removeParams.bucketLPs - redeemedLPs_; - if (removeParams.bucketCollateral == 0 && scaledRemaining == 0 && lpsRemaining != 0) { + if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpsRemaining != 0) { emit BucketBankruptcy(params_.index, lpsRemaining); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; @@ -887,14 +887,14 @@ library LenderActions { * @dev write state: * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): * - update values array state - * @return removedAmount_ Amount of scaled deposit removed. - * @return redeemedLPs_ Amount of bucket LPs corresponding for calculated scaled deposit amount. - * @return scaledRemaining_ Amount of scaled deposit remaining. + * @return removedAmount_ Amount of scaled deposit removed. + * @return redeemedLPs_ Amount of bucket LPs corresponding for calculated scaled deposit amount. + * @return unscaledRemaining_ Amount of unscaled deposit remaining. */ function _removeMaxDeposit( DepositsState storage deposits_, RemoveDepositParams memory params_ - ) internal returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 scaledRemaining_) { + ) internal returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 unscaledRemaining_) { uint256 unscaledDepositAvailable = Deposits.unscaledValueAt(deposits_, params_.index); if (unscaledDepositAvailable == 0) revert InsufficientLiquidity(); // revert if there's no liquidity available to remove @@ -939,9 +939,9 @@ library LenderActions { removedAmount_ = scaledDepositAvailable; } - scaledRemaining_ = scaledDepositAvailable - removedAmount_; - uint256 unscaledRemovedAmount = Maths.min(unscaledDepositAvailable, Maths.wdiv(removedAmount_, depositScale)); + unscaledRemaining_ = unscaledDepositAvailable - unscaledRemovedAmount; + Deposits.unscaledRemove(deposits_, params_.index, unscaledRemovedAmount); // update FenwickTree } diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol index 3d70b75fb..061a89624 100644 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol @@ -271,4 +271,15 @@ contract RegressionTestLiquidation is LiquidationInvariants { invariant_fenwick_prefixSumIndex_F4(); } + function test_regression_invariant_bucketlps_B2_B3() external { + + _liquidationPoolHandler.takeAuction(267050932349, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3887647445238399127687813856507958874); + _liquidationPoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 103646259621272362812910538669334394369354710213939195837836110291707517186914, 22729901925249217583); + _liquidationPoolHandler.takeAuction(100662313874952447676789537887446294, 36755077739534085766246321257993, 20000000001077187985112900413); + _liquidationPoolHandler.settleAuction(999999999999999970610520171679024221920138860, 4339, 19021013243589608614756959415948670046791); + _liquidationPoolHandler.removeQuoteToken(7393406237507791712904627, 1097992169037390343, 30); + + invariant_Buckets_B2_B3(); + } + } \ No newline at end of file From cb2d9342973ba9e919dd5c76729708c74f085512 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Sat, 1 Apr 2023 14:35:53 -0400 Subject: [PATCH 36/70] cleaned up t0Debt2ToCollateral invariant check I4 (#722) # Description of change ## High level * Added new invariant to ensure t0Debt2ToCollateral accumulator contains the correct value every time a borrower is touched. This invariant check loops over all actors, checks their debt and collateral then adds to a sum, once all borrowers are looped over this sum is checked against the pool's t0Debt2ToCollateral value. * pool.debtInfo() now returns t0Debt2ToCollateral (adjusted all returns) * added new invariant to BasicInvariants.t.sol - - **I4**: for all borrowers where (borrower.collateral != 0) the sum of borrower debt squared divided by borrower collateral (borrower.debt^2 / borrower.collateral) should equal borrower collateralization accumulator (t0Debt2ToCollateral) --------- Co-authored-by: Ian Harvey --- src/PoolInfoUtils.sol | 14 ++++---- src/base/Pool.sol | 6 ++-- src/interfaces/pool/commons/IPoolState.sol | 5 +-- tests/INVARIANTS.md | 1 + tests/forge/ERC20Pool/ERC20DSTestPlus.sol | 2 +- .../forge/ERC20Pool/ERC20PoolFlashloan.t.sol | 2 +- .../ERC20PoolInterestRateAndEMAs.t.sol | 4 +-- .../forge/ERC20Pool/ERC20PoolPrecision.t.sol | 4 +-- .../forge/ERC20Pool/ERC20PoolQuoteToken.t.sol | 2 +- .../invariants/BasicInvariants.t.sol | 23 ++++++++++++- .../ERC20Pool/invariants/base/BaseHandler.sol | 2 +- .../base/UnboundedBasicPoolHandler.sol | 4 +-- tests/forge/ERC721Pool/ERC721DSTestPlus.sol | 2 +- .../ERC721Pool/ERC721PoolCollateral.t.sol | 4 +-- .../ERC721Pool/ERC721PoolFlashloan.t.sol | 2 +- .../forge/ERC721Pool/ERC721PoolInterest.t.sol | 34 +++++++++---------- .../ERC721Pool/ERC721PoolReserveAuction.t.sol | 6 ++-- tests/forge/utils/DSTestPlus.sol | 4 +-- 18 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index 01e1134c4..e9a0709f6 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -145,7 +145,7 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (uint256 debt,,) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); hpbIndex_ = pool.depositIndex(1); hpb_ = _priceAt(hpbIndex_); @@ -179,8 +179,8 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (,uint256 poolDebt,) = pool.debtInfo(); - uint256 poolSize = pool.depositSize(); + (,uint256 poolDebt,,) = pool.debtInfo(); + uint256 poolSize = pool.depositSize(); uint256 quoteTokenBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); @@ -223,7 +223,7 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (uint256 poolDebt,,) = pool.debtInfo(); + (uint256 poolDebt,,,) = pool.debtInfo(); uint256 poolCollateral = pool.pledgedCollateral(); (, , uint256 noOfLoans) = pool.loansInfo(); @@ -273,7 +273,7 @@ contract PoolInfoUtils { ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt,,) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); uint256 currentLupIndex = pool.depositIndex(debt); return _priceAt(currentLupIndex); @@ -284,7 +284,7 @@ contract PoolInfoUtils { ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt,,) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); return pool.depositIndex(debt); } @@ -318,7 +318,7 @@ contract PoolInfoUtils { ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt, , ) = pool.debtInfo(); + (uint256 debt, , ,) = pool.debtInfo(); ( , , uint256 noOfLoans) = pool.loansInfo(); noOfLoans += pool.totalAuctionsInPool(); return _priceAt(pool.depositIndex(Maths.wdiv(debt, noOfLoans * 1e18))); diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 1ebf6db7a..0b4f4d1fc 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -736,7 +736,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } /// @inheritdoc IPoolState - function debtInfo() external view returns (uint256, uint256, uint256) { + function debtInfo() external view returns (uint256, uint256, uint256, uint256) { uint256 pendingInflator = PoolCommons.pendingInflator( inflatorState.inflator, inflatorState.inflatorUpdate, @@ -745,10 +745,12 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { return ( Maths.wmul(poolBalances.t0Debt, pendingInflator), Maths.wmul(poolBalances.t0Debt, inflatorState.inflator), - Maths.wmul(poolBalances.t0DebtInAuction, inflatorState.inflator) + Maths.wmul(poolBalances.t0DebtInAuction, inflatorState.inflator), + interestState.t0Debt2ToCollateral ); } + /// @inheritdoc IPoolDerivedState function depositUpToIndex(uint256 index_) external view override returns (uint256) { return Deposits.prefixSum(deposits, index_); diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index 63600e4f9..b46c53829 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -41,9 +41,10 @@ interface IPoolState { * @notice Returns pool related debt values. * @return debt_ Current amount of debt owed by borrowers in pool. * @return accruedDebt_ Debt owed by borrowers based on last inflator snapshot. - * @return debtInAuction_ Total amount of debt in auction. + * @return debtInAuction_ Total amount of debt in auction. + * @return t0Debt2ToCollateral_ t0debt accross all borrowers divided by their collateral, used in determining a collateralization weighted debt. */ - function debtInfo() external view returns (uint256 debt_, uint256 accruedDebt_, uint256 debtInAuction_); + function debtInfo() external view returns (uint256 debt_, uint256 accruedDebt_, uint256 debtInAuction_, uint256 t0Debt2ToCollateral_); /** * @notice Mapping of borrower addresses to {Borrower} structs. diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index af4853598..313bfb77b 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -42,6 +42,7 @@ - **I1**: interest rate (`InterestState.interestRate`) cannot be updated more than once in a 12 hours period of time (`InterestState.interestRateUpdate`) - **I2**: reserve interest (`ReserveAuctionState.totalInterestEarned`) accrues only once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and only if there's debt in the pool (`PoolBalancesState.t0Debt != 0`) - **I3**: pool inflator (`InflatorState.inflator`) cannot be updated more than once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and equals `1e18` if there's no debt in the pool (`PoolBalancesState.t0Debt != 0`) +- **I4**: for all borrowers where (`borrower.collateral != 0`) the sum of borrower debt squared divided by borrower collateral (`borrower.debt^2 / borrower.collateral`) should equal borrower collateralization accumulator (`t0Debt2ToCollateral`) ## Fenwick tree - **F1**: Value represented at index `i` (`Deposits.valueAt(i)`) is equal to the accumulation of scaled values incremented or decremented from index `i` diff --git a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol index 3d06804df..558fef4aa 100644 --- a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/ERC20Pool/ERC20DSTestPlus.sol @@ -133,7 +133,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { assertEq(collateral, 0); } ( , uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); - (uint256 debt, , ) = _pool.debtInfo(); + (uint256 debt, , ,) = _pool.debtInfo(); assertEq(debt, 0); assertEq(loansCount, 0); assertEq(_pool.pledgedCollateral(), 0); diff --git a/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol b/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol index a316f48ad..861eff072 100644 --- a/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol @@ -54,7 +54,7 @@ contract ERC20PoolFlashloanTest is ERC20HelperContract { newLup: _bucketPrice }); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, 25_024.038461538461550000 * 1e18); } diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index 937e0e2d4..97f8a5f07 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -926,14 +926,14 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }) ); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); // accrue interest _updateInterest(); // check that no interest earned if HTP is over the highest price bucket - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); } diff --git a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol index 865899811..1d032baeb 100644 --- a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol @@ -343,7 +343,7 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { maxThresholdPrice: 200.173076923076923000 * 1e18 }); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(_pool.depositSize(), 150_000 * POOL_PRECISION); assertEq(poolDebt, debt); @@ -405,7 +405,7 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { maxThresholdPrice: 100.173076923076923000 * 1e18 }); - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(_pool.depositSize(), 150_000 * 1e18); assertEq(poolDebt, debt); diff --git a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol index 20622c36e..7af66816d 100644 --- a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -1128,7 +1128,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { newLup: 601.252968524772188572 * 1e18 }); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); uint256 ptp = Maths.wdiv(poolDebt, 10 * 1e18); assertEq(ptp, 500.480769230769231000 * 1e18); diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol index 36c4fde06..166bf9230 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol @@ -47,6 +47,7 @@ contract BasicInvariants is InvariantsTestBase { * I1: Interest rate should only update once in 12 hours * I2: ReserveAuctionState.totalInterestEarned accrues only once per block and equals to 1e18 if pool debt = 0 * I3: Inflator should only update once per block + * I4: t0Debt2ToCollateral should sum correctly accross borrowers * Fenwick tree * F1: Value represented at index i (Deposits.valueAt(i)) is equal to the accumulation of scaled values incremented or decremented from index i @@ -166,7 +167,7 @@ contract BasicInvariants is InvariantsTestBase { function invariant_quoteTokenBalance_QT1() public useCurrentTimestamp { // convert pool quote balance into WAD uint256 poolBalance = _quote.balanceOf(address(_pool)) * 10**(18 - _quote.decimals()); - (uint256 poolDebt, , ) = _pool.debtInfo(); + (uint256 poolDebt, , ,) = _pool.debtInfo(); ( uint256 totalBondEscrowed, @@ -326,6 +327,26 @@ contract BasicInvariants is InvariantsTestBase { previousInflatorUpdate = currentInflatorUpdate; } + function invariant_t0Debt2ToCollateral_I4() public useCurrentTimestamp { + + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 manualDebt2ToCollateral; + + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + (uint256 borrowerT0Debt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); + + uint256 weight = borrowerCollateral != 0 ? borrowerT0Debt ** 2 / borrowerCollateral : 0; + + manualDebt2ToCollateral += weight; + } + + (,,, uint256 t0Debt2ToCollateral) = _pool.debtInfo(); + + require(t0Debt2ToCollateral == manualDebt2ToCollateral, "Incorrect debt2ToCollateral"); + + } + // deposits at index i (Deposits.valueAt(i)) is equal to the accumulation of scaled values incremented or decremented from index i function invariant_fenwick_depositAtIndex_F1() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol index 7582ac953..0b2ed40ac 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol @@ -264,7 +264,7 @@ abstract contract BaseHandler is Test { uint256 pendingInflator; uint256 poolDebt; { - (, poolDebt ,) = _pool.debtInfo(); + (, poolDebt ,,) = _pool.debtInfo(); (uint256 inflator, uint256 inflatorUpdate) = _pool.inflatorInfo(); diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol index d38cae271..f04e77384 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol +++ b/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol @@ -34,7 +34,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { numberOfCalls['UBBasicHandler.addQuoteToken']++; (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - (uint256 poolDebt, , ) = _pool.debtInfo(); + (uint256 poolDebt, , ,) = _pool.debtInfo(); uint256 lupIndex = _pool.depositIndex(poolDebt); (uint256 interestRate, ) = _pool.interestRateInfo(); @@ -241,7 +241,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.drawDebt']++; - (uint256 poolDebt, , ) = _pool.debtInfo(); + (uint256 poolDebt, , ,) = _pool.debtInfo(); // find bucket to borrow quote token uint256 bucket = _pool.depositIndex(amount_ + poolDebt) - 1; diff --git a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol index 7a222ae60..b8a3a3a74 100644 --- a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/ERC721Pool/ERC721DSTestPlus.sol @@ -138,7 +138,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { assertEq(collateral, 0); } ( , uint256 loansCount, , , ) = _poolUtils.poolLoansInfo(address(_pool)); - (uint256 debt, , ) = _pool.debtInfo(); + (uint256 debt, , ,) = _pool.debtInfo(); assertEq(debt, 0); assertEq(loansCount, 0); assertEq(_pool.pledgedCollateral(), 0); diff --git a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol index 50fe86a7b..16a7d05f0 100644 --- a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol @@ -490,7 +490,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); // check collateralization after pledge - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(_encumberance(poolDebt, _lup()), 0); // borrower borrows some quote @@ -502,7 +502,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); // check collateralization after borrow - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(_encumberance(poolDebt, _lup()), 2.992021560300836411 * 1e18); // should revert if borrower attempts to pull more collateral than is unencumbered diff --git a/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol b/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol index bb0e01d3e..61bc96714 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol @@ -53,7 +53,7 @@ contract ERC721PoolFlashloanTest is ERC721HelperContract { newLup: _bucketPrice }); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, 200.192307692307692400 * 1e18); } diff --git a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol index d6dc2ea8a..13f63d486 100644 --- a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol @@ -94,7 +94,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); uint256 expectedDebt = 5_004.326923076923075000 * 1e18; - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -118,7 +118,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 5_010.500446015624727374 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -143,7 +143,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 5_016.063127975675193806 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -167,7 +167,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 6_021.775783320497493092 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -195,7 +195,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { newLup: MAX_PRICE }); - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, 0); _assertBorrower({ @@ -250,7 +250,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); uint256 expectedBorrower1Debt = 8_007.692307692307696000 * 1e18; - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedBorrower1Debt); _assertBorrower({ @@ -283,7 +283,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { expectedBorrower1Debt = 8_007.875133804645608008 * 1e18; uint256 expectedBorrower2Debt = 2_752.644230769230770500 * 1e18; uint256 expectedPoolDebt = 10_760.519364573876378508 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); _assertBorrower({ @@ -327,7 +327,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { uint256 expectedBorrower3Debt = 2_502.403846153846154999 * 1e18; expectedPoolDebt = 13_263.168887490336411730 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); _assertBorrower({ @@ -369,7 +369,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { // check pool and borrower debt to confirm interest has accumulated expectedPoolDebt = 13_263.471703022178416339 * 1e18; - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); _assertLenderInterest(liquidityAdded, 0.637680300600360000 * 1e18); @@ -457,7 +457,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); uint256 expectedDebt = 5_004.326923076923075000 * 1e18; - (uint256 poolDebt, , ) = _pool.debtInfo(); + (uint256 poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -472,7 +472,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { skip(30 days); expectedDebt = 5_022.870348947539432923 * 1e18; - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -498,7 +498,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { newLup: MAX_PRICE }); - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, 0); _assertBorrower({ @@ -520,7 +520,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 5_003.894230769230769999 * 1e18; - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -545,7 +545,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 5_009.449578476990224065 * 1e18; - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -570,7 +570,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 5_014.454664494689841709 * 1e18; - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -594,7 +594,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { }); expectedDebt = 6_019.594382773827921756 * 1e18; - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); _assertBorrower({ @@ -622,7 +622,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { newLup: MAX_PRICE }); - (poolDebt, , ) = _pool.debtInfo(); + (poolDebt, , ,) = _pool.debtInfo(); assertEq(poolDebt, 0); _assertBorrower({ diff --git a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol index ec508c4b6..f1ca3fddc 100644 --- a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol @@ -49,12 +49,12 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { newLup: 251_183.992399245533703810 * 1e18 }); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt - 175_000 * 1e18, 168.26923076923085 * 1e18); skip(26 weeks); - (poolDebt,,) = _pool.debtInfo(); + (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt - 175_000 * 1e18, 4_590.373946590638353626 * 1e18); // debt matches develop } @@ -230,7 +230,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { collateralToPull: 0, newLup: MAX_PRICE }); - (uint256 debt,,) = _pool.debtInfo(); + (uint256 debt,,,) = _pool.debtInfo(); assertEq(debt, 0); uint256 reserves = 831.584938142442153626 * 1e18; diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 0187edb90..7ee435f8e 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -444,7 +444,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { (uint256 borrowerDebt, uint256 borrowerCollateral , ) = _poolUtils.borrowerInfo(address(_pool), state_.borrower); (, uint256 lockedBonds) = _pool.kickerInfo(state_.kicker); (uint256 auctionTotalBondEscrowed,,,) = _pool.reservesInfo(); - (,,uint256 auctionDebtInAuction) = _pool.debtInfo(); + (,,uint256 auctionDebtInAuction,) = _pool.debtInfo(); uint256 borrowerThresholdPrice = borrowerCollateral > 0 ? borrowerDebt * Maths.WAD / borrowerCollateral : 0; assertEq(auctionKickTime != 0, state_.active); @@ -485,7 +485,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { uint256 poolTargetUtilization ) = _poolUtils.poolUtilizationInfo(address(_pool)); - (uint256 poolDebt,,) = _pool.debtInfo(); + (uint256 poolDebt,,,) = _pool.debtInfo(); assertEq(htp, state_.htp); assertEq(lup, state_.lup); From 3ce4bb3386b35a84c9762e4bec0dcb2014a0d32f Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Mon, 3 Apr 2023 12:33:15 -0400 Subject: [PATCH 37/70] Calc rewards outside bucket loop (#720) # Description of change ## High level * Adjusted `_calculateNewRewards()` method so that instead of accumulating reward when looping over buckets we accumulate the stakers interest earned. * Change results in a significant reduction in gas for the `calculateRewards` method. * I believe the discrepencies in the test output is the result of the muliplication after division in the `calculateRewards` method and doesn't have to do with this change @mattcushman and @MikeHathaway to confirm. --------- Co-authored-by: Ian Harvey --- src/RewardsManager.sol | 28 +++++++++---------- tests/forge/RewardsManager.t.sol | 48 ++++++++++++++++---------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 683e9ef50..43ace72a2 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -426,6 +426,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { uint256 nextEpoch = epoch_ + 1; uint256 claimedRewardsInNextEpoch = rewardsClaimed[nextEpoch]; uint256 bucketIndex; + uint256 interestEarned; // iterate through all buckets and calculate epoch rewards for for (uint256 i = 0; i < positionIndexes_.length; ) { @@ -444,27 +445,26 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } // calculate the amount of interest accrued in current epoch - uint256 interestEarned = _calculateExchangeRateInterestEarned( + interestEarned += _calculateExchangeRateInterestEarned( ajnaPool_, nextEpoch, bucketIndex, bucketSnapshot.lpsAtStakeTime, bucketRate - ); - - // calculate and accumulate rewards if interest earned - if (interestEarned != 0) { - epochRewards_ += _calculateNewRewards( - ajnaPool_, - interestEarned, - nextEpoch, - epoch_, - claimedRewardsInNextEpoch - ); - } - + ); unchecked { ++i; } } + + // calculate and accumulate rewards if interest earned + if (interestEarned != 0) { + epochRewards_ = _calculateNewRewards( + ajnaPool_, + interestEarned, + nextEpoch, + epoch_, + claimedRewardsInNextEpoch + ); + } } /** diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index cefd36002..690079a30 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -425,7 +425,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); - assertEq(rewardsEarned, 40.899689081331421210 * 1e18); + assertEq(rewardsEarned, 40.899689081331421207 * 1e18); // claim rewards accrued since deposit changePrank(_minterOne); @@ -521,7 +521,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(2, 0), - reward: 86.809555428378605150 * 1e18, + reward: 86.809555428378605148 * 1e18, updateRatesReward: 4.173624213367915365 * 1e18 }); } @@ -963,7 +963,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(1, 0), - reward: 0.298393228234161298 * 1e18, + reward: 0.227347221514659977 * 1e18, updateRatesReward: 0 }); } @@ -1018,7 +1018,7 @@ contract RewardsManagerTest is ERC20HelperContract { assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 204.498445406656758712 * 1e18); + assertEq(rewardsEarned, 204.498445406656758711 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /******************************/ @@ -1043,7 +1043,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567490 * 1e18); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /*****************************/ @@ -1060,7 +1060,7 @@ contract RewardsManagerTest is ERC20HelperContract { // skip updating exchange rates and check available rewards uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380567490 * 1e18); + assertEq(rewardsEarnedNoUpdate, 376.880968767380567488 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); // snapshot calling update exchange rate @@ -1076,7 +1076,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 517.072612267116269224 * 1e18); + assertEq(rewardsEarned, 517.072612267116269218 * 1e18); assertGt(rewardsEarned, rewardsEarnedNoUpdate); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -1097,7 +1097,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567490 * 1e18); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); // call update exchange rate changePrank(_updater2); @@ -1109,7 +1109,7 @@ contract RewardsManagerTest is ERC20HelperContract { // check rewards earned won't increase since previous update was missed rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567490 * 1e18); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); /*****************************/ /*** Fifth Reserve Auction ***/ @@ -1132,7 +1132,7 @@ contract RewardsManagerTest is ERC20HelperContract { assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905358 * 1e18); rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049744944 * 1e18); + assertEq(rewardsEarned, 493.039460320049744942 * 1e18); // claim all rewards accrued since deposit changePrank(_minterOne); @@ -1225,7 +1225,7 @@ contract RewardsManagerTest is ERC20HelperContract { vm.expectEmit(true, true, true, true); emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133149070 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570280 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570277 * 1e18); // check MoveLiquidity emits for (uint256 i = 0; i < firstIndexes.length; ++i) { vm.expectEmit(true, true, true, true); @@ -1274,9 +1274,9 @@ contract RewardsManagerTest is ERC20HelperContract { // claim rewards accrued since second movement of lps changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570277 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249745 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249743 * 1e18); _rewardsManager.claimRewards(tokenId, currentBurnEpoch); } @@ -1346,21 +1346,21 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(1, 0), - reward: 39.908019526547891790 * 1e18, + reward: 39.908019526547891787 * 1e18, updateRatesReward: 0 }); uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 39.908019526547891790 * 1e18); + assertEq(minterTwoBalance, 39.908019526547891787 * 1e18); _unstakeToken({ minter: _minterThree, pool: address(_poolOne), tokenId: tokenIdThree, claimedArray: _epochsClaimedArray(1, 0), - reward: 33.248129642902710516 * 1e18, + reward: 33.248129642902710514 * 1e18, updateRatesReward: 0 }); uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 33.248129642902710516 * 1e18); + assertEq(minterThreeBalance, 33.248129642902710514 * 1e18); assertGt(minterTwoBalance, minterThreeBalance); } @@ -1625,7 +1625,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(3, 0), - reward: 75.626985109732100715 * 1e18, + reward: 75.626985109732100713 * 1e18, updateRatesReward: 0 }); @@ -1634,7 +1634,7 @@ contract RewardsManagerTest is ERC20HelperContract { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(3, 0), - reward: 378.134925548660503565 * 1e18, + reward: 378.134925548660503562 * 1e18, updateRatesReward: 0 }); } @@ -1732,7 +1732,7 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_minterOne); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); vm.expectEmit(true, true, true, true); emit Unstake(_minterOne, address(_poolOne), tokenIdOne); _rewardsManager.unstake(tokenIdOne); @@ -1747,12 +1747,12 @@ contract RewardsManagerTest is ERC20HelperContract { // test when enough tokens in rewards manager contracts changePrank(_minterOne); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); vm.expectEmit(true, true, true, true); emit Unstake(_minterOne, address(_poolOne), tokenIdOne); _rewardsManager.unstake(tokenIdOne); assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421210 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421207 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); @@ -1837,9 +1837,9 @@ contract RewardsManagerTest is ERC20HelperContract { changePrank(_minterOne); assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421210 * 1e18); + emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570280 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570277 * 1e18); assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); } From 1e0c9dda21b9232ab1f0372c535af93da8f9cbe9 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:07:32 -0400 Subject: [PATCH 38/70] Prevent DIV/0 revert calculating MOMP on a pool with no loans (#712) * prevent revert calculating MOMP when pool has no loans * define MOMP as HPB rather than MAX_PRICE when there are no borrowers * pr feedback --- src/PoolInfoUtils.sol | 10 ++++++++-- tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index e9a0709f6..cedd47fdb 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -318,10 +318,16 @@ contract PoolInfoUtils { ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt, , ,) = pool.debtInfo(); ( , , uint256 noOfLoans) = pool.loansInfo(); noOfLoans += pool.totalAuctionsInPool(); - return _priceAt(pool.depositIndex(Maths.wdiv(debt, noOfLoans * 1e18))); + if (noOfLoans == 0) { + // if there are no borrowers, return the HPB + return _priceAt(pool.depositIndex(1)); + } else { + // otherwise, calculate the MOMP + (uint256 debt, , , ) = pool.debtInfo(); + return _priceAt(pool.depositIndex(Maths.wdiv(debt, noOfLoans * 1e18))); + } } /** diff --git a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol b/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol index 142769d87..60729c360 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { Token } from '../utils/Tokens.sol'; import 'src/libraries/helpers/PoolHelper.sol'; import 'src/interfaces/pool/erc20/IERC20Pool.sol'; @@ -204,6 +205,18 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { function testMomp() external { assertEq(_poolUtils.momp(address(_pool)), 2_981.007422784467321543 * 1e18); + + // ensure calculation does not revert on pools with no loans and no deposit + IERC20 otherCollateral = new Token("MompTestCollateral", "MTC"); + IERC20Pool emptyPool = ERC20Pool(_poolFactory.deployPool(address(otherCollateral), address(_quote), 0.05 * 10**18)); + assertEq(_poolUtils.momp(address(emptyPool)), MIN_PRICE); + + // should return HPB on pools with liquidity but no loans + changePrank(_lender); + uint256 hpbIndex = 369; + _quote.approve(address(emptyPool), type(uint256).max); + emptyPool.addQuoteToken(0.0213 * 1e18, hpbIndex, type(uint256).max); + assertEq(_poolUtils.momp(address(emptyPool)), _priceAt(hpbIndex)); } function testBorrowFeeRate() external { From 492e80a5ca51f691e15a6b559238ba83b1ff67f2 Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Mon, 3 Apr 2023 17:25:19 -0400 Subject: [PATCH 39/70] RewardsManager test refactor and cleanup (#718) # Description of change ## High level * Refactored `RewardsManager.t.sol` into `Rewards/RewardsManager.t.sol` & `Rewards/RewadsDSTestPlus.t.sol` --------- Co-authored-by: Ian Harvey --- tests/forge/Rewards/RewardsDSTestPlus.sol | 504 +++++ tests/forge/Rewards/RewardsManager.t.sol | 1876 +++++++++++++++++++ tests/forge/RewardsManager.t.sol | 2048 --------------------- tests/forge/utils/DSTestPlus.sol | 8 +- 4 files changed, 2383 insertions(+), 2053 deletions(-) create mode 100644 tests/forge/Rewards/RewardsDSTestPlus.sol create mode 100644 tests/forge/Rewards/RewardsManager.t.sol delete mode 100644 tests/forge/RewardsManager.t.sol diff --git a/tests/forge/Rewards/RewardsDSTestPlus.sol b/tests/forge/Rewards/RewardsDSTestPlus.sol new file mode 100644 index 000000000..a2b2d5d7b --- /dev/null +++ b/tests/forge/Rewards/RewardsDSTestPlus.sol @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import 'src/RewardsManager.sol'; +import 'src/PoolInfoUtils.sol'; +import 'src/PositionManager.sol'; + +import 'src/interfaces/rewards/IRewardsManager.sol'; +import 'src/interfaces/position/IPositionManager.sol'; + +import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +import { Token } from '../utils/Tokens.sol'; +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { PositionManager } from 'src/PositionManager.sol'; + +import { ERC20HelperContract } from '../ERC20Pool/ERC20DSTestPlus.sol'; +import { IRewardsManagerEvents } from 'src/interfaces/rewards/IRewardsManagerEvents.sol'; + +abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContract { + + address internal _minterOne; + address internal _minterTwo; + address internal _minterThree; + address internal _minterFour; + address internal _minterFive; + + ERC20 internal _ajnaToken; + + IPool internal _poolTwo; + IRewardsManager internal _rewardsManager; + IPositionManager internal _positionManager; + + uint256 internal REWARDS_CAP = 0.8 * 1e18; + + struct MintAndMemorializeParams { + uint256[] indexes; + address minter; + uint256 mintAmount; + IPool pool; + } + + struct TriggerReserveAuctionParams { + address borrower; + uint256 borrowAmount; + uint256 limitIndex; + IPool pool; + } + + function _stakeToken(address pool, address owner, uint256 tokenId) internal { + changePrank(owner); + + // approve and deposit NFT into rewards contract + PositionManager(address(_positionManager)).approve(address(_rewardsManager), tokenId); + vm.expectEmit(true, true, true, true); + emit Stake(owner, address(pool), tokenId); + _rewardsManager.stake(tokenId); + + // check token was transferred to rewards contract + (address ownerInf, address poolInf, ) = _rewardsManager.getStakeInfo(tokenId); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); + assertEq(ownerInf, owner); + assertEq(poolInf, pool); + } + + function _unstakeToken( + address owner, + address pool, + uint256[] memory claimedArray, + uint256 tokenId, + uint256 reward, + uint256[] memory indexes, + uint256 updateExchangeRatesReward + ) internal { + + changePrank(owner); + + // when the token is unstaked updateExchangeRates emits + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(owner, pool, indexes, updateExchangeRatesReward); + + // when the token is unstaked claimRewards emits + vm.expectEmit(true, true, true, true); + emit ClaimRewards(owner, pool, tokenId, claimedArray, reward); + + // when the token is unstaked unstake emits + vm.expectEmit(true, true, true, true); + emit Unstake(owner, address(pool), tokenId); + _rewardsManager.unstake(tokenId); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); + + // check token was transferred from rewards contract to minter + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); + + // invariant: all bucket snapshots are removed for the token id that was unstaken + for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); + assertEq(lps, 0); + assertEq(rate, 0); + } + + (address ownerInf, address poolInf, uint256 interactionBlockInf) = _rewardsManager.getStakeInfo(tokenId); + assertEq(ownerInf, address(0)); + assertEq(poolInf, address(0)); + assertEq(interactionBlockInf, 0); + } + + function _assertBurn( + address pool, + uint256 epoch, + uint256 timestamp, + uint256 interest, + uint256 burned, + uint256 tokensToBurn + ) internal { + + (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); + + assertEq(bETimestamp, timestamp); + assertEq(bEInterest, interest); + assertEq(bEBurned, burned); + assertEq(burned, tokensToBurn); + } + + + function _updateExchangeRates( + address updater, + address pool, + uint256[] memory indexes, + uint256 reward + ) internal { + changePrank(updater); + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(updater, pool, indexes, reward); + _rewardsManager.updateBucketExchangeRatesAndClaim(pool, indexes); + } + + + function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { + epochsClaimed_ = new uint256[](numberOfAuctions_); + uint256 claimEpoch = lastClaimed_; // starting index, not inclusive + + for (uint256 i = 0; i < numberOfAuctions_; i++) { + epochsClaimed_[i] = claimEpoch + 1; + claimEpoch += 1; + } + } + + function _claimRewards( + address from, + address pool, + uint256 tokenId, + uint256 reward, + uint256[] memory epochsClaimed + ) internal { + changePrank(from); + uint256 fromAjnaBal = _ajnaToken.balanceOf(from); + + uint256 currentBurnEpoch = IPool(pool).currentBurnEpoch(); + vm.expectEmit(true, true, true, true); + emit ClaimRewards(from, pool, tokenId, epochsClaimed, reward); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + + assertEq(_ajnaToken.balanceOf(from), fromAjnaBal + reward); + } + + function _moveStakedLiquidity( + address from, + uint256 tokenId, + uint256[] memory fromIndexes, + bool fromIndStaked, + uint256[] memory toIndexes, + uint256 expiry + ) internal { + + changePrank(from); + + // check MoveLiquidity emits + for (uint256 i = 0; i < fromIndexes.length; ++i) { + vm.expectEmit(true, true, true, true); + emit MoveLiquidity(address(_rewardsManager), tokenId, fromIndexes[i], toIndexes[i]); + } + + vm.expectEmit(true, true, true, true); + emit MoveStakedLiquidity(tokenId, fromIndexes, toIndexes); + + if (fromIndStaked) { + // check exchange rates are updated + vm.expectEmit(true, true, true, true); + emit UpdateExchangeRates(_minterOne, address(_pool), toIndexes, 0); + } + _rewardsManager.moveStakedLiquidity(tokenId, fromIndexes, toIndexes, expiry); + + } + + function _assertNotOwnerOfDepositRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertNotOwnerOfDepositUnstakeRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertAlreadyClaimedRevert(address from , uint256 tokenId) internal { + // check only deposit owner can claim rewards + changePrank(from); + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); + _rewardsManager.claimRewards(tokenId, currentBurnEpoch); + } + + function _assertStake( + address owner, + address pool, + uint256 tokenId, + uint256 burnEvent, + uint256 rewardsEarned + ) internal { + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + (address ownerInf, address poolInf, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenId); + uint256 rewardsEarnedInf = _rewardsManager.calculateRewards(tokenId, currentBurnEpoch); + + assertEq(ownerInf, owner); + assertEq(poolInf, pool); + assertEq(interactionBurnEvent, burnEvent); + assertEq(rewardsEarnedInf, rewardsEarned); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), address(_rewardsManager)); + } +} + +abstract contract RewardsHelperContract is RewardsDSTestPlus { + + address internal _bidder; + address internal _updater; + address internal _updater2; + + Token internal _collateralOne; + Token internal _quoteOne; + Token internal _collateralTwo; + Token internal _quoteTwo; + + constructor() { + vm.makePersistent(_ajna); + + _ajnaToken = ERC20(_ajna); + _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); + _rewardsManager = new RewardsManager(_ajna, _positionManager); + + _collateralOne = new Token("Collateral 1", "C1"); + _quoteOne = new Token("Quote 1", "Q1"); + _collateralTwo = new Token("Collateral 2", "C2"); + _quoteTwo = new Token("Quote 2", "Q2"); + + _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); + + // provide initial ajna tokens to staking rewards contract + deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); + } + + // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt + function _createTestBorrower(address pool, address borrower, uint256 borrowAmount, uint256 limitIndex) internal returns (uint256 collateralToPledge_) { + + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + // deal twice as much quote so the borrower has sufficient quote to repay the loan + deal(address(quote), borrower, Maths.wmul(borrowAmount, Maths.wad(2))); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + collateralToPledge_ = _requiredCollateral(borrowAmount, limitIndex); + deal(address(collateral), borrower, collateralToPledge_); + } + + function _triggerReserveAuctionsNoTake( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex + ) internal { + // create a new borrower to write state required for reserve auctions + uint256 collateralToPledge = _createTestBorrower(address(pool), borrower, borrowAmount, limitIndex); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, Maths.wdiv(borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + _startClaimableReserveAuction(address(pool), _bidder); + } + + function _startClaimableReserveAuction( + address pool, + address bidder + ) internal { + changePrank(bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + } + + function _mintAndMemorializePositionNFT( + address minter, + uint256 mintAmount, + address pool, + uint256[] memory indexes + ) internal returns (uint256 tokenId_) { + changePrank(minter); + + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + + // deal tokens to the minter + deal(address(quote), minter, mintAmount * indexes.length); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(minter, address(pool), keccak256("ERC20_NON_SUBSET_HASH")); + tokenId_ = _positionManager.mint(mintParams); + + uint256[] memory lpBalances = new uint256[](indexes.length); + + for (uint256 i = 0; i < indexes.length; i++) { + ERC20Pool(address(pool)).addQuoteToken(mintAmount, indexes[i], type(uint256).max); + (lpBalances[i], ) = ERC20Pool(address(pool)).lenderInfo(indexes[i], minter); + } + + ERC20Pool(address(pool)).increaseLPsAllowance(address(_positionManager), indexes, lpBalances); + + // construct memorialize params struct + IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( + tokenId_, indexes + ); + + _positionManager.memorializePositions(memorializeParams); + + // register position manager as lender at memorialized indexes (for LP test assertions) + _registerLender(address(_positionManager), indexes); + } + + function _triggerReserveAuctions( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex, + uint256 tokensToBurn + ) internal returns (uint256 tokensBurned_) { + + // fund borrower to write state required for reserve auctions + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + deal(address(quote), borrower, borrowAmount); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); + deal(address(_collateral), borrower, collateralToPledge); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // Can't trigger reserve auction if less than two weeks have passed since last auction + vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves(pool, _bidder); + + (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); + assertEq(tokensBurned_, tokensToBurn); + + return tokensBurned_; + } + + function _triggerReserveAuctionsBurnUnknown( + address borrower, + address pool, + uint256 borrowAmount, + uint256 limitIndex + ) internal returns (uint256 tokensBurned_) { + + // fund borrower to write state required for reserve auctions + changePrank(borrower); + Token collateral = Token(ERC20Pool(address(pool)).collateralAddress()); + Token quote = Token(ERC20Pool(address(pool)).quoteTokenAddress()); + deal(address(quote), borrower, borrowAmount); + + // approve tokens + collateral.approve(address(pool), type(uint256).max); + quote.approve(address(pool), type(uint256).max); + + uint256 collateralToPledge = _requiredCollateral(borrowAmount, limitIndex); + deal(address(_collateral), borrower, collateralToPledge); + + // borrower drawsDebt from the pool + ERC20Pool(address(pool)).drawDebt(borrower, borrowAmount, limitIndex, collateralToPledge); + + // allow time to pass for interest to accumulate + skip(26 weeks); + + // borrower repays some of their debt, providing reserves to be claimed + // don't pull any collateral, as such functionality is unrelated to reserve auctions + ERC20Pool(address(pool)).repayDebt(borrower, borrowAmount, 0, borrower, MAX_FENWICK_INDEX); + + // start reserve auction + changePrank(_bidder); + _ajnaToken.approve(address(pool), type(uint256).max); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // Can't trigger reserve auction if less than two weeks have passed since last auction + vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); + ERC20Pool(address(pool)).startClaimableReserveAuction(); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves(pool, _bidder); + + (,, tokensBurned_) = IPool(pool).burnInfo(IPool(pool).currentBurnEpoch()); + + return tokensBurned_; + } + + function _takeReserves(address pool, address from) internal { + changePrank(from); + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(pool); + + ERC20Pool(pool).takeReserves(curClaimableReservesRemaining); + } + + function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { + // calculate the required collateral based upon the borrow amount and index price + (uint256 interestRate, ) = pool_.interestRateInfo(); + uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient + uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); + requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; + } + + // Helper function that returns a random subset from array + function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { + uint256[] memory copyOfArray = new uint256[](array.length); + for(uint j = 0; j < copyOfArray.length; j++){ + copyOfArray[j] = array[j]; + } + uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); + subsetArray = new uint256[](randomNoOfNfts); + for(uint256 i = 0; i < randomNoOfNfts; i++) { + uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); + subsetArray[i] = copyOfArray[randomIndex]; + copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; + } + } + + // Returns N addresses array + function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { + addresses_ = new address[](noOfAddress); + for(uint i = 0; i < noOfAddress; i++) { + addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); + } + } +} diff --git a/tests/forge/Rewards/RewardsManager.t.sol b/tests/forge/Rewards/RewardsManager.t.sol new file mode 100644 index 000000000..af18e4417 --- /dev/null +++ b/tests/forge/Rewards/RewardsManager.t.sol @@ -0,0 +1,1876 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.14; + +import 'src/PoolInfoUtils.sol'; +import 'src/PositionManager.sol'; +import 'src/interfaces/rewards/IRewardsManager.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { RewardsHelperContract } from './RewardsDSTestPlus.sol'; + +contract RewardsManagerTest is RewardsHelperContract { + + address internal _borrower; + address internal _borrower2; + address internal _borrower3; + address internal _lender; + address internal _lender1; + + uint256 constant BLOCKS_IN_DAY = 7200; + mapping (uint256 => address) internal tokenIdToMinter; + mapping (address => uint256) internal minterToBalance; + + function setUp() external { + + // borrowers + _borrower = makeAddr("borrower"); + _borrower2 = makeAddr("borrower2"); + _borrower3 = makeAddr("borrower3"); + + _lender = makeAddr("lender"); + _lender1 = makeAddr("lender1"); + + // instantiate test minters + _minterOne = makeAddr("minterOne"); + _minterTwo = makeAddr("minterTwo"); + _minterThree = makeAddr("minterThree"); + _minterFour = makeAddr("minterFour"); + _minterFive = makeAddr("minterFive"); + + // instantiate test bidder + _bidder = makeAddr("bidder"); + deal(address(_ajna), _bidder, 900_000_000 * 10**18); + + vm.prank(_bidder); + _ajnaToken.approve(address(_pool), type(uint256).max); + vm.prank(_bidder); + ERC20(address(_quoteOne)).approve(address(_pool), type(uint256).max); + ERC20(address(_quoteTwo)).approve(address(_pool), type(uint256).max); + + // instantiate test updater + _updater = makeAddr("updater"); + _updater2 = makeAddr("updater2"); + + _mintCollateralAndApproveTokens(_borrower, 100 * 1e18); + _mintQuoteAndApproveTokens(_borrower, 200_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower2, 1_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower2, 200_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower3, 1_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower3, 200_000 * 1e18); + + _mintQuoteAndApproveTokens(_lender, 200_000 * 1e18); + _mintQuoteAndApproveTokens(_lender1, 200_000 * 1e18); + + _mintQuoteAndApproveTokens(_minterOne, 500_000_000 * 1e18); + _mintQuoteAndApproveTokens(_minterTwo, 500_000_000 * 1e18); + } + + + function testStakeToken() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure NFT position two + depositIndexes = new uint256[](4); + depositIndexes[0] = 5; + depositIndexes[1] = 1; + depositIndexes[2] = 3; + depositIndexes[3] = 12; + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_poolTwo) + }); + + // check only owner of an NFT can deposit it into the rewards contract + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_poolTwo), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + } + + function testUnstakeToken() external { + skip(10); + + // configure NFT position one + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // check only owner of an NFT can deposit it into the rewards contract + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // only owner should be able to withdraw the NFT + _assertNotOwnerOfDepositUnstakeRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + uint256[] memory claimedArray = new uint256[](0); + + _unstakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne, + claimedArray: claimedArray, // no rewards as no reserve auctions have occured + reward: 0, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + } + + function testUpdateExchangeRatesAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool), + tokensToBurn: 81.799378162662704349 * 1e18 + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + // check only deposit owner can claim rewards + _assertNotOwnerOfDepositRevert({ + from: _updater, + tokenId: tokenIdOne + }); + + // claim rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: 40.899689081331351737 * 1e18, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + + // check can't claim rewards twice + _assertAlreadyClaimedRevert({ + from: _minterOne, + tokenId: tokenIdOne + }); + + _assertStake({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + burnEvent: 1, + rewardsEarned: 0 + }); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331351737 * 1e18); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18 + }); + + skip(2 weeks); + + // check can't call update exchange rate after the update period has elapsed + uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + assertEq(updateRewards, 0); + } + + function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // epoch 0 - 1 is checked for rewards + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // first reserve auction happens successfully -> epoch 1 + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2_555, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18 + }); + + + // second reserve auction happens successfully -> epoch 2 + tokensToBurn += _triggerReserveAuctions({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool), + tokensToBurn: 150.804205428530300439 * 1e18 + }); + + // check owner can withdraw the NFT and rewards will be automatically claimed + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(2, 0), + reward: 78.852344077558527016 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 3.450241363293378951 * 1e18 + }); + } + + function testWithdrawAndClaimRewardsNoReserveTake() external { + + // healthy epoch, bad epoch + + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // epoch 0 - 1 is checked for rewards + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + //call update exchange rate to enable claiming rewards for epoch 0 - 1 + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + skip(2 weeks); + + // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 + _triggerReserveAuctionsNoTake({ + borrower: _borrower, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), + burned: 81.799378162662704349 * 1e18, + tokensToBurn: tokensToBurn, + interest: 6.443638300196908069 * 1e18 + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 3.399633583097821167 * 1e18 + }); + } + + // two lenders stake their positions in the pool + // staker one bucket bankrupt, staker two bucket active + // interest accrued to both buckets, but staker one receives no rewards + function testClaimRewardsBankruptBucket() external { + + address[] memory transferors = new address[](1); + transferors[0] = address(_positionManager); + + changePrank(_minterOne); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + changePrank(_minterTwo); + _quote.approve(address(_positionManager), type(uint256).max); + _pool.approveLPsTransferors(transferors); + + /*****************************/ + /*** Initialize Pool State ***/ + /*****************************/ + + // MinterOne adds Quote token accross 5 prices + _addInitialLiquidity({ + from: _minterOne, + amount: 2_000 * 1e18, + index: _i9_91 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 5_000 * 1e18, + index: _i9_81 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 11_000 * 1e18, + index: _i9_72 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 25_000 * 1e18, + index: _i9_62 + }); + _addInitialLiquidity({ + from: _minterOne, + amount: 30_000 * 1e18, + index: _i9_52 + }); + + // first borrower adds collateral token and borrows + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + amount: 2 * 1e18 + }); + _borrow({ + from: _borrower, + amount: 19.25 * 1e18, + indexLimit: _i9_91, + newLup: 9.917184843435912074 * 1e18 + }); + + // second borrower adds collateral token and borrows + _pledgeCollateral({ + from: _borrower2, + borrower: _borrower2, + amount: 1_000 * 1e18 + }); + _borrow({ + from: _borrower2, + amount: 9_710 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); + + /*****************************/ + /*** Lenders Deposits NFTs ***/ + /*****************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](1); + uint256[] memory depositIndexes2 = new uint256[](1); + depositIndexes[0] = _i9_91; + depositIndexes2[0] = _i9_81; + + // ERC20Pool pool = ERC20Pool(address(_pool)); + + // stake NFT position one + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 2_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + + // stake NFT position two + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes2, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: address(_pool) + }); + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + + /***********************************/ + /*** Borrower Bankrupts A Bucket ***/ + /***********************************/ + + // Skip to make borrower two undercollateralized + skip(100 days); + + // all QT was inserted when minting NFT, provide more to kick + deal(address(_quote), _minterTwo, 10_000 * 1e18); + + _kick({ + from: _minterTwo, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + + // skip ahead so take can be called on the loan + skip(10 hours); + + // take entire collateral + _take({ + from: _minterTwo, + borrower: _borrower2, + maxCollateral: 1_000 * 1e18, + bondChange: 6.531114528261135360 * 1e18, + givenAmount: 653.111452826113536000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); + + _settle({ + from: _minterTwo, + borrower: _borrower2, + maxDepth: 10, + settledDebt: 9_891.935520844277346922 * 1e18 + }); + + // bucket is insolvent, balances are reset + _assertBucket({ + index: _i9_91, + lpBalance: 0, // bucket is bankrupt + collateral: 0, + deposit: 0, + exchangeRate: 1 * 1e18 + }); + + // lower priced bucket isn't bankrupt, but exchange rate has decreased + _assertBucket({ + index: _i9_81, + lpBalance: 10_000 * 1e18, + collateral: 0, + deposit: 4_936.865619773958005817 * 1e18, + exchangeRate: 0.493686561977395801 * 1e18 + }); + + /***********************/ + /*** Reserve Auction ***/ + /***********************/ + + // skip some time to accumulate reserves + skip(1000 days); + + // update pool reserves + _pool.updateInterest(); + + // start reserve auction + _startClaimableReserveAuction({ + pool: address(_pool), + bidder: _bidder + }); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + ( + , + , + uint256 curClaimableReservesRemaining, + , + ) = _poolUtils.poolReservesInfo(address(_pool)); + + // take claimable reserves + changePrank(_bidder); + _pool.takeReserves(curClaimableReservesRemaining); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + // _minterOne withdraws and claims rewards, rewards should be 0 + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + + // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0, + indexes: depositIndexes2, + updateExchangeRatesReward: 0 + }); + } + + function testClaimRewardsCap() external { + skip(10); + + /***************************/ + /*** Lender Deposits NFT ***/ + /***************************/ + + // set deposit indexes + uint256[] memory depositIndexes = new uint256[](2); + uint256[] memory depositIndex1 = new uint256[](1); + uint256[] memory depositIndex2 = new uint256[](1); + depositIndexes[0] = 2770; + depositIndexes[1] = 2771; + depositIndex1[0] = 2771; + depositIndex2[0] = 2770; + + // configure NFT position one + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 10_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /************************************/ + /*** Borrower One Accrue Interest ***/ + /************************************/ + + // borrower borrows + (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower, 10_000 * 1e18, 2770); + + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 5 * 1e18, + limitIndex: 2770, + collateralToPledge: collateralToPledge, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // pass time to allow interest to accrue + skip(2 hours); + + // borrower repays their loan + (uint256 debt, , ) = _pool.borrowerInfo(_borrower); + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: debt, + amountRepaid: 5.004807692307692310 * 1e18, + collateralToPull: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + // start reserve auction + _startClaimableReserveAuction({ + pool: address(_pool), + bidder: _bidder + }); + + // _borrower now takes out more debt to accumulate more interest + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 2_000 * 1e18, + limitIndex: 2770, + collateralToPledge: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // allow time to pass for the reserve price to decrease + skip(24 hours); + + _takeReserves({ + pool: address(_pool), + from: _bidder + }); + + (,, uint256 tokensBurned) = IPool(address(_pool)).burnInfo(IPool(address(_pool)).currentBurnEpoch()); + + // recorder updates the change in exchange rates in the first index + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndex1, + reward: 0.007104600671645296 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); + + _assertBurn({ + pool: address(_pool), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0, + tokensToBurn: 0 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 0.284184026893324971 * 1e18, + interest: 0.000048562908902619 * 1e18, + tokensToBurn: tokensBurned + }); + + // skip more time to allow more interest to accrue + skip(10 days); + + // borrower repays their loan again + (debt, , ) = _pool.borrowerInfo(_borrower); + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: debt, + amountRepaid: 2001.900281182536528586 * 1e18, + collateralToPull: 0, + newLup: 1_004.989662429170775094 * 1e18 + }); + + // recorder updates the change in exchange rates in the second index + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndex2, + reward: 0.021313802017687201 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); + + + /*******************************************/ + /*** Lender Withdraws And Claims Rewards ***/ + /*******************************************/ + + // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 0.227347221514659977 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + } + + function testMultiPeriodRewardsSingleClaim() external { + skip(10); + + uint256 totalTokensBurned; + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](10); + depositIndexes[0] = 5995; + depositIndexes[1] = 5996; + depositIndexes[2] = 5997; + depositIndexes[3] = 5998; + depositIndexes[4] = 5999; + depositIndexes[5] = 6000; + depositIndexes[6] = 6001; + depositIndexes[7] = 6002; + depositIndexes[8] = 6003; + depositIndexes[9] = 6004; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 408.996890813313521802 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6000, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 20.449844540665688882 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); + + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 204.498445406656758711 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + // trigger second reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 753.761937534761336922 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 17.238252336072314418 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater), 37.688096876738003300 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + /*****************************/ + /*** Third Reserve Auction ***/ + /*****************************/ + + // trigger third reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1_034.145224534232837796 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // skip updating exchange rates and check available rewards + uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarnedNoUpdate, 376.880968767380567488 * 1e18); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // snapshot calling update exchange rate + uint256 snapshot = vm.snapshot(); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 14.019164349973606338 * 1e18 + }); + + assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606338 * 1e18); + + // check available rewards + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertGt(rewardsEarned, rewardsEarnedNoUpdate); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + + // revert to no update state + vm.revertTo(snapshot); + + /******************************/ + /*** Fourth Reserve Auction ***/ + /******************************/ + + // triger fourth reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1_289.513636917170056104 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // check rewards earned + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 0 + }); + assertEq(_ajnaToken.balanceOf(_updater2), 0); + + // check rewards earned won't increase since previous update was missed + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + + /*****************************/ + /*** Fifth Reserve Auction ***/ + /*****************************/ + + // triger fifth reserve auction + totalTokensBurned += _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 1521.830620022508618175 * 1e18, + borrowAmount: 1_500 * 1e18, + limitIndex: 6_000, + pool: address(_pool) + }); + + // call update exchange rate + _updateExchangeRates({ + updater: _updater2, + pool: address(_pool), + indexes: depositIndexes, + reward: 11.615849155266905358 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905358 * 1e18); + + rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(rewardsEarned, 493.039460320049744942 * 1e18); + + // claim all rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(5,0), + reward: 493.039460320049744942 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); + assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); + } + + function testMoveStakedLiquidity() external { + skip(10); + + /*****************/ + /*** Stake NFT ***/ + /*****************/ + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + // configure NFT position + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // stake nft + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + _updateExchangeRates({ + updater: _minterOne, + pool: address(_pool), + indexes: depositIndexes, + reward: 0 + }); + + + uint256[] memory secondIndexes = new uint256[](5); + secondIndexes[0] = 2556; + secondIndexes[1] = 2557; + secondIndexes[2] = 2558; + secondIndexes[3] = 2559; + secondIndexes[4] = 2560; + + _moveStakedLiquidity({ + from: _minterOne, + tokenId: tokenIdOne, + fromIndexes: depositIndexes, + fromIndStaked: false, + toIndexes: secondIndexes, + expiry: block.timestamp + 1000 + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // first reserve auction happens successfully -> epoch 1 + _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2560, + pool: address(_pool) + }); + + /***********************/ + /*** Move Staked NFT ***/ + /***********************/ + + // retrieve the position managers index set, recreating typical tx flow since positionIndexes are stored unordered in EnnumerableSets + secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); + + _moveStakedLiquidity({ + from: _minterOne, + tokenId: tokenIdOne, + fromIndexes: secondIndexes, + fromIndStaked: true, + toIndexes: depositIndexes, + expiry: block.timestamp + 1000 + }); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + + // second reserve auction happens successfully -> epoch 1 + _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 149.275382184037345529 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + /******************************/ + /*** Exchange Rates Updated ***/ + /******************************/ + + // retrieve the position managers index set, recreating typical tx flow since positionIndexes are stored unordered in EnnumerableSets + depositIndexes = _positionManager.getPositionIndexes(tokenIdOne); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 3.373279477974559345 * 1e18 + }); + + /*********************/ + /*** Claim Rewards ***/ + /*********************/ + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,1), + reward: 33.732794779745615314 * 1e18 + }); + } + + function testEarlyAndLateStakerRewards() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + // configure NFT position two + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // bucket exchange rates are not changed at the time minter two stakes + assertEq(_pool.bucketExchangeRate(2550), 1e18); + assertEq(_pool.bucketExchangeRate(2551), 1e18); + assertEq(_pool.bucketExchangeRate(2552), 1e18); + assertEq(_pool.bucketExchangeRate(2553), 1e18); + assertEq(_pool.bucketExchangeRate(2555), 1e18); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + + (uint256 collateralToPledge) = _createTestBorrower(address(_pool), _borrower2, 10_000 * 1e18, 2770); + + // borrower borrows and change the exchange rates of buckets + _drawDebt({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 5 * 1e18, + limitIndex: 2770, + collateralToPledge: collateralToPledge, + newLup: 3_010.892022197881557845 * 1e18 + }); + + skip(1 days); + + // configure NFT position three one day after early minter + uint256 tokenIdThree = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // bucket exchange rates are higher at the time minter three stakes + assertEq(_pool.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); + assertEq(_pool.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterThree, + tokenId: tokenIdThree + }); + + skip(1 days); + + _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 133.010293986888191760 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + // unstake and compare rewards and balances of minter two and minter three + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(1, 0), + reward: 39.906015449635223195 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 6.651002176006408713 * 1e18 + }); + + uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); + assertEq(minterTwoBalance, 39.906015449635223195 * 1e18); + _unstakeToken({ + owner: _minterThree, + pool: address(_pool), + tokenId: tokenIdThree, + claimedArray: _epochsClaimedArray(1, 0), + reward: 33.250133719815196731 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); + assertEq(minterThreeBalance, 33.250133719815196731 * 1e18); + + assertGt(minterTwoBalance, minterThreeBalance); + } + + // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves + function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { + skip(10); + + /*****************************/ + /*** First Lender Deposits ***/ + /*****************************/ + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + // mint memorialize and deposit NFT + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // stake NFT + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + /*****************************/ + /*** First Reserve Auction ***/ + /*****************************/ + + // borrower takes actions providing reserves enabling reserve auctions + uint256 firstTokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + /******************************/ + /*** Second Lender Deposits ***/ + /******************************/ + + // second depositor deposits an NFT representing the same positions into the rewards contract + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterTwo, + mintAmount: 1000 * 1e18, + pool: address(_pool) + }); + + // second depositor stakes NFT, generating an update reward + _stakeToken({ + pool: address(_pool), + owner: _minterTwo, + tokenId: tokenIdTwo + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.154797961459423073 * 1e18); + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtOne, firstTokensToBurn); + assertGt(idOneRewardsAtOne, 1); + + // minter one claims rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,0), + reward: idOneRewardsAtOne + }); + + /******************************/ + /*** Second Reserve Auction ***/ + /******************************/ + // // borrower takes actions providing reserves enabling additional reserve auctions + // conduct second reserve auction + uint256 secondTokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 176.189760225502880454 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + /*****************************/ + /*** Third Lender Deposits ***/ + /*****************************/ + + // third depositor deposits an NFT representing the same positions into the rewards contract + uint256 tokenIdThree = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterThree, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken({ + pool: address(_pool), + owner: _minterThree, + tokenId: tokenIdThree + }); + + /***********************/ + /*** Rewards Claimed ***/ + /***********************/ + + // calculate rewards earned since exchange rates have been updated + uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo, secondTokensToBurn); + assertGt(idOneRewardsAtTwo, 0); + assertEq(idOneRewardsAtTwo, 23.615632136163281165 * 1e18); + + uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); + assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, secondTokensToBurn); + assertEq(idTwoRewardsAtTwo, 23.583081554200477722 * 1e18); + assertGt(idTwoRewardsAtTwo, 0); + + // minter one claims rewards accrued after second auction + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + epochsClaimed: _epochsClaimedArray(1,1), + reward: 23.615632136163281165 * 1e18 + }); + + assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); + + // minter two claims rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterTwo, + tokenId: tokenIdTwo, + epochsClaimed: _epochsClaimedArray(1,1), + reward: idTwoRewardsAtTwo + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 31.737879515659900795 * 1e18); + + // check there are no remaining rewards available after claiming + uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + + remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _pool.currentBurnEpoch()); + assertEq(remainingRewards, 0); + } + + function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { + // configure _minterOne's NFT position + uint256[] memory firstIndexes = new uint256[](5); + firstIndexes[0] = 2550; + firstIndexes[1] = 2551; + firstIndexes[2] = 2552; + firstIndexes[3] = 2553; + firstIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: firstIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure _minterTwo's NFT position + uint256[] memory secondIndexes = new uint256[](5); + secondIndexes[0] = 2550; + secondIndexes[1] = 2551; + secondIndexes[2] = 2200; + secondIndexes[3] = 2221; + secondIndexes[4] = 2222; + + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: secondIndexes, + minter: _minterTwo, + mintAmount: 5_000 * 1e18, + pool: address(_pool) + }); + + // lenders stake their NFTs + _stakeToken(address(_pool), _minterOne, tokenIdOne); + _stakeToken(address(_pool), _minterTwo, tokenIdTwo); + + uint256[] memory depositIndexes = new uint256[](8); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + depositIndexes[5] = 2200; + depositIndexes[6] = 2221; + depositIndexes[7] = 2222; + + // borrower takes actions providing reserves enabling three reserve auctions + // proof of burn events (burn epoch 0) + _assertBurn({ + pool: address(_pool), + epoch: 0, + timestamp: 0, + burned: 0, + interest: 0, + tokensToBurn: 0 + }); + + // auction one + uint256 tokensToBurnE1 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162663471460 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133195708 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 1, + timestamp: block.timestamp - 24 hours, + burned: 81.799378162663471460 * 1e18, + tokensToBurn: tokensToBurnE1, + interest: 6.443638300196908069 * 1e18 + }); + + // auction two + uint256 tokensToBurnE2 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 308.672122067616182565 * 1e18, + borrowAmount: 1_000 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 11.343637195247653990 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 2, + timestamp: block.timestamp - 24 hours, + burned: 308.672122067616182565 * 1e18, + tokensToBurn: tokensToBurnE2, + interest: 23.936044239893182713 * 1e18 + }); + + // auction three + uint256 tokensToBurnE3 = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 676.658832743043390869 * 1e18, + borrowAmount: 2_000 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 18.399335533771072810 * 1e18 + }); + + _assertBurn({ + pool: address(_pool), + epoch: 3, + timestamp: block.timestamp - 24 hours, + burned: 676.658832743043390869 * 1e18, + tokensToBurn: tokensToBurnE3, + interest: 52.421031459480795903 * 1e18 + }); + + // both stakers claim rewards + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(3, 0), + reward: 51.511621814050026070 * 1e18, + indexes: firstIndexes, + updateExchangeRatesReward: 0 + }); + + _unstakeToken({ + owner: _minterTwo, + pool: address(_pool), + tokenId: tokenIdTwo, + claimedArray: _epochsClaimedArray(3, 0), + reward: 286.817794557471160695 * 1e18, + indexes: secondIndexes, + updateExchangeRatesReward: 0 + }); + } + + + function testWithdrawAndClaimRewards() external { + skip(10); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 2550; + depositIndexes[1] = 2551; + depositIndexes[2] = 2552; + depositIndexes[3] = 2553; + depositIndexes[4] = 2555; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // stake nft + _stakeToken({ + pool: address(_pool), + owner: _minterOne, + tokenId: tokenIdOne + }); + + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 2555, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _updater, + pool: address(_pool), + indexes: depositIndexes, + reward: 4.089968908133134138 * 1e18 + }); + + // check owner can withdraw the NFT and rewards will be automatically claimed + + uint256 snapshot = vm.snapshot(); + + // claimed rewards amount is greater than available tokens in rewards manager contract + + // burn rewards manager tokens and leave only 5 tokens available + changePrank(address(_rewardsManager)); + IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); + + uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); + assertEq(managerBalance, 4.931444746461913452 * 1e18); + + // _minterOne unstakes staked position + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 40.899689081331351737 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + + // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 + assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); + // all 5 tokens available in manager balance were used to reward minter one + assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); + + vm.revertTo(snapshot); + + // test when enough tokens in rewards manager contracts + // _minterOne unstakes staked position + _unstakeToken({ + owner: _minterOne, + pool: address(_pool), + tokenId: tokenIdOne, + claimedArray: _epochsClaimedArray(1, 0), + reward: 40.899689081331351737 * 1e18, + indexes: depositIndexes, + updateExchangeRatesReward: 0 + }); + + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenIdOne), _minterOne); + assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331351737 * 1e18); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + + // check can't claim rewards twice + _assertNotOwnerOfDepositRevert({ + from: _minterOne, + tokenId: tokenIdOne + }); + } + + function testMultiplePools() external { + skip(10); + + // configure NFT position one + uint256[] memory firstIndexes = new uint256[](5); + firstIndexes[0] = 9; + firstIndexes[1] = 1; + firstIndexes[2] = 2; + firstIndexes[3] = 3; + firstIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: firstIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // configure NFT position two + uint256[] memory secondIndexes = new uint256[](4); + secondIndexes[0] = 5; + secondIndexes[1] = 1; + secondIndexes[2] = 3; + secondIndexes[3] = 12; + + uint256 tokenIdTwo = _mintAndMemorializePositionNFT({ + indexes: secondIndexes, + minter: _minterTwo, + mintAmount: 1_000 * 1e18, + pool: address(_poolTwo) + }); + + // minterOne deposits their NFT into the rewards contract + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + // minterTwo deposits their NFT into the rewards contract + _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); + + // borrower takes actions providing reserves enabling reserve auctions + // bidder takes reserve auctions by providing ajna tokens to be burned + uint256 tokensToBurn = _triggerReserveAuctions({ + borrower: _borrower, + tokensToBurn: 81.799378162662704349 * 1e18, + borrowAmount: 300 * 1e18, + limitIndex: 3, + pool: address(_pool) + }); + + // check only deposit owner can claim rewards + _assertNotOwnerOfDepositRevert({ + from: _minterTwo, + tokenId: tokenIdOne + }); + + // check rewards earned in one pool shouldn't be claimable by depositors from another pool + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + _claimRewards({ + pool: address(_poolTwo), + from: _minterTwo, + tokenId: tokenIdTwo, + reward: 0, + epochsClaimed: _epochsClaimedArray(0, 0) + }); + assertEq(_ajnaToken.balanceOf(_minterTwo), 0); + + // call update exchange rate to enable claiming rewards + _updateExchangeRates({ + updater: _minterOne, + pool: address(_pool), + indexes: firstIndexes, + reward: 4.089968908133134138 * 1e18 + }); + assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133134138 * 1e18); + + // check owner in pool with accrued interest can properly claim rewards + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: 40.899689081331351737 * 1e18, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); + + } + + /********************/ + /*** FUZZ TESTING ***/ + /********************/ + + function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { + indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to + mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](indexes); + for (uint256 i = 0; i < indexes; ++i) { + depositIndexes[i] = _randomIndex(); + } + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: mintAmount, + pool: address(_pool) + }); + + // stake NFT + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + // calculates a limit index leaving one index above the htp to accrue interest + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + + // start and end new reserve auction + uint256 tokensToBurn= _triggerReserveAuctionsBurnUnknown({ + borrower: _borrower, + borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), + limitIndex: limitIndex, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), 0); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + assertGt(_ajnaToken.balanceOf(_updater), 0); + + // calculate rewards earned and compare to percentages for updating and claiming + uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); + assertGt(rewardsEarned, 0); + + // claim rewards accrued since deposit + _claimRewards({ + pool: address(_pool), + from: _minterOne, + tokenId: tokenIdOne, + reward: rewardsEarned, + epochsClaimed: _epochsClaimedArray(1, 0) + }); + + // assert rewards claimed is less than ajna tokens burned cap + assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); + } + + function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { + deposits = bound(deposits, 1, 25); // number of deposits to make + reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete + + uint256[] memory tokenIds = new uint256[](deposits); + + // configure NFT position + uint256[] memory depositIndexes = new uint256[](3); + for (uint256 j = 0; j < 3; ++j) { + depositIndexes[j] = _randomIndex(); + vm.roll(block.number + 1); // advance block to ensure that the index price is different + } + + address[] memory minters = _getAddresses(deposits); + + // stake variable no of deposits + for(uint256 i = 0; i < deposits; ++i) { + + tokenIds[i] = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: minters[i], + mintAmount: 1_000_000_000 * 1e18, + pool: address(_pool) + }); + tokenIdToMinter[tokenIds[i]] = minters[i]; + _stakeToken(address(_pool), minters[i], tokenIds[i]); + } + + uint256 updaterBalance = _ajnaToken.balanceOf(_updater); + + for(uint i = 0; i < deposits; i++) { + minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); + } + + // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch + for(uint i = 0; i < reserveAuctions; ++i) { + uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); + + // start and end new reserve auction + uint256 tokensBurned = _triggerReserveAuctionsBurnUnknown({ + borrower: _borrower, + borrowAmount: 10_000 * 1e18, + limitIndex: limitIndex, + pool: address(_pool) + }); + + // call update exchange rate to enable claiming rewards + assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); + + changePrank(_updater); + assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); + _rewardsManager.updateBucketExchangeRatesAndClaim(address(_pool), depositIndexes); + + // ensure updater gets reward for updating exchange rate + assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); + + // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned + assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); + + updaterBalance = _ajnaToken.balanceOf(_updater); + + // pick random NFTs from all NFTs to claim rewards + uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); + + for(uint j = 0; j < randomNfts.length; j++) { + address minterAddress = tokenIdToMinter[randomNfts[j]]; + changePrank(minterAddress); + + (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); + + // select random epoch to claim reward + uint256 epochToClaim = lastInteractionEpoch < _pool.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _pool.currentBurnEpoch()) : lastInteractionEpoch; + + uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); + assertGt(rewardsEarned, 0); + + _rewardsManager.claimRewards(randomNfts[j], _pool.currentBurnEpoch()); + + // ensure user gets reward + assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); + minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); + } + } + } + + function testClaimRewardsFreezeUnclaimedYield() external { + skip(10); + + uint256[] memory depositIndexes = new uint256[](5); + depositIndexes[0] = 9; + depositIndexes[1] = 1; + depositIndexes[2] = 2; + depositIndexes[3] = 3; + depositIndexes[4] = 4; + + uint256 tokenIdOne = _mintAndMemorializePositionNFT({ + indexes: depositIndexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + _stakeToken(address(_pool), _minterOne, tokenIdOne); + + uint256 currentBurnEpoch = _pool.currentBurnEpoch(); + + changePrank(_minterOne); + // should revert if the epoch to claim is not available yet + vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); + + // user should be able to claim rewards for current epoch + _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); + } + +} diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol deleted file mode 100644 index 690079a30..000000000 --- a/tests/forge/RewardsManager.t.sol +++ /dev/null @@ -1,2048 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.14; - -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; - -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; - -import 'src/RewardsManager.sol'; -import 'src/interfaces/rewards/IRewardsManager.sol'; - -import 'src/interfaces/position/IPositionManager.sol'; -import 'src/PositionManager.sol'; -import 'src/PoolInfoUtils.sol'; -import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; - -import { _borrowFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; - -import { Token } from './utils/Tokens.sol'; -import { ERC20HelperContract } from './ERC20Pool/ERC20DSTestPlus.sol'; - -contract RewardsManagerTest is ERC20HelperContract { - - address internal _bidder; - address internal _minterOne; - address internal _minterTwo; - address internal _minterThree; - address internal _minterFour; - address internal _minterFive; - address internal _updater; - address internal _updater2; - - ERC20 internal _ajnaToken; - - RewardsManager internal _rewardsManager; - PositionManager internal _positionManager; - - Token internal _collateralOne; - Token internal _quoteOne; - ERC20Pool internal _poolOne; - Token internal _collateralTwo; - Token internal _quoteTwo; - ERC20Pool internal _poolTwo; - - event ClaimRewards(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId, uint256[] epochsClaimed, uint256 amount); - event Stake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); - event UpdateExchangeRates(address indexed caller, address indexed ajnaPool, uint256[] indexesUpdated, uint256 rewardsClaimed); - event Unstake(address indexed owner, address indexed ajnaPool, uint256 indexed tokenId); - event MoveStakedLiquidity( - uint256 tokenId, - uint256[] fromIndexes, - uint256[] toIndexes - ); - - uint256 constant BLOCKS_IN_DAY = 7200; - mapping (uint256 => address) internal tokenIdToMinter; - mapping (address => uint256) internal minterToBalance; - - struct MintAndMemorializeParams { - uint256[] indexes; - address minter; - uint256 mintAmount; - ERC20Pool pool; - } - - struct TriggerReserveAuctionParams { - uint256 borrowAmount; - uint256 limitIndex; - ERC20Pool pool; - } - - function setUp() external { - vm.makePersistent(_ajna); - - _ajnaToken = ERC20(_ajna); - _positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); - _rewardsManager = new RewardsManager(_ajna, _positionManager); - _poolUtils = new PoolInfoUtils(); - - _collateralOne = new Token("Collateral 1", "C1"); - _quoteOne = new Token("Quote 1", "Q1"); - _poolOne = ERC20Pool(_poolFactory.deployPool(address(_collateralOne), address(_quoteOne), 0.05 * 10**18)); - - _collateralTwo = new Token("Collateral 2", "C2"); - _quoteTwo = new Token("Quote 2", "Q2"); - _poolTwo = ERC20Pool(_poolFactory.deployPool(address(_collateralTwo), address(_quoteTwo), 0.05 * 10**18)); - - // provide initial ajna tokens to staking rewards contract - deal(_ajna, address(_rewardsManager), 100_000_000 * 1e18); - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 100_000_000 * 1e18); - - // instantiate test minters - _minterOne = makeAddr("minterOne"); - _minterTwo = makeAddr("minterTwo"); - _minterThree = makeAddr("minterThree"); - _minterFour = makeAddr("minterFour"); - _minterFive = makeAddr("minterFive"); - - // instantiate test bidder - _bidder = makeAddr("bidder"); - changePrank(_bidder); - deal(_ajna, _bidder, 900_000_000 * 10**18); - - // instantiate test updater - _updater = makeAddr("updater"); - _updater2 = makeAddr("updater2"); - } - - // create a new test borrower with quote and collateral sufficient to draw a specified amount of debt - function _createTestBorrower(ERC20Pool pool_, string memory borrowerName_, uint256 borrowAmount_, uint256 limitIndex_) internal returns (address borrower_, uint256 collateralToPledge_) { - borrower_ = makeAddr(borrowerName_); - - changePrank(borrower_); - - Token collateral = Token(pool_.collateralAddress()); - Token quote = Token(pool_.quoteTokenAddress()); - - // deal twice as much quote so the borrower has sufficient quote to repay the loan - deal(address(quote), borrower_, Maths.wmul(borrowAmount_, Maths.wad(2))); - - // approve tokens - collateral.approve(address(pool_), type(uint256).max); - quote.approve(address(pool_), type(uint256).max); - - collateralToPledge_ = _requiredCollateral(pool_, borrowAmount_, limitIndex_); - deal(address(collateral), borrower_, collateralToPledge_); - } - - function _stakeToken(address pool_, address owner_, uint256 tokenId_) internal { - changePrank(owner_); - - // approve and deposit NFT into rewards contract - _positionManager.approve(address(_rewardsManager), tokenId_); - vm.expectEmit(true, true, true, true); - emit Stake(owner_, address(pool_), tokenId_); - _rewardsManager.stake(tokenId_); - - // check token was transferred to rewards contract - assertEq(_positionManager.ownerOf(tokenId_), address(_rewardsManager)); - } - - function _unstakeToken( - address minter, - address pool, - uint256[] memory claimedArray, - uint256 tokenId, - uint256 reward, - uint256 updateRatesReward - ) internal { - - changePrank(minter); - - if (updateRatesReward != 0) { - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), _positionManager.getPositionIndexes(tokenId), updateRatesReward); - } - - vm.expectEmit(true, true, true, true); - emit ClaimRewards(minter, pool, tokenId, claimedArray, reward); - vm.expectEmit(true, true, true, true); - emit Unstake(minter, address(pool), tokenId); - _rewardsManager.unstake(tokenId); - assertEq(_positionManager.ownerOf(tokenId), minter); - - // check token was transferred from rewards contract to minter - assertEq(_positionManager.ownerOf(tokenId), address(minter)); - - // invariant: all bucket snapshots are removed for the token id that was unstaken - for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { - (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); - assertEq(lps, 0); - assertEq(rate, 0); - } - } - - function _triggerReserveAuctionsNoTake(TriggerReserveAuctionParams memory params_) internal { - // create a new borrower to write state required for reserve auctions - ( - address borrower, - uint256 collateralToPledge - ) = _createTestBorrower(params_.pool, string("borrower"), params_.borrowAmount, params_.limitIndex); - - // borrower drawsDebt from the pool - params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, Maths.wdiv(params_.borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(params_.pool), type(uint256).max); - params_.pool.startClaimableReserveAuction(); - } - - function _assertBurn( - address pool, - uint256 epoch, - uint256 timestamp, - uint256 interest, - uint256 burned - ) internal { - - (uint256 bETimestamp, uint256 bEInterest, uint256 bEBurned) = IPool(pool).burnInfo(epoch); - - assertEq(bETimestamp, timestamp); - assertEq(bEInterest, interest); - assertEq(bEBurned, burned); - } - - - function _updateExchangeRates(address updater, address pool, uint256[] memory depositIndexes, uint256 reward) internal { - uint256 initialUpdaterTokenBalance = _ajnaToken.balanceOf(updater); - - changePrank(updater); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(updater, pool, depositIndexes, reward); - _rewardsManager.updateBucketExchangeRatesAndClaim(pool, depositIndexes); - assertEq(_ajnaToken.balanceOf(updater), initialUpdaterTokenBalance + reward); - } - - - function _epochsClaimedArray(uint256 numberOfAuctions_, uint256 lastClaimed_) internal pure returns (uint256[] memory epochsClaimed_) { - epochsClaimed_ = new uint256[](numberOfAuctions_); - uint256 claimEpoch = lastClaimed_; // starting index, not inclusive - - for (uint256 i = 0; i < numberOfAuctions_; i++) { - epochsClaimed_[i] = claimEpoch + 1; - claimEpoch += 1; - } - } - - function _mintAndMemorializePositionNFT(MintAndMemorializeParams memory params_) internal returns (uint256 tokenId_) { - changePrank(params_.minter); - - Token collateral = Token(params_.pool.collateralAddress()); - Token quote = Token(params_.pool.quoteTokenAddress()); - - // deal tokens to the minter - deal(address(collateral), params_.minter, 250_000 * 1e18); - deal(address(quote), params_.minter, params_.mintAmount * params_.indexes.length); - - // approve tokens - collateral.approve(address(params_.pool), type(uint256).max); - quote.approve(address(params_.pool), type(uint256).max); - - IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams(params_.minter, address(params_.pool), keccak256("ERC20_NON_SUBSET_HASH")); - tokenId_ = _positionManager.mint(mintParams); - - uint256[] memory lpBalances = new uint256[](params_.indexes.length); - - for (uint256 i = 0; i < params_.indexes.length; i++) { - params_.pool.addQuoteToken(params_.mintAmount, params_.indexes[i], type(uint256).max); - (lpBalances[i], ) = params_.pool.lenderInfo(params_.indexes[i], params_.minter); - } - - params_.pool.increaseLPsAllowance(address(_positionManager), params_.indexes, lpBalances); - - // construct memorialize params struct - IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( - tokenId_, params_.indexes - ); - - _positionManager.memorializePositions(memorializeParams); - - // register position manager as lender at memorialized indexes (for LP test assertions) - _registerLender(address(_positionManager), params_.indexes); - } - - function _triggerReserveAuctions(TriggerReserveAuctionParams memory params_) internal returns (uint256 tokensBurned_) { - // create a new borrower to write state required for reserve auctions - address borrower = makeAddr("borrower"); - - changePrank(borrower); - - Token collateral = Token(params_.pool.collateralAddress()); - Token quote = Token(params_.pool.quoteTokenAddress()); - - deal(address(quote), borrower, params_.borrowAmount); - - // approve tokens - collateral.approve(address(params_.pool), type(uint256).max); - quote.approve(address(params_.pool), type(uint256).max); - - uint256 collateralToPledge = _requiredCollateral(params_.pool, params_.borrowAmount, params_.limitIndex); - deal(address(collateral), borrower, collateralToPledge); - - // borrower drawsDebt from the pool - params_.pool.drawDebt(borrower, params_.borrowAmount, params_.limitIndex, collateralToPledge); - - // allow time to pass for interest to accumulate - skip(26 weeks); - - // borrower repays some of their debt, providing reserves to be claimed - // don't pull any collateral, as such functionality is unrelated to reserve auctions - params_.pool.repayDebt(borrower, params_.borrowAmount, 0, borrower, MAX_FENWICK_INDEX); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(params_.pool), type(uint256).max); - params_.pool.startClaimableReserveAuction(); - - // Can't trigger reserve auction if less than two weeks have passed since last auction - vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - params_.pool.startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(params_.pool)); - - // take claimable reserves - params_.pool.takeReserves(curClaimableReservesRemaining); - - (,, tokensBurned_) = IPool(params_.pool).burnInfo(IPool(params_.pool).currentBurnEpoch()); - - return tokensBurned_; - } - - function testStakeToken() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // configure NFT position two - depositIndexes = new uint256[](4); - depositIndexes[0] = 5; - depositIndexes[1] = 1; - depositIndexes[2] = 3; - depositIndexes[3] = 12; - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolTwo - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // check only owner of an NFT can deposit it into the rewards contract - changePrank(_minterTwo); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.stake(tokenIdOne); - - // minterOne deposits their NFT into the rewards contract - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - // check deposit state - (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, _minterOne); - assertEq(pool, address(_poolOne)); - assertEq(interactionBurnEvent, 0); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); - // check deposit state - (owner, pool, interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdTwo); - assertEq(owner, _minterTwo); - assertEq(pool, address(_poolTwo)); - assertEq(interactionBurnEvent, 0); - } - - function testUpdateExchangeRatesAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 4.089968908133149070 * 1e18); - - // check only deposit owner can claim rewards - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - - // check rewards earned - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, currentBurnEpoch); - assertEq(rewardsEarned, 40.899689081331421207 * 1e18); - - // claim rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - - // check can't claim rewards twice - vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - - // check deposit state - (address owner, address pool, uint256 interactionBurnEvent) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, _minterOne); - assertEq(pool, address(_poolOne)); - assertEq(interactionBurnEvent, 1); - assertEq(_positionManager.ownerOf(tokenIdOne), address(_rewardsManager)); - - // assert rewards claimed is less than ajna tokens burned cap - assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); - - // check can't call update exchange rate after the update period has elapsed - skip(2 weeks); - - // Although an `UpdateExchangeRates` is emmited the rates are not updated, as demonstrated by updateRewards == 0 - uint256 updateRewards = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(updateRewards, 0); - } - - function testWithdrawAndClaimRewardsNoExchangeRateUpdate() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // epoch 0 - 1 is checked for rewards - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - // first reserve auction happens successfully -> epoch 1 - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133149070 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 81.799378162662881374 * 1e18, - interest: 6.443638300196908069 * 1e18 - }); - - // second reserve auction happens successfully -> epoch 2 - tokensToBurn += _triggerReserveAuctions(triggerReserveAuctionParams); - - // check owner can withdraw the NFT and rewards will be automatically claimed - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(2, 0), - reward: 86.809555428378605148 * 1e18, - updateRatesReward: 4.173624213367915365 * 1e18 - }); - } - - function testWithdrawAndClaimRewardsNoReserveTake() external { - - // healthy epoch, bad epoch - - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // epoch 0 - 1 is checked for rewards - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - //call update exchange rate to enable claiming rewards for epoch 0 - 1 - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133149070 * 1e18 - }); - - skip(2 weeks); - - // first reserve auction happens successfully Staker should receive rewards epoch 0 - 1 - _triggerReserveAuctionsNoTake(triggerReserveAuctionParams); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - (2 weeks + 26 weeks + 24 hours), - burned: 81.799378162662881374 * 1e18, - interest: 6.443638300196908069 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 2, - timestamp: block.timestamp, - burned: 0, - interest: 0 - }); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.206490995172302285 * 1e18 - }); - } - - // two lenders stake their positions in the pool - // staker one bucket bankrupt, staker two bucket active - // interest accrued to both buckets, but staker one receives no rewards - function testClaimRewardsBankruptBucket() external { - - address borrower = makeAddr("borrower"); - address borrowerTwo = makeAddr("borrowerTwo"); - - deal(address(_collateral), borrower, 4 * 1e18); - changePrank(borrower); - _collateral.approve(address(_pool), type(uint256).max); - _quote.approve(address(_pool), type(uint256).max); - - deal(address(_collateral), borrowerTwo, 1_000 * 1e18); - changePrank(borrowerTwo); - _collateral.approve(address(_pool), type(uint256).max); - _quote.approve(address(_pool), type(uint256).max); - - address[] memory transferors = new address[](1); - transferors[0] = address(_positionManager); - - changePrank(_minterOne); - deal(address(_quote), _minterOne, 500_000_000 * 1e18); - _quote.approve(address(_pool), type(uint256).max); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - changePrank(_minterTwo); - deal(address(_quote), _minterTwo, 500_000_000 * 1e18); - _quote.approve(address(_pool), type(uint256).max); - _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); - - /*****************************/ - /*** Initialize Pool State ***/ - /*****************************/ - - // Lender adds Quote token accross 5 prices - _addInitialLiquidity({ - from: _minterOne, - amount: 2_000 * 1e18, - index: _i9_91 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 5_000 * 1e18, - index: _i9_81 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 11_000 * 1e18, - index: _i9_72 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 25_000 * 1e18, - index: _i9_62 - }); - _addInitialLiquidity({ - from: _minterOne, - amount: 30_000 * 1e18, - index: _i9_52 - }); - - // first borrower adds collateral token and borrows - _pledgeCollateral({ - from: borrower, - borrower: borrower, - amount: 2 * 1e18 - }); - _borrow({ - from: borrower, - amount: 19.25 * 1e18, - indexLimit: _i9_91, - newLup: 9.917184843435912074 * 1e18 - }); - - // second borrower adds collateral token and borrows - _pledgeCollateral({ - from: borrowerTwo, - borrower: borrowerTwo, - amount: 1_000 * 1e18 - }); - _borrow({ - from: borrowerTwo, - amount: 7_980 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - }); - - _borrow({ - from: borrowerTwo, - amount: 1_730 * 1e18, - indexLimit: _i9_72, - newLup: 9.721295865031779605 * 1e18 - }); - - /*****************************/ - /*** Lenders Deposits NFTs ***/ - /*****************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](1); - uint256[] memory depositIndexes2 = new uint256[](1); - depositIndexes[0] = _i9_91; - depositIndexes2[0] = _i9_81; - - ERC20Pool pool = ERC20Pool(address(_pool)); - - // stake NFT position one - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 2_000 * 1e18, - pool: pool - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterOne); - _stakeToken(address(pool), _minterOne, tokenIdOne); - - // stake NFT position two - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes2, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: pool - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterTwo); - _stakeToken(address(pool), _minterTwo, tokenIdTwo); - - /***********************************/ - /*** Borrower Bankrupts A Bucket ***/ - /***********************************/ - - // Skip to make borrower two undercollateralized - skip(100 days); - - deal(address(_quote), _minterTwo, 500_000_000 * 1e18); - - _kick({ - from: _minterTwo, - borrower: borrowerTwo, - debt: 9_976.561670003961916237 * 1e18, - collateral: 1_000 * 1e18, - bond: 98.533942419792216457 * 1e18, - transferAmount: 98.533942419792216457 * 1e18 - }); - - // skip ahead so take can be called on the loan - skip(10 hours); - - // take entire collateral - _take({ - from: _minterTwo, - borrower: borrowerTwo, - maxCollateral: 1_000 * 1e18, - bondChange: 6.531114528261135360 * 1e18, - givenAmount: 653.111452826113536000 * 1e18, - collateralTaken: 1_000 * 1e18, - isReward: true - }); - - _settle({ - from: _minterTwo, - borrower: borrowerTwo, - maxDepth: 10, - settledDebt: 9_891.935520844277346922 * 1e18 - }); - - // bucket is insolvent, balances are reset - _assertBucket({ - index: _i9_91, - lpBalance: 0, // bucket is bankrupt - collateral: 0, - deposit: 0, - exchangeRate: 1 * 1e18 - }); - - // lower priced bucket isn't bankrupt, but exchange rate has decreased - _assertBucket({ - index: _i9_81, - lpBalance: 10_000 * 1e18, - collateral: 0, - deposit: 4_936.865619773958005817 * 1e18, - exchangeRate: 0.493686561977395801 * 1e18 - }); - - /***********************/ - /*** Reserve Auction ***/ - /***********************/ - - // skip some time to accumulate reserves - skip(1000 days); - - // update pool reserves - _pool.updateInterest(); - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(_pool), type(uint256).max); - _pool.startClaimableReserveAuction(); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(_pool)); - - // take claimable reserves - changePrank(_bidder); - _pool.takeReserves(curClaimableReservesRemaining); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - // _minterOne withdraws and claims rewards, rewards should be 0 - _unstakeToken({ - minter: _minterOne, - pool: address(_pool), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - - // _minterTwo withdraws and claims rewards, rewards should be 0 as their bucket exchange rate decreased - _unstakeToken({ - minter: _minterTwo, - pool: address(_pool), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0, - updateRatesReward: 0 - }); - } - - function testClaimRewardsCap() external { - skip(10); - - /***************************/ - /*** Lender Deposits NFT ***/ - /***************************/ - - // set deposit indexes - uint256[] memory depositIndexes = new uint256[](2); - uint256[] memory depositIndex1 = new uint256[](1); - uint256[] memory depositIndex2 = new uint256[](1); - depositIndexes[0] = 2770; - depositIndexes[1] = 2771; - depositIndex1[0] = 2771; - depositIndex2[0] = 2770; - - // configure NFT position one - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 10_000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - changePrank(_minterOne); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /************************************/ - /*** Borrower One Accrue Interest ***/ - /************************************/ - - // borrower1 borrows - (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); - changePrank(borrower1); - - _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); - - // pass time to allow interest to accrue - skip(2 hours); - - // borrower1 repays their loan - (uint256 debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // start reserve auction - changePrank(_bidder); - _ajnaToken.approve(address(_poolOne), type(uint256).max); - _poolOne.startClaimableReserveAuction(); - - // borrower1 now takes out more debt to accumulate more interest - changePrank(borrower1); - _poolOne.drawDebt(borrower1, 2_000 * 1e18, 2770, 0); - - // allow time to pass for the reserve price to decrease - skip(24 hours); - - ( - , - , - uint256 curClaimableReservesRemaining, - , - ) = _poolUtils.poolReservesInfo(address(_poolOne)); - - // take claimable reserves - changePrank(_bidder); - _poolOne.takeReserves(curClaimableReservesRemaining); - - // recorder updates the change in exchange rates in the first index - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndex1, - reward: 0.007104600671645296 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater), .007104600671645296 * 1e18); - - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - 24 hours, - burned: 0.284184026893324971 * 1e18, - interest: 0.000048562908902619 * 1e18 - }); - - // skip more time to allow more interest to accrue - skip(10 days); - - // borrower1 repays their loan again - changePrank(borrower1); - (debt, , ) = _poolOne.borrowerInfo(borrower1); - _poolOne.repayDebt(borrower1, debt, 0, borrower1, MAX_FENWICK_INDEX); - - // recorder updates the change in exchange rates in the second index - _updateExchangeRates({ - updater: _updater2, - pool: address(_poolOne), - depositIndexes: depositIndex2, - reward: 0.021313802017687201 * 1e18 - }); - assertEq(_ajnaToken.balanceOf(_updater2), .021313802017687201 * 1e18); - - /*******************************************/ - /*** Lender Withdraws And Claims Rewards ***/ - /*******************************************/ - - // _minterOne withdraws and claims rewards, rewards should be set to the difference between total claimed and cap - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(1, 0), - reward: 0.227347221514659977 * 1e18, - updateRatesReward: 0 - }); - } - - function testMultiPeriodRewardsSingleClaim() external { - skip(10); - - uint256 totalTokensBurned; - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](10); - depositIndexes[0] = 5995; - depositIndexes[1] = 5996; - depositIndexes[2] = 5997; - depositIndexes[3] = 5998; - depositIndexes[4] = 5999; - depositIndexes[5] = 6000; - depositIndexes[6] = 6001; - depositIndexes[7] = 6002; - depositIndexes[8] = 6003; - depositIndexes[9] = 6004; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 20.449844540665688882 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); - - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 204.498445406656758711 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - // trigger second reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 20.449844540665688882 * 1e18); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 17.238252336072314418 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876738003300 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - /*****************************/ - /*** Third Reserve Auction ***/ - /*****************************/ - - // trigger third reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // skip updating exchange rates and check available rewards - uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380567488 * 1e18); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // snapshot calling update exchange rate - uint256 snapshot = vm.snapshot(); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 14.019164349973606338 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606338 * 1e18); - - // check available rewards - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 517.072612267116269218 * 1e18); - assertGt(rewardsEarned, rewardsEarnedNoUpdate); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - - // revert to no update state - vm.revertTo(snapshot); - - /******************************/ - /*** Fourth Reserve Auction ***/ - /******************************/ - - // triger fourth reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // check rewards earned - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 0); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - - // check rewards earned won't increase since previous update was missed - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); - - /*****************************/ - /*** Fifth Reserve Auction ***/ - /*****************************/ - - // triger fourth reserve auction - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 1_500 * 1e18, - limitIndex: 6000, - pool: _poolOne - }); - totalTokensBurned += _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate - changePrank(_updater2); - assertEq(_ajnaToken.balanceOf(_updater2), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater2, address(_poolOne), depositIndexes, 11.615849155266905358 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905358 * 1e18); - - rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049744942 * 1e18); - - // claim all rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(5, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); - } - - function testMoveStakedLiquidity() external { - skip(10); - - /*****************/ - /*** Stake NFT ***/ - /*****************/ - - uint256[] memory firstIndexes = new uint256[](5); - firstIndexes[0] = 2550; - firstIndexes[1] = 2551; - firstIndexes[2] = 2552; - firstIndexes[3] = 2553; - firstIndexes[4] = 2555; - - // configure NFT position - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: firstIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenId = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // stake nft - _stakeToken(address(_poolOne), _minterOne, tokenId); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - uint256 expiry = block.timestamp + 1000; - uint256[] memory secondIndexes = new uint256[](5); - secondIndexes[0] = 2556; - secondIndexes[1] = 2557; - secondIndexes[2] = 2558; - secondIndexes[3] = 2559; - secondIndexes[4] = 2560; - - // check no rewards are claimed on first move - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(0, 0), 0); - - // check MoveLiquidity emits - for (uint256 i = 0; i < firstIndexes.length; ++i) { - vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, firstIndexes[i], secondIndexes[i]); - } - - vm.expectEmit(true, true, true, true); - emit MoveStakedLiquidity(tokenId, firstIndexes, secondIndexes); - _rewardsManager.moveStakedLiquidity(tokenId, firstIndexes, secondIndexes, expiry); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2560, - pool: _poolOne - }); - // first reserve auction happens successfully -> epoch 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - /***********************/ - /*** Move Staked NFT ***/ - /***********************/ - - expiry = block.timestamp + 1000; - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - secondIndexes = _positionManager.getPositionIndexes(tokenId); - - // check rewards are claimed from the indexes that the staker is moving away from - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), secondIndexes, 4.089968908133149070 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 0), 44.989657989464570277 * 1e18); - // check MoveLiquidity emits - for (uint256 i = 0; i < firstIndexes.length; ++i) { - vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, secondIndexes[i], firstIndexes[i]); - } - vm.expectEmit(true, true, true, true); - emit MoveStakedLiquidity(tokenId, secondIndexes, firstIndexes); - - // check exchange rates are updated - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), firstIndexes, 0); - - changePrank(_minterOne); - _rewardsManager.moveStakedLiquidity(tokenId, secondIndexes, firstIndexes, expiry); - - // check that no rewards are available yet in the indexes that the staker moved to - vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - // first reserve auction happens successfully -> epoch 1 - _triggerReserveAuctions(triggerReserveAuctionParams); - - currentBurnEpoch = _poolOne.currentBurnEpoch(); - - /******************************/ - /*** Exchange Rates Updated ***/ - /******************************/ - - // need to retrieve the position managers index set since positionIndexes are stored unordered in EnnumerableSets - firstIndexes = _positionManager.getPositionIndexes(tokenId); - - _updateExchangeRates(_updater, address(_poolOne), firstIndexes, 4.173045926578320550 * 1e18); - - /*********************/ - /*** Claim Rewards ***/ - /*********************/ - - // claim rewards accrued since second movement of lps - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570277 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenId, _epochsClaimedArray(1, 1), 41.730459265783249743 * 1e18); - _rewardsManager.claimRewards(tokenId, currentBurnEpoch); - } - - function testEarlyAndLateStakerRewards() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - - // configure NFT position two - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - // bucket exchange rates are not changed at the time minter two stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1e18); - assertEq(_poolOne.bucketExchangeRate(2551), 1e18); - assertEq(_poolOne.bucketExchangeRate(2552), 1e18); - assertEq(_poolOne.bucketExchangeRate(2553), 1e18); - assertEq(_poolOne.bucketExchangeRate(2555), 1e18); - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - - // borrower borrows and change the exchange rates of buckets - (address borrower1, uint256 collateralToPledge) = _createTestBorrower(_poolOne, string("borrower1"), 10_000 * 1e18, 2770); - changePrank(borrower1); - - _poolOne.drawDebt(borrower1, 5 * 1e18, 2770, collateralToPledge); - - skip(1 days); - - // configure NFT position three one day after early minter - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); - // bucket exchange rates are higher at the time minter three stakes - assertEq(_poolOne.bucketExchangeRate(2550), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2551), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2552), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2553), 1.000000116558299385 * 1e18); - assertEq(_poolOne.bucketExchangeRate(2555), 1.000000116558299385 * 1e18); - _stakeToken(address(_poolOne), _minterThree, tokenIdThree); - - skip(1 days); - - // trigger reserve auction and update rates - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - _triggerReserveAuctions(triggerReserveAuctionParams); - - // unstake and compare rewards and balances of minter two and minter three - _unstakeToken({ - minter: _minterTwo, - pool: address(_poolOne), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(1, 0), - reward: 39.908019526547891787 * 1e18, - updateRatesReward: 0 - }); - uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 39.908019526547891787 * 1e18); - _unstakeToken({ - minter: _minterThree, - pool: address(_poolOne), - tokenId: tokenIdThree, - claimedArray: _epochsClaimedArray(1, 0), - reward: 33.248129642902710514 * 1e18, - updateRatesReward: 0 - }); - uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 33.248129642902710514 * 1e18); - - assertGt(minterTwoBalance, minterThreeBalance); - } - - // Calling updateExchangeRates not needed since deposits will update the exchange rate themselves - function testClaimRewardsMultipleDepositsSameBucketsMultipleAuctions() external { - skip(10); - - /*****************************/ - /*** First Lender Deposits ***/ - /*****************************/ - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - /*****************************/ - /*** First Reserve Auction ***/ - /*****************************/ - - // borrower takes actions providing reserves enabling reserve auctions - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - uint256 auctionOneTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - /******************************/ - /*** Second Lender Deposits ***/ - /******************************/ - - // second depositor deposits an NFT representing the same positions into the rewards contract - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - // second depositor stakes NFT, generating an update reward - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtOne, auctionOneTokensToBurn); - assertGt(idOneRewardsAtOne, 0); - - // minter one claims rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), idOneRewardsAtOne); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); - - /******************************/ - /*** Second Reserve Auction ***/ - /******************************/ - - // borrower takes actions providing reserves enabling additional reserve auctions - triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - - // conduct second reserve auction - uint256 auctionTwoTokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - /*****************************/ - /*** Third Lender Deposits ***/ - /*****************************/ - - // third depositor deposits an NFT representing the same positions into the rewards contract - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterThree, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - uint256 tokenIdThree = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterThree, tokenIdThree); - - /***********************/ - /*** Rewards Claimed ***/ - /***********************/ - - // calculate rewards earned since exchange rates have been updated - uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo, auctionTwoTokensToBurn); - assertGt(idOneRewardsAtTwo, 0); - - uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, auctionTwoTokensToBurn); - assertGt(idTwoRewardsAtTwo, 0); - - // minter one claims rewards accrued after second auction - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 1), idOneRewardsAtTwo); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); - - // minter two claims rewards accrued since deposit - changePrank(_minterTwo); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.175422393077328665 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterTwo, address(_poolOne), tokenIdTwo, _epochsClaimedArray(1, 1), idTwoRewardsAtTwo); - _rewardsManager.claimRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterTwo), idTwoRewardsAtTwo + 8.175422393077328665 * 1e18); - - // check there are no remaining rewards available after claiming - uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdTwo, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - - remainingRewards = _rewardsManager.calculateRewards(tokenIdThree, _poolOne.currentBurnEpoch()); - assertEq(remainingRewards, 0); - } - - function testClaimRewardsMultipleDepositsDifferentBucketsMultipleAuctions() external { - // configure _minterOne's NFT position - uint256[] memory depositIndexesMinterOne = new uint256[](5); - depositIndexesMinterOne[0] = 2550; - depositIndexesMinterOne[1] = 2551; - depositIndexesMinterOne[2] = 2552; - depositIndexesMinterOne[3] = 2553; - depositIndexesMinterOne[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParamsMinterOne = MintAndMemorializeParams({ - indexes: depositIndexesMinterOne, - minter: _minterOne, - mintAmount: 1_000 * 1e18, - pool: _poolOne - }); - - // configure _minterTwo's NFT position - uint256[] memory depositIndexesMinterTwo = new uint256[](5); - depositIndexesMinterTwo[0] = 2550; - depositIndexesMinterTwo[1] = 2551; - depositIndexesMinterTwo[2] = 2200; - depositIndexesMinterTwo[3] = 2221; - depositIndexesMinterTwo[4] = 2222; - MintAndMemorializeParams memory mintMemorializeParamsMinterTwo = MintAndMemorializeParams({ - indexes: depositIndexesMinterTwo, - minter: _minterTwo, - mintAmount: 5_000 * 1e18, - pool: _poolOne - }); - - uint256[] memory depositIndexes = new uint256[](8); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - depositIndexes[5] = 2200; - depositIndexes[6] = 2221; - depositIndexes[7] = 2222; - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterOne); - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParamsMinterTwo); - - // lenders stake their NFTs - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - _stakeToken(address(_poolOne), _minterTwo, tokenIdTwo); - - // borrower takes actions providing reserves enabling three reserve auctions - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 4.089968908133202125 * 1e18 - }); - - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 1_000 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 13.717705175494265742 * 1e18 - }); - - _triggerReserveAuctions(TriggerReserveAuctionParams({ - borrowAmount: 2_000 * 1e18, - limitIndex: 2555, - pool: _poolOne - })); - - _updateExchangeRates({ - updater: _updater, - pool: address(_poolOne), - depositIndexes: depositIndexes, - reward: 27.568516982211953592 * 1e18 - }); - - // proof of burn events - _assertBurn({ - pool: address(_poolOne), - epoch: 0, - timestamp: 0, - burned: 0, - interest: 0 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 1, - timestamp: block.timestamp - (52 weeks + 72 hours), - interest: 6.443638300196908069 * 1e18, - burned: 81.799378162664356589 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 2, - timestamp: block.timestamp - (26 weeks + 48 hours), - burned: 356.153481672547831475 * 1e18, - interest: 28.092564949680668737 * 1e18 - }); - - _assertBurn({ - pool: address(_poolOne), - epoch: 3, - timestamp: block.timestamp - 24 hours, - burned: 907.523821316786357044 * 1e18, - interest: 71.814132054505950833 * 1e18 - }); - - // both stakers claim rewards - _unstakeToken({ - minter: _minterOne, - pool: address(_poolOne), - tokenId: tokenIdOne, - claimedArray: _epochsClaimedArray(3, 0), - reward: 75.626985109732100713 * 1e18, - updateRatesReward: 0 - }); - - _unstakeToken({ - minter: _minterTwo, - pool: address(_poolOne), - tokenId: tokenIdTwo, - claimedArray: _epochsClaimedArray(3, 0), - reward: 378.134925548660503562 * 1e18, - updateRatesReward: 0 - }); - } - - function testUnstakeToken() external { - skip(10); - - address nonOwner = makeAddr("nonOwner"); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - // mint memorialize and deposit NFT - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // only owner should be able to withdraw the NFT - changePrank(nonOwner); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.unstake(tokenIdOne); - - // check owner can withdraw the NFT - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - - // deposit information should have been deleted on withdrawal - (address owner, address pool, uint256 interactionBlock) = _rewardsManager.getStakeInfo(tokenIdOne); - assertEq(owner, address(0)); - assertEq(pool, address(0)); - assertEq(interactionBlock, 0); - } - - function testWithdrawAndClaimRewards() external { - skip(10); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 2550; - depositIndexes[1] = 2551; - depositIndexes[2] = 2552; - depositIndexes[3] = 2553; - depositIndexes[4] = 2555; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 2555, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_updater, address(_poolOne), depositIndexes, 4.089968908133149070 * 1e18); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertGt(_ajnaToken.balanceOf(_updater), 0); - - // check owner can withdraw the NFT and rewards will be automatically claimed - - uint256 snapshot = vm.snapshot(); - - // claimed rewards amount is greater than available tokens in rewards manager contract - - // burn rewards manager tokens and leave only 5 tokens available - changePrank(address(_rewardsManager)); - IERC20Token(address(_ajnaToken)).burn(99_999_990.978586345404952410 * 1e18); - - uint256 managerBalance = _ajnaToken.balanceOf(address(_rewardsManager)); - assertEq(managerBalance, 4.931444746461898520 * 1e18); - - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - - // minter one receives only the amount of 5 ajna tokens available in manager balance instead calculated rewards of 40.214136545950568150 - assertEq(_ajnaToken.balanceOf(_minterOne), managerBalance); - // all 5 tokens available in manager balance were used to reward minter one - assertEq(_ajnaToken.balanceOf(address(_rewardsManager)), 0); - - vm.revertTo(snapshot); - - // test when enough tokens in rewards manager contracts - changePrank(_minterOne); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); - vm.expectEmit(true, true, true, true); - emit Unstake(_minterOne, address(_poolOne), tokenIdOne); - _rewardsManager.unstake(tokenIdOne); - assertEq(_positionManager.ownerOf(tokenIdOne), _minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 40.899689081331421207 * 1e18); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - // check can't claim rewards twice - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - } - - function testMultiplePools() external { - skip(10); - - // configure NFT position one - uint256[] memory depositIndexesOne = new uint256[](5); - depositIndexesOne[0] = 9; - depositIndexesOne[1] = 1; - depositIndexesOne[2] = 2; - depositIndexesOne[3] = 3; - depositIndexesOne[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexesOne, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // configure NFT position two - uint256[] memory depositIndexesTwo = new uint256[](4); - depositIndexesTwo[0] = 5; - depositIndexesTwo[1] = 1; - depositIndexesTwo[2] = 3; - depositIndexesTwo[3] = 12; - mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexesTwo, - minter: _minterTwo, - mintAmount: 1000 * 1e18, - pool: _poolTwo - }); - - uint256 tokenIdTwo = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // minterOne deposits their NFT into the rewards contract - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // minterTwo deposits their NFT into the rewards contract - _stakeToken(address(_poolTwo), _minterTwo, tokenIdTwo); - - // borrower takes actions providing reserves enabling reserve auctions - // bidder takes reserve auctions by providing ajna tokens to be burned - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 300 * 1e18, - limitIndex: 3, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - uint256 currentBurnEpochPoolOne = _poolOne.currentBurnEpoch(); - - // check only deposit owner can claim rewards - changePrank(_minterTwo); - vm.expectRevert(IRewardsManagerErrors.NotOwnerOfDeposit.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - - // check rewards earned in one pool shouldn't be claimable by depositors from another pool - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - _rewardsManager.claimRewards(tokenIdTwo, _poolTwo.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterTwo), 0); - - // call update exchange rate to enable claiming rewards - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit UpdateExchangeRates(_minterOne, address(_poolOne), depositIndexesOne, 4.089968908133149070 * 1e18); - uint256 updateReward = _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexesOne); - assertEq(_ajnaToken.balanceOf(_minterOne), updateReward); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); - - // check owner in pool with accrued interest can properly claim rewards - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 4.089968908133149070 * 1e18); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), 40.899689081331421207 * 1e18); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpochPoolOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 44.989657989464570277 * 1e18); - assertLt(_ajnaToken.balanceOf(_minterOne), tokensToBurn); - } - - /********************/ - /*** FUZZ TESTING ***/ - /********************/ - - function _requiredCollateral(ERC20Pool pool_, uint256 borrowAmount, uint256 indexPrice) internal view returns (uint256 requiredCollateral_) { - // calculate the required collateral based upon the borrow amount and index price - (uint256 interestRate, ) = pool_.interestRateInfo(); - uint256 newInterestRate = Maths.wmul(interestRate, 1.1 * 10**18); // interest rate multipled by increase coefficient - uint256 expectedDebt = Maths.wmul(borrowAmount, _borrowFeeRate(newInterestRate) + Maths.WAD); - requiredCollateral_ = Maths.wdiv(expectedDebt, _poolUtils.indexToPrice(indexPrice)) + Maths.WAD; - } - - // Helper function that returns a random subset from array - function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { - uint256[] memory copyOfArray = new uint256[](array.length); - for(uint j = 0; j < copyOfArray.length; j++){ - copyOfArray[j] = array[j]; - } - uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); - subsetArray = new uint256[](randomNoOfNfts); - for(uint256 i = 0; i < randomNoOfNfts; i++) { - uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); - subsetArray[i] = copyOfArray[randomIndex]; - copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; - } - } - - // Returns N addresses array - function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { - addresses_ = new address[](noOfAddress); - for(uint i = 0; i < noOfAddress; i++) { - addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); - } - } - - function testClaimRewardsFuzzy(uint256 indexes, uint256 mintAmount) external { - indexes = bound(indexes, 3, 10); // number of indexes to add liquidity to - mintAmount = bound(mintAmount, 1 * 1e18, 100_000 * 1e18); // bound mint amount and dynamically determine borrow amount and collateral based upon provided index and mintAmount - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](indexes); - for (uint256 i = 0; i < indexes; ++i) { - depositIndexes[i] = _randomIndex(); - } - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: mintAmount, - pool: _poolOne - }); - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - - // stake NFT - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - // calculates a limit index leaving one index above the htp to accrue interest - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: Maths.wdiv(mintAmount, Maths.wad(3)), - limitIndex: limitIndex, - pool: _poolOne - }); - - uint256 tokensToBurn = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), 0); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - assertGt(_ajnaToken.balanceOf(_updater), 0); - - // calculate rewards earned and compare to percentages for updating and claiming - uint256 rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertGt(rewardsEarned, 0); - - // claim rewards accrued since deposit - changePrank(_minterOne); - assertEq(_ajnaToken.balanceOf(_minterOne), 0); - vm.expectEmit(true, true, true, true); - emit ClaimRewards(_minterOne, address(_poolOne), tokenIdOne, _epochsClaimedArray(1, 0), rewardsEarned); - _rewardsManager.claimRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); - - // assert rewards claimed is less than ajna tokens burned cap - assertLt(_ajnaToken.balanceOf(_minterOne), Maths.wmul(tokensToBurn, 0.800000000000000000 * 1e18)); - } - - function testStakingRewardsFuzzy(uint256 deposits, uint256 reserveAuctions) external { - deposits = bound(deposits, 1, 25); // number of deposits to make - reserveAuctions = bound(reserveAuctions, 1, 25); // number of reserve Auctions to complete - - uint256[] memory tokenIds = new uint256[](deposits); - - // configure NFT position - uint256[] memory depositIndexes = new uint256[](3); - for (uint256 j = 0; j < 3; ++j) { - depositIndexes[j] = _randomIndex(); - vm.roll(block.number + 1); // advance block to ensure that the index price is different - } - - address[] memory minters = _getAddresses(deposits); - - // stake variable no of deposits - for(uint256 i = 0; i < deposits; ++i) { - // mint and memorilize Positions - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: minters[i], - mintAmount: 1_000_000_000 * 1e18, - pool: _poolOne - }); - - tokenIds[i] = _mintAndMemorializePositionNFT(mintMemorializeParams); - tokenIdToMinter[tokenIds[i]] = minters[i]; - _stakeToken(address(_poolOne), minters[i], tokenIds[i]); - } - - uint256 updaterBalance = _ajnaToken.balanceOf(_updater); - - for(uint i = 0; i < deposits; i++) { - minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); - } - - // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch - for(uint i = 0; i < reserveAuctions; ++i) { - uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); - TriggerReserveAuctionParams memory triggerReserveAuctionParams = TriggerReserveAuctionParams({ - borrowAmount: 10_000 * 1e18, - limitIndex: limitIndex, - pool: _poolOne - }); - - // start and end new reserve auction - uint256 tokensBurned = _triggerReserveAuctions(triggerReserveAuctionParams); - - // call update exchange rate to enable claiming rewards - changePrank(_updater); - assertEq(_ajnaToken.balanceOf(_updater), updaterBalance); - _rewardsManager.updateBucketExchangeRatesAndClaim(address(_poolOne), depositIndexes); - - // ensure updater gets reward for updating exchange rate - assertGt(_ajnaToken.balanceOf(_updater), updaterBalance); - - // ensure update rewards in each epoch is less than or equals to 10% of tokensBurned - assertLe(_ajnaToken.balanceOf(_updater) - updaterBalance, tokensBurned / 10); - - updaterBalance = _ajnaToken.balanceOf(_updater); - - // pick random NFTs from all NFTs to claim rewards - uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); - - for(uint j = 0; j < randomNfts.length; j++) { - address minterAddress = tokenIdToMinter[randomNfts[j]]; - changePrank(minterAddress); - - (, , uint256 lastInteractionEpoch) = _rewardsManager.getStakeInfo(randomNfts[j]); - - // select random epoch to claim reward - uint256 epochToClaim = lastInteractionEpoch < _poolOne.currentBurnEpoch() ? randomInRange(lastInteractionEpoch + 1, _poolOne.currentBurnEpoch()) : lastInteractionEpoch; - - uint256 rewardsEarned = _rewardsManager.calculateRewards(randomNfts[j], epochToClaim); - assertGt(rewardsEarned, 0); - - _rewardsManager.claimRewards(randomNfts[j], _poolOne.currentBurnEpoch()); - - // ensure user gets reward - assertGt(_ajnaToken.balanceOf(minterAddress), minterToBalance[minterAddress]); - minterToBalance[minterAddress] = _ajnaToken.balanceOf(minterAddress); - } - } - } - - function testClaimRewardsFreezeUnclaimedYield() external { - skip(10); - - uint256[] memory depositIndexes = new uint256[](5); - depositIndexes[0] = 9; - depositIndexes[1] = 1; - depositIndexes[2] = 2; - depositIndexes[3] = 3; - depositIndexes[4] = 4; - MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({ - indexes: depositIndexes, - minter: _minterOne, - mintAmount: 1000 * 1e18, - pool: _poolOne - }); - - uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams); - _stakeToken(address(_poolOne), _minterOne, tokenIdOne); - - uint256 currentBurnEpoch = _poolOne.currentBurnEpoch(); - - changePrank(_minterOne); - // should revert if the epoch to claim is not available yet - vm.expectRevert(IRewardsManagerErrors.EpochNotAvailable.selector); - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10); - - // user should be able to claim rewards for current epoch - _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch); - } - -} diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 7ee435f8e..8257ad927 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -10,11 +10,9 @@ import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import 'src/interfaces/pool/IPool.sol'; import 'src/interfaces/pool/commons/IPoolEvents.sol'; import 'src/interfaces/pool/IERC3156FlashBorrower.sol'; - import 'src/PoolInfoUtils.sol'; import 'src/libraries/external/Auctions.sol'; -import 'src/libraries/internal/Maths.sol'; abstract contract DSTestPlus is Test, IPoolEvents { @@ -31,9 +29,9 @@ abstract contract DSTestPlus is Test, IPoolEvents { /*** Pools ***/ /*************/ - IPool internal _pool; - PoolInfoUtils internal _poolUtils; - uint256 internal _startTime; + IPool internal _pool; + PoolInfoUtils internal _poolUtils; + uint256 internal _startTime; uint256 internal _p1505_26 = 1_505.263728469068226832 * 1e18; uint256 internal _p1004_98 = 1_004.989662429170775094 * 1e18; From 2d869f2879b2c9ad2ccb33c4979ac22aa255c587 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:54:02 +0300 Subject: [PATCH 40/70] TOB-AJNA-8: Array lengths are not checked in LP allowance update functions (#725) - add input validation (indexes array length same as amounts array length) otherwise revert with InvalidAllowancesInput - unit tests --- src/interfaces/pool/commons/IPoolErrors.sol | 5 +++++ src/libraries/external/LenderActions.sol | 12 +++++++++++- .../ERC20Pool/ERC20PoolTransferLPs.t.sol | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/interfaces/pool/commons/IPoolErrors.sol b/src/interfaces/pool/commons/IPoolErrors.sol index a85af9f01..00418787c 100644 --- a/src/interfaces/pool/commons/IPoolErrors.sol +++ b/src/interfaces/pool/commons/IPoolErrors.sol @@ -107,6 +107,11 @@ interface IPoolErrors { */ error InsufficientLiquidity(); + /** + * @notice When increasing / decreasing LPs allowances indexes and amounts arrays parameters should have same length. + */ + error InvalidAllowancesInput(); + /** * @notice When transferring LPs between indices, the new index must be a valid index. */ diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index d214ef551..71eb7e7c6 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -86,6 +86,7 @@ library LenderActions { error CannotMergeToHigherPrice(); error DustAmountNotExceeded(); error NoAllowance(); + error InvalidAllowancesInput(); error InvalidIndex(); error InvalidAmount(); error LUPBelowHTP(); @@ -579,6 +580,8 @@ library LenderActions { * @notice See `IPoolLenderActions` for descriptions * @dev write state: * - increment LPs allowances + * @dev reverts on: + * - invalid indexes and amounts input InvalidAllowancesInput() * @dev emit events: * - IncreaseLPsAllowance */ @@ -589,8 +592,10 @@ library LenderActions { uint256[] calldata amounts_ ) external { uint256 indexesLength = indexes_.length; - uint256 index; + if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); + + uint256 index; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; @@ -611,6 +616,8 @@ library LenderActions { * @notice See `IPoolLenderActions` for descriptions * @dev write state: * - decrement LPs allowances + * @dev reverts on: + * - invalid indexes and amounts input InvalidAllowancesInput() * @dev emit events: * - DecreaseLPsAllowance */ @@ -621,6 +628,9 @@ library LenderActions { uint256[] calldata amounts_ ) external { uint256 indexesLength = indexes_.length; + + if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); + uint256 index; for (uint256 i = 0; i < indexesLength; ) { diff --git a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol index 5393246fa..2da968134 100644 --- a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol @@ -186,6 +186,25 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } + function testIncreaseDecreaseLPsWithInvalidInput() external tearDown { + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2550; + indexes[1] = 2551; + indexes[2] = 2552; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10_000 * 1e18; + amounts[1] = 30_000 * 1e18; + + // increase allowance should revert for invalid input + vm.expectRevert(IPoolErrors.InvalidAllowancesInput.selector); + _pool.increaseLPsAllowance(_lender2, indexes, amounts); + + // decrease allowance should revert for invalid input + vm.expectRevert(IPoolErrors.InvalidAllowancesInput.selector); + _pool.decreaseLPsAllowance(_lender2, indexes, amounts); + } + function testTransferLPsForAllIndexes() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; From af2f77b90a68cab66ac47de1ba4e6f0c75b61c35 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Tue, 4 Apr 2023 09:08:01 -0400 Subject: [PATCH 41/70] Expose auction price and status (#721) * implemented unit test to satisfy a concern * expose auction status details * changed to natspec comments * reuse assert revert utility function per PR feedback --- src/PoolInfoUtils.sol | 37 ++++++++ .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 54 ++++++++++++ tests/forge/utils/DSTestPlus.sol | 87 ++++++++++++++----- 3 files changed, 154 insertions(+), 24 deletions(-) diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index cedd47fdb..3448dce01 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -9,6 +9,7 @@ import { _borrowFeeRate, _depositFeeRate, _indexOf, + _isCollateralized, _lpsToCollateral, _lpsToQuoteToken, _minDebtAmount, @@ -31,6 +32,42 @@ import { PoolCommons } from './libraries/external/PoolCommons.sol'; */ contract PoolInfoUtils { + /** + * @notice Exposes status of a liquidation auction + * @param borrower_ Identifies the loan being liquidated + * @return kickTime_ Time auction was kicked, implying end time + * @return collateral_ Remaining collateral available to be purchased (WAD) + * @return debtToCover_ Borrower debt to be covered (WAD) + * @return isCollateralized_ If true, takes will revert + * @return price_ Current price of the auction (WAD) + * @return neutralPrice_ Price at which bond holder is neither rewarded nor penalized (WAD) + */ + function auctionStatus(address ajnaPool_, address borrower_) + external + view + returns ( + uint256 kickTime_, + uint256 collateral_, + uint256 debtToCover_, + bool isCollateralized_, + uint256 price_, + uint256 neutralPrice_ + ) + { + IPool pool = IPool(ajnaPool_); + uint256 kickMomp; + ( , , , kickTime_, kickMomp, neutralPrice_, , , , ) = pool.auctionInfo(borrower_); + if (kickTime_ != 0) { + (debtToCover_, collateral_, ) = this.borrowerInfo(ajnaPool_, borrower_); + + (uint256 poolDebt,,,) = pool.debtInfo(); + uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); + isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); + + price_ = Auctions._auctionPrice(kickMomp, neutralPrice_, kickTime_); + } + } + function borrowerInfo(address ajnaPool_, address borrower_) external view diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 96d9712cb..2dc3e4e16 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -1878,6 +1878,60 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); } + function testTakeAfterSettleReverts() external { + // Borrower draws debt + _borrow({ + from: _borrower2, + amount: 1_730 * 1e18, + indexLimit: _i9_72, + newLup: 9.721295865031779605 * 1e18 + }); + + // Skip to make borrower undercollateralized and kick auction + skip(100 days); + _kick({ + from: _lender, + borrower: _borrower2, + debt: 9_976.561670003961916237 * 1e18, + collateral: 1_000 * 1e18, + bond: 98.533942419792216457 * 1e18, + transferAmount: 98.533942419792216457 * 1e18 + }); + + // Take everything + skip(10 hours); + _take({ + from: _lender, + borrower: _borrower2, + maxCollateral: 1_000 * 1e18, + bondChange: 6.531114528261135360 * 1e18, + givenAmount: 653.111452826113536000 * 1e18, + collateralTaken: 1_000 * 1e18, + isReward: true + }); + + // Partially settle the auction, such that it is not removed from queue + _settle({ + from: _lender, + borrower: _borrower2, + maxDepth: 1, + settledDebt: 2_824.753001999316079070 * 1e18 + }); + + // Borrower draws more debt + _drawDebt({ + from: _borrower2, + borrower: _borrower2, + amountToBorrow: 1_000 * 1e18, + limitIndex: _i9_72, + collateralToPledge: 1_000 * 1e18, + newLup: 9.721295865031779605 * 1e18 + }); + + // Take should revert + _assertTakeNoAuctionRevert(_borrower2, _borrower2, 1_000 * 1e18); + } + function testTakeAuctionPriceLtNeutralPrice() external tearDown { _addLiquidity({ diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index 8257ad927..af907558e 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -426,14 +426,27 @@ abstract contract DSTestPlus is Test, IPoolEvents { /*** State asserts ***/ /*********************/ + struct AuctionLocalVars { + address auctionKicker; + uint256 auctionBondFactor; + uint256 auctionBondSize; + uint256 auctionKickTime; + uint256 auctionKickMomp; + uint256 auctionNeutralPrice; + uint256 auctionTotalBondEscrowed; + uint256 auctionDebtInAuction; + uint256 borrowerThresholdPrice; + } + function _assertAuction(AuctionParams memory state_) internal { + AuctionLocalVars memory vars; ( - address auctionKicker, - uint256 auctionBondFactor, - uint256 auctionBondSize, - uint256 auctionKickTime, - uint256 auctionKickMomp, - uint256 auctionNeutralPrice, + vars.auctionKicker, + vars.auctionBondFactor, + vars.auctionBondSize, + vars.auctionKickTime, + vars.auctionKickMomp, + vars.auctionNeutralPrice, , , , @@ -441,25 +454,51 @@ abstract contract DSTestPlus is Test, IPoolEvents { (uint256 borrowerDebt, uint256 borrowerCollateral , ) = _poolUtils.borrowerInfo(address(_pool), state_.borrower); (, uint256 lockedBonds) = _pool.kickerInfo(state_.kicker); - (uint256 auctionTotalBondEscrowed,,,) = _pool.reservesInfo(); - (,,uint256 auctionDebtInAuction,) = _pool.debtInfo(); - uint256 borrowerThresholdPrice = borrowerCollateral > 0 ? borrowerDebt * Maths.WAD / borrowerCollateral : 0; - - assertEq(auctionKickTime != 0, state_.active); - assertEq(auctionKicker, state_.kicker); - assertGe(lockedBonds, auctionBondSize); - assertEq(auctionBondSize, state_.bondSize); - assertEq(auctionBondFactor, state_.bondFactor); - assertEq(auctionKickTime, state_.kickTime); - assertEq(auctionKickMomp, state_.kickMomp); - assertEq(auctionTotalBondEscrowed, state_.totalBondEscrowed); + (vars.auctionTotalBondEscrowed,,,) = _pool.reservesInfo(); + (,, vars.auctionDebtInAuction,) = _pool.debtInfo(); + vars.borrowerThresholdPrice = borrowerCollateral > 0 ? borrowerDebt * Maths.WAD / borrowerCollateral : 0; + + assertEq(vars.auctionKickTime != 0, state_.active); + assertEq(vars.auctionKicker, state_.kicker); + assertGe(lockedBonds, vars.auctionBondSize); + assertEq(vars.auctionBondSize, state_.bondSize); + assertEq(vars.auctionBondFactor, state_.bondFactor); + assertEq(vars.auctionKickTime, state_.kickTime); + assertEq(vars.auctionKickMomp, state_.kickMomp); + assertEq(vars.auctionTotalBondEscrowed, state_.totalBondEscrowed); assertEq(Auctions._auctionPrice( - auctionKickMomp, - auctionNeutralPrice, - auctionKickTime), state_.auctionPrice); - assertEq(auctionDebtInAuction, state_.debtInAuction); - assertEq(auctionNeutralPrice, state_.neutralPrice); - assertEq(borrowerThresholdPrice, state_.thresholdPrice); + vars.auctionKickMomp, + vars.auctionNeutralPrice, + vars.auctionKickTime), state_.auctionPrice); + assertEq(vars.auctionDebtInAuction, state_.debtInAuction); + assertEq(vars.auctionNeutralPrice, state_.neutralPrice); + assertEq(vars.borrowerThresholdPrice, state_.thresholdPrice); + + ( + uint256 kickTime, + uint256 collateral, + uint256 debtToCover, + bool isCollateralized, + uint256 price, + uint256 neutralPrice + ) = _poolUtils.auctionStatus(address(_pool), state_.borrower); + assertEq(kickTime, state_.kickTime); + assertEq(neutralPrice, state_.neutralPrice); + if (kickTime == 0) { + assertEq(collateral, 0); + assertEq(debtToCover, 0); + assertEq(price, 0); + } else { + assertEq(collateral, borrowerCollateral); + assertEq(debtToCover, borrowerDebt); + assertEq(isCollateralized, _isCollateralized( + borrowerDebt, + borrowerCollateral, + _lup(), + _pool.poolType()) + ); + assertEq(price, state_.auctionPrice); + } } function _assertPool(PoolParams memory state_) internal { From 4a57aa5647f728626d9d0aa338261f55db9c7bb8 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Tue, 4 Apr 2023 21:40:01 +0530 Subject: [PATCH 42/70] NFT pool invariants setup (#723) * Update invariant setup * Initial ERC721 invariants setup * Restructure tests directory * Add basic erc721Pool handler * Merged develop into erc721-invariants * Fix nit * Add NFT collateral invariants CT2, CT3, CT4, CT5, CT6 and CT7 * Move Rewards tests into unit test directory * Add make command for all invariant and all regression tests --- Makefile | 6 +- src/ERC721Pool.sol | 15 + .../pool/erc721/IERC721PoolState.sol | 15 + tests/README.md | 24 + .../regression/RegressionTestBasic.t.sol | 263 ----------- .../RegressionTestLiquidation.t.sol | 285 ------------ .../regression/RegressionTestReserves.t.sol | 409 ----------------- tests/forge/PositionManager.t.sol | 4 +- .../ERC721TakeWithExternalLiquidity.sol | 2 +- .../ERC20Pool/BasicERC20PoolInvariants.t.sol | 113 +++++ .../LiquidationERC20PoolInvariants.t.sol | 38 ++ .../ReserveERC20PoolInvariants.t.sol | 56 +++ .../handlers/BasicERC20PoolHandler.sol} | 134 +----- .../handlers/LiquidationERC20PoolHandler.sol | 86 ++++ .../handlers/ReserveERC20PoolHandler.sol | 22 + .../unbounded/BaseERC20PoolHandler.sol | 65 +++ .../UnboundedBasicERC20PoolHandler.sol | 142 ++++++ .../UnboundedLiquidationERC20PoolHandler.sol | 101 +++++ .../BasicERC721PoolInvariants.t.sol | 190 ++++++++ .../handlers/BasicERC721PoolHandler.sol | 218 ++++++++++ .../unbounded/BaseERC721PoolHandler.sol | 65 +++ .../UnboundedBasicERC721PoolHandler.sol | 157 +++++++ .../base/BaseInvariants.sol} | 32 +- .../base}/BasicInvariants.t.sol | 98 +---- .../invariants/base/InvariantsTestHelpers.sol | 0 .../base}/LiquidationInvariants.t.sol | 34 +- .../base}/ReserveInvariants.t.sol | 34 +- .../base/handlers/BasicPoolHandler.sol | 146 +++++++ .../base}/handlers/LiquidationPoolHandler.sol | 78 +--- .../base}/handlers/ReservePoolHandler.sol | 23 +- .../base/handlers/unbounded}/BaseHandler.sol | 43 +- .../unbounded}/UnboundedBasicPoolHandler.sol | 131 +----- .../UnboundedLiquidationPoolHandler.sol | 98 +---- .../UnboundedReservePoolHandler.sol | 0 .../invariants/interfaces/IBaseHandler.sol | 0 .../invariants/interfaces/ITestBase.sol | 0 .../RegressionTestBasicERC20Pool.t.sol | 263 +++++++++++ .../RegressionTestLiquidationERC20Pool.t.sol | 285 ++++++++++++ .../RegressionTestReservesERC20Pool.t.sol | 410 ++++++++++++++++++ .../{ => unit}/ERC20Pool/ERC20DSTestPlus.sol | 4 +- .../ERC20Pool/ERC20PoolBorrow.t.sol | 0 .../ERC20Pool/ERC20PoolCollateral.t.sol | 0 .../ERC20Pool/ERC20PoolFactory.t.sol | 2 +- .../ERC20Pool/ERC20PoolFlashloan.t.sol | 2 +- .../ERC20Pool/ERC20PoolGasLoadTest.t.sol | 2 +- .../ERC20Pool/ERC20PoolInfoUtils.t.sol | 2 +- .../ERC20Pool/ERC20PoolInputValidation.t.sol | 0 .../ERC20PoolInterestRateAndEMAs.t.sol | 0 .../ERC20PoolLiquidationsArbTake.t.sol | 0 .../ERC20PoolLiquidationsDepositTake.t.sol | 0 .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 0 ...ERC20PoolLiquidationsKickWithDeposit.t.sol | 0 .../ERC20Pool/ERC20PoolLiquidationsMisc.t.sol | 0 .../ERC20PoolLiquidationsScaled.t.sol | 2 +- .../ERC20PoolLiquidationsSettle.t.sol | 0 .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 0 .../ERC20Pool/ERC20PoolLoanHeap.t.sol | 0 .../ERC20Pool/ERC20PoolMulticall.t.sol | 0 .../ERC20Pool/ERC20PoolPrecision.t.sol | 2 +- .../ERC20Pool/ERC20PoolPurchaseQuote.t.sol | 0 .../ERC20Pool/ERC20PoolQuoteToken.t.sol | 0 .../ERC20Pool/ERC20PoolReserveAuction.t.sol | 2 +- .../ERC20Pool/ERC20PoolTransferLPs.t.sol | 0 .../ERC20Pool/ERC20SafeTransferTokens.sol | 0 .../ERC721Pool/ERC721DSTestPlus.sol | 4 +- .../ERC721Pool/ERC721PoolBorrow.t.sol | 0 .../ERC721Pool/ERC721PoolCollateral.t.sol | 0 .../ERC721Pool/ERC721PoolEMAs.t.sol | 0 .../ERC721Pool/ERC721PoolFactory.t.sol | 2 +- .../ERC721Pool/ERC721PoolFlashloan.t.sol | 2 +- .../ERC721PoolInputValidation.t.sol | 0 .../ERC721Pool/ERC721PoolInterest.t.sol | 0 .../ERC721PoolLiquidationsDepositTake.t.sol | 0 .../ERC721PoolLiquidationsKick.t.sol | 0 .../ERC721PoolLiquidationsSettle.t.sol | 0 .../ERC721PoolLiquidationsSettleAuction.t.sol | 0 .../ERC721PoolLiquidationsTake.t.sol | 2 +- .../ERC721Pool/ERC721PoolPurchaseQuote.t.sol | 0 .../ERC721Pool/ERC721PoolReserveAuction.t.sol | 0 .../{ => unit}/Rewards/RewardsDSTestPlus.sol | 2 +- .../{ => unit}/Rewards/RewardsManager.t.sol | 0 tests/forge/utils/Tokens.sol | 5 +- 82 files changed, 2503 insertions(+), 1617 deletions(-) delete mode 100644 tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol delete mode 100644 tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol delete mode 100644 tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol create mode 100644 tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol create mode 100644 tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol create mode 100644 tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol rename tests/forge/{ERC20Pool/invariants/handlers/BasicPoolHandler.sol => invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol} (56%) create mode 100644 tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol create mode 100644 tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol create mode 100644 tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol create mode 100644 tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol rename tests/forge/{ERC20Pool/invariants/base/InvariantsTestBase.sol => invariants/base/BaseInvariants.sol} (55%) rename tests/forge/{ERC20Pool/invariants => invariants/base}/BasicInvariants.t.sol (84%) rename tests/forge/{ERC20Pool => }/invariants/base/InvariantsTestHelpers.sol (100%) rename tests/forge/{ERC20Pool/invariants => invariants/base}/LiquidationInvariants.t.sol (89%) rename tests/forge/{ERC20Pool/invariants => invariants/base}/ReserveInvariants.t.sol (76%) create mode 100644 tests/forge/invariants/base/handlers/BasicPoolHandler.sol rename tests/forge/{ERC20Pool/invariants => invariants/base}/handlers/LiquidationPoolHandler.sol (55%) rename tests/forge/{ERC20Pool/invariants => invariants/base}/handlers/ReservePoolHandler.sol (67%) rename tests/forge/{ERC20Pool/invariants/base => invariants/base/handlers/unbounded}/BaseHandler.sol (92%) rename tests/forge/{ERC20Pool/invariants/base => invariants/base/handlers/unbounded}/UnboundedBasicPoolHandler.sol (54%) rename tests/forge/{ERC20Pool/invariants/base => invariants/base/handlers/unbounded}/UnboundedLiquidationPoolHandler.sol (58%) rename tests/forge/{ERC20Pool/invariants/base => invariants/base/handlers/unbounded}/UnboundedReservePoolHandler.sol (100%) rename tests/forge/{ERC20Pool => }/invariants/interfaces/IBaseHandler.sol (100%) rename tests/forge/{ERC20Pool => }/invariants/interfaces/ITestBase.sol (100%) create mode 100644 tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol create mode 100644 tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol create mode 100644 tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol rename tests/forge/{ => unit}/ERC20Pool/ERC20DSTestPlus.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolBorrow.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolCollateral.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolFactory.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolFlashloan.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolGasLoadTest.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolInfoUtils.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolInputValidation.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsKick.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLiquidationsTake.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolLoanHeap.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolMulticall.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolPrecision.t.sol (99%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolPurchaseQuote.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolQuoteToken.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolReserveAuction.t.sol (97%) rename tests/forge/{ => unit}/ERC20Pool/ERC20PoolTransferLPs.t.sol (100%) rename tests/forge/{ => unit}/ERC20Pool/ERC20SafeTransferTokens.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721DSTestPlus.sol (99%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolBorrow.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolCollateral.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolEMAs.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolFactory.t.sol (99%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolFlashloan.t.sol (98%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolInputValidation.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolInterest.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolLiquidationsKick.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolLiquidationsTake.t.sol (99%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolPurchaseQuote.t.sol (100%) rename tests/forge/{ => unit}/ERC721Pool/ERC721PoolReserveAuction.t.sol (100%) rename tests/forge/{ => unit}/Rewards/RewardsDSTestPlus.sol (99%) rename tests/forge/{ => unit}/Rewards/RewardsManager.t.sol (100%) diff --git a/Makefile b/Makefile index c19f65ac6..c466f8404 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,12 @@ build :; forge clean && forge build test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report -test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest +test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest +test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC20 +test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC721 test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression +test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression --mc ERC20 +test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt test_regression --mc ERC721 coverage :; forge coverage --no-match-test "testLoad|invariant" test-invariant-erc20-precision :; ./test-invariant-erc20-precision.sh diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 5bacfce3b..4c1e12117 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -25,6 +25,7 @@ import { IERC721PoolLenderActions } from './interfaces/pool/erc721/IERC721Pool.sol'; import { IERC721Taker } from './interfaces/pool/erc721/IERC721Taker.sol'; +import { IERC721PoolState } from './interfaces/pool/erc721/IERC721PoolState.sol'; import { FlashloanablePool } from './base/FlashloanablePool.sol'; @@ -649,4 +650,18 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { IERC721Token(_getArgAddress(COLLATERAL_ADDRESS)).transferFrom(from_, to_, tokenId_); } + /**************************/ + /*** External Functions ***/ + /**************************/ + + /// @inheritdoc IERC721PoolState + function totalBorrowerTokens(address borrower_) external view override returns(uint256) { + return borrowerTokenIds[borrower_].length; + } + + /// @inheritdoc IERC721PoolState + function totalBucketTokens() external view override returns(uint256) { + return bucketTokenIds.length; + } + } diff --git a/src/interfaces/pool/erc721/IERC721PoolState.sol b/src/interfaces/pool/erc721/IERC721PoolState.sol index ce76f45c9..4bc028a82 100644 --- a/src/interfaces/pool/erc721/IERC721PoolState.sol +++ b/src/interfaces/pool/erc721/IERC721PoolState.sol @@ -35,4 +35,19 @@ interface IERC721PoolState { function bucketTokenIds( uint256 nftIndex ) external view returns (uint256 tokenId); + + /** + * @notice Returns the total NFT pledged by a borrower. + * @param borrower The address of borrower that pledged the NFT. + * @return tokens Total borrower NFTs. + */ + function totalBorrowerTokens( + address borrower + ) external view returns (uint256 tokens); + + /** + * @notice Returns the total NFT added in pool bucket. + * @return tokens Total NFT in bucket. + */ + function totalBucketTokens() external view returns (uint256 tokens); } \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 77fe0ae2b..71a894e95 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,6 +12,10 @@ make test-with-gas-report ```bash make test-load ``` +- run both ERC20 and ERC721 Pool invariant tests: +```bash +make test-invariant +``` - run ERC20 Pool invariant tests: ```bash make test-invariant-erc20 @@ -20,10 +24,30 @@ make test-invariant-erc20 ```bash make test-invariant-erc20 QUOTE_PRECISION= COLLATERAL_PRECISION= ``` +- run ERC721 Pool invariant tests: +```bash +make test-invariant-erc721 +``` +- run ERC721 Pool invariant tests for specific quote token precision, default value(18): +```bash +make test-invariant-erc721 QUOTE_PRECISION= +``` - run ERC20 Pool invariant tests for most popular token precision combinations(6,8 and 18): ```bash make test-invariant-erc20-precision ``` +- run regression test for both ERC20 and ERC721 Pool: +```bash +make test-regression +``` +- run regression test for ERC20 Pool: +```bash +make test-regression-erc20 +``` +- run regression test for ERC721 Pool: +```bash +make test-regression-erc721 +``` - generate code coverage report: ```bash make coverage diff --git a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol deleted file mode 100644 index 465f1a360..000000000 --- a/tests/forge/ERC20Pool/regression/RegressionTestBasic.t.sol +++ /dev/null @@ -1,263 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -import { BasicInvariants } from "../invariants/BasicInvariants.t.sol"; - -contract RegressionTestBasic is BasicInvariants { - - function setUp() public override { - super.setUp(); - } - - function test_regression_Underflow_1() external { - _basicPoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000); - - // check invariants hold true - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_exchange_rate_1() external { - _basicPoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809); - _basicPoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592); - _basicPoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 - function test_regression_exchange_rate_2() external { - _basicPoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); - _basicPoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); - _basicPoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _basicPoolHandler.removeCollateral(1, 1e36, 2570); - _basicPoolHandler.removeQuoteToken(1, 1e36, 2570); - _basicPoolHandler.removeCollateral(2, 1e36, 2570); - _basicPoolHandler.removeQuoteToken(2, 1e36, 2570); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test will fail when actors = 10, buckets = [2570], maxAmount = 1e36 - function test_regression_exchange_rate_3() external { - _basicPoolHandler.addQuoteToken(2842, 304, 2468594405605444095992); - _basicPoolHandler.addCollateral(0, 1, 3); - _basicPoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _basicPoolHandler.removeCollateral(0, 1, 3); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 - function test_regression_exchange_rate_4() external { - _basicPoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 587135207579305083672251579076072787077); - _basicPoolHandler.removeCollateral(712291886391993882782748602346033231793324080118979183300958, 673221151277569661050873992210938589, 999999997387885196930781163353866909746906615); - _basicPoolHandler.removeCollateral(4434852123445331038838, 92373980881732279172264, 16357203); - _basicPoolHandler.addQuoteToken(6532756, 16338, 2488340072929715905208495398161339232954907500634); - _basicPoolHandler.removeCollateral(934473801621702106582064701468475360, 999999998588451849650292641565069384488310108, 2726105246641027837873401505120164058057757115396); - _basicPoolHandler.addQuoteToken(0, 3272, 688437777000000000); - _basicPoolHandler.removeQuoteToken(36653992905059663682442427, 3272, 688437777000000000); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 - function test_regression_exchange_rate_5() external { - _basicPoolHandler.drawDebt(1156, 1686); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicPoolHandler.addQuoteToken(711, 2161, 2012); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 1, buckets = [2570] - function test_regression_exchange_rate_6() external { - _basicPoolHandler.addCollateral(999999999000000000000000081002632733724231666, 999999999243662968633890481597751057821356823, 1827379824097500721086759239664926559); - _basicPoolHandler.addQuoteToken(108018811574020559, 3, 617501271956497833026154369680502407518122199901237699791086943); - _basicPoolHandler.addCollateral(95036573736725249741129171676163161793295193492729984020, 5009341426566798172861627799, 2); - _basicPoolHandler.removeCollateral(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 5814100241); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 - // Fixed with commit -> https://github.com/ajna-finance/contracts/pull/613/commits/f106f0f7c96c1662325bdb5151fd745544e6dce0 - function test_regression_exchange_rate_7() external { - _basicPoolHandler.addCollateral(999999999249784004703856120761629301735818638, 15200, 2324618456838396048595845067026807532884041462750983926777912015561); - _basicPoolHandler.addQuoteToken(0, 2, 60971449684543878); - _basicPoolHandler.addCollateral(0, 648001392760875820320327007315181208349883976901103343226563974622543668416, 38134304133913510899173609232567613); - _basicPoolHandler.removeCollateral(0, 1290407354289435191451647900348688457414638662069174249777953, 125945131546441554612275631955778759442752893948134984981883798); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 - function test_regression_exchange_rate_8() external { - _basicPoolHandler.drawDebt(0, 10430); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicPoolHandler.addCollateral(86808428701435509359888008280539191473421, 35, 89260656586096811497271673595050); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - function test_regression_exchange_rate_9() external { - _basicPoolHandler.addQuoteToken(179828875014111774829603408358905079754763388655646874, 39999923045226513122629818514849844245682430, 12649859691422584279364490330583846883); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicPoolHandler.addCollateral(472, 2100, 11836); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicPoolHandler.pledgeCollateral(7289, 8216); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - function test_regression_fenwick_deposit_1() external { - _basicPoolHandler.addQuoteToken(60321923115154876306287876901335341390357684483818363750, 2, 0); - _basicPoolHandler.repayDebt(58055409653178, 2); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_fenwick_deposit_2() external { - _basicPoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2146593659305556796718319988088528090847459411703413796483450011160); - _basicPoolHandler.addCollateral(16885296866566559818671993560820380984757301691657405859955072474117, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 7764878663795446754367); - _basicPoolHandler.removeCollateral(999999997000000000000000000000000000000756426, 7366, 4723); - _basicPoolHandler.addQuoteToken(5673, 8294, 11316); - _basicPoolHandler.moveQuoteToken(919997327910338711763724656061931477, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3933155006830995444792575696); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_fenwick_deposit_3() external { - _basicPoolHandler.pullCollateral(64217420783566726909348297066823202824683000164554083, 651944294303386510182040138076901697073); - _basicPoolHandler.removeQuoteToken(172614182, 2999, 725); - _basicPoolHandler.addQuoteToken(52646814442098488638488433580148374391481084017027388775686120188766352301, 5021, 16410); - _basicPoolHandler.moveQuoteToken(2, 1, 3, 11769823729834119405789456482320067049929344685247053661486); - _basicPoolHandler.moveQuoteToken(1, 2833727997543655292227288672285470, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 7962018528962356191057551420322350); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_fenwick_deposit_4() external { - _basicPoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 2, 1267); - _basicPoolHandler.pledgeCollateral(1700127358962530, 0); - _basicPoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_fenwick_deposit_5() external { - _basicPoolHandler.repayDebt(281, 1502); - _basicPoolHandler.addCollateral(5529, 1090, 5431); - _basicPoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_fenwick_deposit_6() external { - _basicPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); - _basicPoolHandler.addQuoteToken(1000000000000000, 19319, 308); - _basicPoolHandler.pullCollateral(4218, 4175); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_fenwick_prefixSum_1() external { - _basicPoolHandler.addQuoteToken(5851, 999999999999999999999999999999000087, 1938); - _basicPoolHandler.addCollateral(135454721201807374404103595951250949, 172411742705067521609848985260337891060745418778973, 3); - _basicPoolHandler.pledgeCollateral(2, 185978674898652363737734333012844452989790885966093618883814734917759475); - _basicPoolHandler.moveQuoteToken(976453319, 2825105681459470134743617749102858205411027017903767825282483319, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 145056082857394229503325854914710239303685607721150607568547620026); - - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_fenwick_index_1() external { - _basicPoolHandler.addQuoteToken(3056, 915, 1594); - _basicPoolHandler.pullCollateral(274694202801760577094218807, 1); - _basicPoolHandler.addQuoteToken(1088, 3407, 3555); - _basicPoolHandler.addCollateral(1557, 13472, 15303); - _basicPoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 40692552539277917058910464963); - _basicPoolHandler.pullCollateral(1131485716992204156645660898919702, 30971207810832254868222941038507448); - _basicPoolHandler.removeCollateral(27428712668923640148402320299830959263828759458932482391338247903954077260349, 1136, 3944); - _basicPoolHandler.moveQuoteToken(9746204317995874651524496302383356801834068305156642323380998069579800880, 1723109236200550802774859945265636287, 3213180193920898024510373220802133410941904907229061207617048152428481, 0); - - invariant_fenwick_bucket_index_F3(); - } - - function test_regression_transferLps_1() external { - _basicPoolHandler.transferLps(0, 1, 200, 2570); - - invariant_Bucket_deposit_time_B5_B6_B7(); - } - - function test_regression_transferLps_2() external { - _basicPoolHandler.transferLps(37233021465377552730514154972012012669272, 45957263314208417069590941186697869465410494677646946058359554, 405, 89727160292150007024940); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_transferLps_3() external { - _basicPoolHandler.transferLps(1795, 6198, 3110, 11449); - - invariant_Bucket_deposit_time_B5_B6_B7(); - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - function test_regression_pull_collateral_when_encumbered_greater_than_pledged() external { - _basicPoolHandler.drawDebt(1535776046383997344779595, 5191646246012456798576386242824793107669233); - _basicPoolHandler.transferLps(17293, 19210, 227780, 999999999999999999999999999999999999999999997); - _basicPoolHandler.removeQuoteToken(0, 0, 2); - _basicPoolHandler.pullCollateral(115, 149220); - } - - function test_regression_incorrect_zero_deposit_buckets_1() external { - _basicPoolHandler.repayDebt(15119, 6786); - _basicPoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1578322581132549441186648538841, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_fenwick_index_2() external { - uint256 depositAt2570 = 570036521745120847917211; - uint256 depositAt2571 = _basicPoolHandler.constrictToRange(2578324552477056269186646552413, 1e6, 1e28); - uint256 depositAt2572 = _basicPoolHandler.constrictToRange(1212, 1e6, 1e28); - _basicPoolHandler.addQuoteToken(1, depositAt2570, 2570); - _basicPoolHandler.addQuoteToken(1, depositAt2571, 2571); - _basicPoolHandler.addQuoteToken(1, depositAt2572, 2572); - assertEq(_pool.depositIndex(depositAt2570), 2570); - assertEq(_pool.depositIndex(depositAt2570 + depositAt2571), 2571); - assertEq(_pool.depositIndex(depositAt2570 + depositAt2571 + depositAt2572), 2572); - } - - function test_regression_collateralBalance_CT1_CT7() external { - _basicPoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _basicPoolHandler.repayDebt(2712912126128356234217, 251720382485531952743041849848); - _basicPoolHandler.addQuoteToken(253022590763482364356576159, 999999999999999273028438503236995092261608400, 712808213364422679443324012750); - _basicPoolHandler.removeQuoteToken(121890555084215923472733925382, 0, 3); - - invariant_collateralBalance_CT1_CT7(); - } - - // TODO: poolBalance + poolDebt >= _pool.depositSize fails by 1 unit of WAD (1.000115588871659711 >= 1.000115588871659712) - function test_regression_invariant_quoteTokenBalance_QT1() external { - _basicPoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098); - _basicPoolHandler.repayDebt(1, 2); - _basicPoolHandler.removeCollateral(200953640940463935290718680397023889633667961549, 2481, 3); - _basicPoolHandler.moveQuoteToken(695230664226651211376892782958299806602599384639648126900062519785408512, 1000115588871659705, 22812, 1955101796782211288928817909562); - _basicPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639932, 103); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_fenwick_deposit_8() external { - _basicPoolHandler.drawDebt(226719918559509764892175185709, 228676957600917178383525685311331); - - invariant_fenwick_depositAtIndex_F1(); - } -} diff --git a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol deleted file mode 100644 index 061a89624..000000000 --- a/tests/forge/ERC20Pool/regression/RegressionTestLiquidation.t.sol +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -import { LiquidationInvariants } from "../invariants/LiquidationInvariants.t.sol"; - -contract RegressionTestLiquidation is LiquidationInvariants { - - function setUp() public override { - super.setUp(); - } - - function test_regression_quote_token() external { - _liquidationPoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_arithmetic_overflow() external { - _liquidationPoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903); - _liquidationPoolHandler.addQuoteToken(3487, 871, 1654); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_bucket_take_lps() external { - _liquidationPoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.addQuoteToken(1, 20033186019073, 1); - _liquidationPoolHandler.bucketTake(0, 0, false, 2876997751); - - invariant_Lps_B1_B4(); - } - - function test_regression_interest_rate() external { - _liquidationPoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769); - - invariant_interest_rate_I1(); - } - - function test_regression_incorrect_no_of_borrowers() external { - _liquidationPoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513); - _liquidationPoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271); - _liquidationPoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294); - _liquidationPoolHandler.moveQuoteToken(47205392335275917691737183012282140599753693978176314740917, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 164043848691337333691028718232); - _liquidationPoolHandler.kickAuction(184206711567329609153924955630229148705869686378631519380021040314, 78351, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationPoolHandler.kickAuction(3, 199726916764352560035199423206927461876998880387108455962754538835220966553, 3); - _liquidationPoolHandler.removeQuoteToken(999999991828440064944955196599190431639924811, 2781559202773230142346489450532860130, 3000000005240421579956496007310960085855569344); - _liquidationPoolHandler.pullCollateral(48768502867710912107594904694036421700, 275047566877984818806178837359260100); - _liquidationPoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, false, 8154570107391684241724530527782571978369827827856399749867491880); - _liquidationPoolHandler.removeCollateral(43733538637150108518954934566131291302796656384802361118757432084573, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationPoolHandler.addQuoteToken(1, 2, 2); - _liquidationPoolHandler.repayDebt(647805461526201272, 0); - _liquidationPoolHandler.kickAuction(1019259585194528028904148545812353964867041444572537077023497678982801, 58796345025472936970320, 131319002678489819637546489086162345032717166507611595521); - _liquidationPoolHandler.moveQuoteToken(2, 2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationPoolHandler.moveQuoteToken(6164937621056362865643346803975636714, 4, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 315548939052682258); - _liquidationPoolHandler.repayDebt(2987067394366841692658, 170206016570563384086766968869520628); - _liquidationPoolHandler.pledgeCollateral(3558446182295495994762049031, 0); - _liquidationPoolHandler.drawDebt(4525700839008283200312069904720925039, 3000000000753374912785563581177665475703155339); - _liquidationPoolHandler.kickAuction(1, 3559779948348618822016735773117619950447774, 218801416747720); - _liquidationPoolHandler.addQuoteToken(1469716416900282992357252011629715552, 13037214114647887147246343731476169800, 984665637618013480616943810604306792); - _liquidationPoolHandler.pullCollateral(438961419917818200942534689247815826455600131, 64633474453314038763068322072915580384442279897841981); - - invariant_auctions_A3_A4(); - } - - // test was failing due to deposit time update even if kicker lp reward is 0. - // resolved with PR: https://github.com/ajna-finance/contracts/pull/674 - function test_regression_bucket_deposit_time() external { - _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 2079356830967144967054363629631641573895835179323954988585146991431, 233005625580787863707944); - _liquidationPoolHandler.bucketTake(21616, 1047473235778002354, false, 1062098588952039043823357); - _liquidationPoolHandler.bucketTake(1673497622984405133414814181152, 94526073941076989987362055170246, false, 1462); - - invariant_Bucket_deposit_time_B5_B6_B7(); - } - - function test_regression_transfer_taker_lps_bucket_deposit_time() external { - _liquidationPoolHandler.settleAuction(3637866246331061119113494215, 0, 6163485280468362485998190762304829820899757798629605592174295845105660515); - _liquidationPoolHandler.transferLps(1610, 1000000000018496758270674070884, 168395863093969200027183125335, 2799494920515362640996160058); - _liquidationPoolHandler.bucketTake(0, 10619296457595008969473693936299982020664977642271808785891719078511288, true, 1681500683437506364426133778273769573223975355182845498494263153646356302); - - invariant_Bucket_deposit_time_B5_B6_B7(); - } - - function test_regression_invariant_fenwick_depositAtIndex_F1() external { - _liquidationPoolHandler.moveQuoteToken(4058, 2725046678043704335543997294802562, 16226066, 4284); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_depositKick() external { - _liquidationPoolHandler.repayDebt(13418, 1160); - _liquidationPoolHandler.kickWithDeposit(143703836638834364678, 470133688850921941603); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_incorrect_take_2() external { - _liquidationPoolHandler.kickAuction(13452, 7198, 11328); - _liquidationPoolHandler.takeAuction(6772, 18720, 6668); - _liquidationPoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1666258487708695528254610529989951, 490873240291829575083322665078478117042861655783753); - - invariant_auction_taken_A6(); - } - - function test_regression_invariant_exchange_rate_bucket_take_1() external { - _liquidationPoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); - _liquidationPoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); - _liquidationPoolHandler.bucketTake(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - function test_regression_invariant_exchange_rate_bucket_take_2() external { - _liquidationPoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); - _liquidationPoolHandler.settleAuction(186120755740, 2, 59199623628501455128); - _liquidationPoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); - _liquidationPoolHandler.bucketTake(2, 259574184, true, 248534890472324170412180243783490514876275); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - - function test_regression_quote_token_2() external { - _liquidationPoolHandler.kickAuction(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationPoolHandler.kickAuction(416882035302092397436677640325827, 7379, 253058086367250264569525665396366); - _liquidationPoolHandler.kickAuction(95740057146806695735694068330212313517380414204596464841344800376300745, 15462030827034, 17811087070659573835739283446817); - _liquidationPoolHandler.drawDebt(91685640224888183606335500279, 3284161781338443742266950748717011); - _liquidationPoolHandler.settleAuction(366366807138151363686, 2, 39227118695514892784493088788799944161631371060); - - invariant_quoteTokenBalance_QT1(); - } - function test_regression_invariant_settle_F1_1() external { - _liquidationPoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980); - _liquidationPoolHandler.kickAuction(5135, 1752, 6350); - _liquidationPoolHandler.kickAuction(142699, 4496, 4356); - _liquidationPoolHandler.moveQuoteToken(1173, 1445, 792325212, 447); - _liquidationPoolHandler.settleAuction(18308, 3145, 947); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_settle_F1_2() external { - _liquidationPoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationPoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); - _liquidationPoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); - _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); - _liquidationPoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_settle_F1_3() external { - _liquidationPoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); - _liquidationPoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); - _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_settle_F2_1() external { - _liquidationPoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationPoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); - _liquidationPoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); - _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); - _liquidationPoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); - - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_invariant_settle_F2_2() external { - _liquidationPoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); - _liquidationPoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); - _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); - - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_invariant_settle_F1_4() external { - _liquidationPoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); - _liquidationPoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); - _liquidationPoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); - _liquidationPoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); - _liquidationPoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); - _liquidationPoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_settle_F2_3() external { - _liquidationPoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); - _liquidationPoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); - _liquidationPoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); - _liquidationPoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); - _liquidationPoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); - _liquidationPoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_invariant_F3_1() external { - _liquidationPoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationPoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3731592205777443374190, 2); - _liquidationPoolHandler.takeAuction(3554599780774102176805971372130467746, 140835031537485528703906318530162192, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationPoolHandler.repayDebt(2692074105646752292572533908391, 1968526964305399089154844418825); - _liquidationPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 4553829); - _liquidationPoolHandler.bucketTake(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 0); - _liquidationPoolHandler.drawDebt(626971501456142588551128155365, 816763288150043968438676); - _liquidationPoolHandler.pullCollateral(381299861468989210101433912, 999999999999997998400442008957368645662570165); - - invariant_fenwick_bucket_index_F3(); - } - - function test_regression_invariant_F3_2() external { - _liquidationPoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); - _liquidationPoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); - _liquidationPoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); - _liquidationPoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - - invariant_fenwick_bucket_index_F3(); - } - - function test_regression_invariant_F4_1() external { - _liquidationPoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 127546297848367334892478587751, 723921922395815633171615243621131242188407029895233162931857565302); - _liquidationPoolHandler.removeQuoteToken(2, 2, 7361820555); - _liquidationPoolHandler.takeAuction(85885591922376805486065427318859822458293427950603, 8526258315228761831408142393759013524255378290706574861831877477, 1267004887455971938409309909682740381503049590444968840223); - _liquidationPoolHandler.drawDebt(663777721413606329209923101072, 946300054291644291801213511570); - _liquidationPoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); - _liquidationPoolHandler.addQuoteToken(9360900796482582322800, 694431436637841996793959397509, 553923154643858021986449189292); - _liquidationPoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 34469655866078951331675076928366708920312931751567797); - _liquidationPoolHandler.bucketTake(0, 1, false, 3); - _liquidationPoolHandler.bucketTake(1190209291225920034207711400729307351194726, 2492241351445208059551299524117408972943752042954, false, 3385052658235853990473420226123930971); - _liquidationPoolHandler.settleAuction(2693191148227658159823862814074, 44032195641927234172430384447, 2992758194960713897487381207167); - _liquidationPoolHandler.removeQuoteToken(3, 34308174710409047450205135565, 2); - _liquidationPoolHandler.takeAuction(235062105582030911119033338, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_invariant_F4_2() external { - _liquidationPoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); - _liquidationPoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); - _liquidationPoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); - _liquidationPoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_invariant_F4_3() external { - _liquidationPoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 88); - _liquidationPoolHandler.kickWithDeposit(454046303796091226235, 1); - _liquidationPoolHandler.addQuoteToken(22366532024867500041595597535594488494092956872779970834638, 2056702511, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationPoolHandler.takeAuction(7409458575819003489055485098, 19999999999999999999998047232, 160427188541373972791114); - _liquidationPoolHandler.drawDebt(54, 1078707919809097500728008); - _liquidationPoolHandler.takeAuction(2, 11014481, 0); - _liquidationPoolHandler.kickWithDeposit(6261145081390052923416, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationPoolHandler.repayDebt(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationPoolHandler.repayDebt(19522111312004366551699434321235702562902449, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationPoolHandler.removeQuoteToken(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationPoolHandler.kickAuction(1, 2109173590696846176713716365608775182694735853511202473079, 1); - _liquidationPoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_invariant_F4_4() external { - _liquidationPoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_invariant_bucketlps_B2_B3() external { - - _liquidationPoolHandler.takeAuction(267050932349, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3887647445238399127687813856507958874); - _liquidationPoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 103646259621272362812910538669334394369354710213939195837836110291707517186914, 22729901925249217583); - _liquidationPoolHandler.takeAuction(100662313874952447676789537887446294, 36755077739534085766246321257993, 20000000001077187985112900413); - _liquidationPoolHandler.settleAuction(999999999999999970610520171679024221920138860, 4339, 19021013243589608614756959415948670046791); - _liquidationPoolHandler.removeQuoteToken(7393406237507791712904627, 1097992169037390343, 30); - - invariant_Buckets_B2_B3(); - } - -} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol b/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol deleted file mode 100644 index f5610b9c6..000000000 --- a/tests/forge/ERC20Pool/regression/RegressionTestReserves.t.sol +++ /dev/null @@ -1,409 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -import { ReserveInvariants } from "../invariants/ReserveInvariants.t.sol"; - -contract RegressionTestReserve is ReserveInvariants { - - function setUp() public override { - super.setUp(); - } - - function test_regression_reserve_1() external { - _reservePoolHandler.kickAuction(3833, 15167, 15812); - _reservePoolHandler.removeQuoteToken(3841, 5339, 3672); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - // test was failing due to error in local fenwickAccureInterest method - function test_regression_reserve_2() external { - _reservePoolHandler.bucketTake(19730, 10740, false, 15745); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - _reservePoolHandler.addCollateral(14982, 18415, 2079); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_3() external { - _reservePoolHandler.repayDebt(404759030515771436961484, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - _reservePoolHandler.removeQuoteToken(1, 48462143332689486187207611220503504, 3016379223696706064676286307759709760607418884028758142005949880337746); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_4() external { - _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_5() external { - _reservePoolHandler.addQuoteToken(16175599156223678030374425049208907710, 7790130564765920091364739351727, 3); - _reservePoolHandler.takeReserves(5189, 15843); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, false, 32141946615464); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_6() external { - _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.removeQuoteToken(3, 76598848420614737624527356706527, 0); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_7() external { - _reservePoolHandler.addQuoteToken(3457, 669447918254181815570046125126321316, 999999999837564549363536522206516458546098684); - _reservePoolHandler.takeReserves(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.takeAuction(1340780, 50855928079819281347583122859151761721081932621621575848930363902528865907253, 1955849966715168052511460257792969975295827229642304100359774335664); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_8() external { - _reservePoolHandler.addQuoteToken(0, 16517235514828622102184417372650002297563613398679232953, 3); - _reservePoolHandler.takeReserves(1, 824651); - _reservePoolHandler.kickAuction(353274873012743605831170677893, 0, 297442424590491337560428021161844134441441035247561757); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_9() external { - _reservePoolHandler.addQuoteToken(8167, 13910, 6572); - _reservePoolHandler.removeQuoteToken(450224344766393467188006446127940623592343232978, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3); - _reservePoolHandler.addQuoteToken(1338758958425242459263005073411197235389119160018038412507867175716953081924, 0, 3); - _reservePoolHandler.removeQuoteToken(13684, 7152374202712184607581797, 37874588407625287908455929174); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_10() external { - _reservePoolHandler.drawDebt(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.takeAuction(57952503477150200455919212210202824, 59396836510148646246120666527, 253313800651499290076173012431766464943796699909751081638812681630219); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_11() external { - _reservePoolHandler.drawDebt(121976811044722028186086534321386307, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reservePoolHandler.removeQuoteToken(22099, 75368688232971077945057, 1089607217901154741924938851595); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_12() external { - _reservePoolHandler.drawDebt(7201, 13634); - _reservePoolHandler.startClaimableReserveAuction(4584); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_13() external { - _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 540213858694280098848655811354140073005); - _reservePoolHandler.takeAuction(0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 16744276840254269931315148200783781329474); - _reservePoolHandler.settleAuction(1052055081946638635908683442568, 2, 3); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_14() external { - _reservePoolHandler.settleAuction(437841947740231831335707997666789355668988087441752683415964733126988332082, 147808166723925302409649247274, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_reserve_16() external { - _reservePoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); - _reservePoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); - _reservePoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231); - _reservePoolHandler.takeReserves(3052809529665022333893308239466671666604242469878272137069, 2); - _reservePoolHandler.settleAuction(56829802927206056542134152487104, 1, 16551256); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 4559892907266199616760, true, 92132592320410512639572628067656882480659844625060229234412683145); - _reservePoolHandler.addQuoteToken(26659, 27252796304289191617124780530313880584663397025838797405583704016009646047240, 8174069071114126926049883726727); - _reservePoolHandler.settleAuction(7416752279321695807446009676282848840713503167567654621163487831711306738, 42429259698839522507819580090756, 4353185348715295869540288672); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.takeReserves(7414584624540108578389380660398591567646816233407392320795021351932076518, 119186585263660671065239170291646549528129172578); - _reservePoolHandler.takeReserves(14604452466686952199052773378, 15308); - _reservePoolHandler.moveQuoteToken(2, 7113439765, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 101839127799233627783); - _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); - _reservePoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 175006273713916823228319530732179, 3); - _reservePoolHandler.kickAuction(999999999999999989948035804259829580593704779, 2999999999999999995605838724439103323477035837, 567178035339127142779327214); - _reservePoolHandler.kickWithDeposit(17028734043909648834002499445, 9578925065330517200577552073309); - _reservePoolHandler.addQuoteToken(6672165, 3776221923932077947607417775990788567, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_fenwick_deposits_1() external { - _reservePoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_incorrect_zero_deposit_buckets_1() external { - _reservePoolHandler.addQuoteToken(26716, 792071517553389595371632366275, 1999999999999999449873579333598595527312558403); - - invariant_fenwick_prefixSumIndex_F4(); - _reservePoolHandler.takeAuction(3383098792294835418337099631478603398072656037191240558595006969488860, 23280466048203500609787983860018797249195596837096487660362732305, 999999999999999999999999012359); - - invariant_fenwick_prefixSumIndex_F4(); - } - - function test_regression_incorrect_zero_deposit_buckets_2() external { - _reservePoolHandler.addCollateral(9093188371345232280759885514931620, 736370925, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.removeQuoteToken(2, 743823342719479363729966668312423206558602, 6003791801508574660825548152233943700089469549364090309); - _reservePoolHandler.removeQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); - _reservePoolHandler.addQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); - _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.addQuoteToken(22558, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 26798251134); - _reservePoolHandler.moveQuoteToken(699684583201376669946795465695023954383, 871337618071093223322748209250657757655686665685488924893819949988, 6856667370119202181100844692321254723509125063768335, 2); - - invariant_fenwick_prefixSumIndex_F4(); - - } - - function test_regression_incorrect_bond() external { - _reservePoolHandler.settleAuction(18129, 6125, 756); - - invariant_bond_A2(); - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_reserves_fenwick_depositAtIndex_F1() external { - _reservePoolHandler.kickAuction(14062, 13380, 20332); - _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 250713412144308447525906089113510093407014793436690623); - _reservePoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_fenwick_depositAtIndex_F1(); - } - - function test_regression_invariant_reserves_settle_1() external { - _reservePoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886); - _reservePoolHandler.startClaimableReserveAuction(18407833277983020451007887294192863287187933); - _reservePoolHandler.settleAuction(2720, 3319, 516); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_reserves_settle_2() external { - _reservePoolHandler.takeAuction(3, 214198155653990209702223102757081411626927025, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.repayDebt(36, 19087); - _reservePoolHandler.drawDebt(2550145944163683156825587547113715005197220288637184, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_1() external { - _reservePoolHandler.kickAuction(22771, 1111716442170237736883602263032, 7068); - _reservePoolHandler.addCollateral(450013003559446434159001584489461823249847174057443177111241841181931, 312804075096415570730723645176181753809227168111076176815108, 0); - _reservePoolHandler.pledgeCollateral(1985831902099838153679635097394320832859625435, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); - _reservePoolHandler.takeAuction(159178586166894, 2, 2); - _reservePoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653); - _reservePoolHandler.startClaimableReserveAuction(2162); - _reservePoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524); - _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_2() external { - _reservePoolHandler.drawDebt(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reservePoolHandler.pullCollateral(8213783947977569843117913236674123519747026, 26007879196259510050186964175498569516185804333067186877); - _reservePoolHandler.drawDebt(2301679051848045604, 2599238865); - _reservePoolHandler.addCollateral(4242066606167690018840733069974159, 2308657525655903223461843364795, 65478701235782653506998474972558); - _reservePoolHandler.kickAuction(69087967303211947138147234149237227681311399268590256122007, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 61509477439); - _reservePoolHandler.bucketTake(72107205250762587233492136850, 1244277915808615586782916545843, false, 39013151190969055659579687996); - _reservePoolHandler.transferLps(235427298074932216827475360756961, 2730975142229662626738653393718571, 1801094436838792863068211758488417, 879376648610435813515943108046); - _reservePoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208); - _reservePoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765); - _reservePoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383); - _reservePoolHandler.startClaimableReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589); - _reservePoolHandler.drawDebt(3, 2); - _reservePoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855); - _reservePoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758); - _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reservePoolHandler.startClaimableReserveAuction(0); - _reservePoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_3() external { - _reservePoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442); - _reservePoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189); - _reservePoolHandler.startClaimableReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); - _reservePoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804); - _reservePoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067); - _reservePoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2); - _reservePoolHandler.settleAuction(2475430586786710276861336070835, 2600907908657087816392951766665339, 618867463233346276220185869); - _reservePoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417); - _reservePoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615); - _reservePoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417); - _reservePoolHandler.startClaimableReserveAuction(1); - _reservePoolHandler.bucketTake(3, 1, true, 2); - _reservePoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893); - _reservePoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reservePoolHandler.pullCollateral(2, 2); - _reservePoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 60110048782249025340, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_4() external { - _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.repayDebt(123785744463475277851, 431477); - _reservePoolHandler.transferLps(8349868629210939854344368826901611192, 2050523511941068426657597285533, 482178822629563486190079445656644, 113294184847064316812952522804); - _reservePoolHandler.kickWithDeposit(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); - _reservePoolHandler.settleAuction(2, 60232917818899277216367937385395389606, 109871490879953029603376159938904259489696033217506136); - _reservePoolHandler.repayDebt(11000946587948121111587595267746251370302202324589596297423219199459160, 1640564753028103680512592653747); - _reservePoolHandler.kickAuction(3981871706795545560915874060150150667177950440617972926122855684987, 198277768150818655020367, 2892877132676919180494078569276042); - _reservePoolHandler.addCollateral(1263277608, 63278488014355910828533249093658068159654702008400, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.pullCollateral(2673207612857671157084473752324442, 2000121050152966887141053752381); - _reservePoolHandler.removeCollateral(17512256671104333742254942029, 940622488995047370832475, 17490); - _reservePoolHandler.takeReserves(4664936529054748613171449032640911546982046023628226142220220474, 12228144613454452340256380805978754348438442703119); - - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_5() external { - _reservePoolHandler.settleAuction(841361270493647884419014561906636, 98291268956781519518581599501066994252857442823583923678216713962377882453983, 1406581758883); - _reservePoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0); - _reservePoolHandler.repayDebt(46968019084877, 3); - _reservePoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3); - // _reservePoolHandler.bucketTake(17377, 2748873005452892812548622619587, false, 999999999999999989712357375741033502535274466); - skip(2 hours); - _pool.updateInterest(); - /* - TODO: Check why deposit change is more than debt change in accrue interest in "updateInterest" - debt change --> 236352821760996207141053 - deposit change --> 236352821761181451576056 - */ - currentTimestamp = block.timestamp; - invariant_quoteTokenBalance_QT1(); - } - - function test_regression_invariant_reserves_settle_3() external { - _reservePoolHandler.bucketTake(38522325070060518315904717784000000000, 74804166371079302281493396778, false, 243284095655821418741726406906); - _reservePoolHandler.removeQuoteToken(63300517263709739718213296806, 544282601310994378458621785271097, 93004761485750531023207874); - _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 10850580031398165201080403693039642, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reservePoolHandler.takeAuction(1006654503300439100037731502194, 999999999999999820916638470184939411687495097, 2999999999999999849116243910762621146260836956); - _reservePoolHandler.settleAuction(513358560825207984200760701, 527826952804937875408570995575150, 3075); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_reserves_settle_4() external { - _reservePoolHandler.kickAuction(999999999999999886611844846637902655009191722, 809319421722186623206028334686443, 33424777291596678039713); - _reservePoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 2503088493515274266); - _reservePoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 16755); - _reservePoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 2); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_take_reserves_1() external { - _reservePoolHandler.drawDebt(3, 2472487412192096145519673462983934503); - _reservePoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 50482403089838632034016548451617756782); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_take_reserves_2() external { - _reservePoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.takeReserves(9990, 2); - _reservePoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 32167191465467724730024789812); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_take_reserves_3() external { - _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 7863893832813740178393566165935290555711); - _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 672940003103495713632014456312899612181893075117989217767500902); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_take_reserves_4() external { - _reservePoolHandler.bucketTake(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.takeReserves(2000008144440715646777241504589, 695559613732339828463793224249); - _reservePoolHandler.takeAuction(5260, 3000000000000000000000010654836333921317470662, 6571232818648673809695471386); - - invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - } - - function test_regression_invariant_repayDebt_F2_1() external { - _reservePoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045); - _reservePoolHandler.kickAuction(1000000000372489032271805343253, 33527, 2999999999999998999627510967728193679786334003); - _reservePoolHandler.takeAuction(30442763437987671335943625876181535412080651070033770037765737902267600059, 0, 62793434148368637031717982910725); - _reservePoolHandler.drawDebt(1, 2); - _reservePoolHandler.takeAuction(28478785935025462058931686388528614452411453327852591879599088, 1426479312070353, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.drawDebt(10933, 2937); - _reservePoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 6122968755523040); - _reservePoolHandler.repayDebt(10917282482493108186780095138347753666882231491750232316870663654516774564, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_regression_invariant_takeAuction_F3() external { - _reservePoolHandler.drawDebt(66012189296213, 3501011380219996136241089195497); - _reservePoolHandler.kickAuction(5022297903775350684886398975, 20526, 2902853749630275072725962069); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 4391496802861267555764811220); - _reservePoolHandler.moveQuoteToken(26018560, 3192, 25995484456155391449642016017, 22537); - _reservePoolHandler.transferLps(10763986310328530217005920827655704540417291683469924162879658, 4634, 8842, 3); - _reservePoolHandler.settleAuction(2913861884801667469428509650, 17685440748964982730500143988068465999241920952718023027278539889735696458314, 744860398079104642573120377479575543713282684535849403581932752660396046); - _reservePoolHandler.takeReserves(9546428924610247071820016, 1); - _reservePoolHandler.kickAuction(1021712469506287128291988, 470273052888220, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reservePoolHandler.kickWithDeposit(21372131561480654576901520848, 583255095299263976575486908); - _reservePoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 219682941, 6398456408984021365251851328837461998816613070677747503909692892499751257833); - _reservePoolHandler.moveQuoteToken(8413969458442105899430554342773, 42973831423907508485458560352, 14483994975746621772566970294, 27693669185946254354714892761); - _reservePoolHandler.bucketTake(0, 1, false, 1); - _reservePoolHandler.takeAuction(2760306433008897416497, 35178760526536102733112750779, 307455027758822287663945712); - - invariant_fenwick_bucket_index_F3(); - invariant_fenwick_prefixSumIndex_F4(); - } - - // FIXME: Seems to be an issue with Deposits.mult() in accrue interest or some issue with timestamp in invariant setup - function _test_regression_kick_F1_F2() external { - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); - _reservePoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); - _reservePoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); - _reservePoolHandler.takeAuction(296258719633565160185329, 490859840095298219320862, 16604700944401714968833692676); - _reservePoolHandler.kickAuction(1007024558278734662013991074770, 12316238, 8522190612260582802728723964891359810344750053801981528212387048); - _reservePoolHandler.takeAuction(999999999999999990212662818220103017885508577, 13644265990130681739980240101, 365402912996683431395427167362586262781607554542513822722975820380813222232); - _reservePoolHandler.takeAuction(999999999999999990000000000000000000000993018, 31506548945590221240114018464, 1016963456957222995035464545); - _reservePoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, false, 30294494991681513847857232418933803770638682537); - _reservePoolHandler.kickAuction(2324631542950979206383056100280239271207523734887421, 1, 23494016960770235530146856844201861803189848725938507629); - - invariant_fenwick_depositAtIndex_F1(); - invariant_fenwick_depositsTillIndex_F2(); - } - - function test_remove_regression_R1() external { - _reservePoolHandler.takeAuction(1000000000147122258, 3919731510820678131056801, 158441107709132461742605107); - _reservePoolHandler.repayDebt(15097247704276523502490912, 5821681489746654725611665637); - _reservePoolHandler.addQuoteToken(409278183265946161107935122, 13459778251101474251175765782, 17131651646875762675637482511491680925564181440856864512); - _reservePoolHandler.kickWithDeposit(3000000000000000000003060052276861736589117902, 10971651541557993591476169); - _reservePoolHandler.drawDebt(99176811231448450752542388131222351, 4756085816094695387473840); - _reservePoolHandler.transferLps(345464481275697722, 1, 1571, 636770839146216364947817981246144824780203402016795537219680499840300283500); - _reservePoolHandler.takeReserves(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reservePoolHandler.removeQuoteToken(2921676640197348125883567882, 110429299813004951706741973, 5838113258459267571531065497); - - invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - } - -} diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/PositionManager.t.sol index 6381bf8b7..bf960776d 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/PositionManager.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.14; import { Base64 } from '@base64-sol/base64.sol'; -import { ERC20HelperContract } from './ERC20Pool/ERC20DSTestPlus.sol'; -import { ERC721HelperContract } from './ERC721Pool/ERC721DSTestPlus.sol'; +import { ERC20HelperContract } from './unit/ERC20Pool/ERC20DSTestPlus.sol'; +import { ERC721HelperContract } from './unit/ERC721Pool/ERC721DSTestPlus.sol'; import 'src/interfaces/position/IPositionManager.sol'; import 'src/PositionManager.sol'; diff --git a/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol b/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol index 7fd88c128..e78c12a78 100644 --- a/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol +++ b/tests/forge/interactions/ERC721TakeWithExternalLiquidity.sol @@ -9,7 +9,7 @@ import { NFTCollateralToken } from '../utils/Tokens.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; -import { ERC721HelperContract } from '../ERC721Pool/ERC721DSTestPlus.sol'; +import { ERC721HelperContract } from '../unit/ERC721Pool/ERC721DSTestPlus.sol'; import 'src/PoolInfoUtils.sol'; import "./NFTTakeExample.sol"; diff --git a/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol new file mode 100644 index 000000000..884cc0363 --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import "@std/console.sol"; + +import { Pool } from 'src/base/Pool.sol'; +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; + +import { TokenWithNDecimals } from '../../utils/Tokens.sol'; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX +} from '../base/handlers/unbounded/BaseHandler.sol'; + +import { BasicERC20PoolHandler } from './handlers/BasicERC20PoolHandler.sol'; +import { BasicInvariants } from '../base/BasicInvariants.t.sol'; +import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; + +// contains invariants for the test +contract BasicERC20PoolInvariants is BasicInvariants { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + + * Collateral Token + * CT1: poolCtBal >= sum of all borrower's collateral + sum of all bucket's claimable collateral + * CT7: pool Pledged collateral = sum of all borrower's pledged collateral + + ****************************************************************************************************************************************/ + + uint256 internal constant NUM_ACTORS = 10; + + TokenWithNDecimals internal _collateral; + ERC20Pool internal _erc20pool; + ERC20Pool internal _impl; + ERC20PoolFactory internal _erc20poolFactory; + BasicERC20PoolHandler internal _basicERC20PoolHandler; + + function setUp() public override virtual{ + + super.setUp(); + + _collateral = new TokenWithNDecimals("Collateral", "C", uint8(vm.envUint("COLLATERAL_PRECISION"))); + _erc20poolFactory = new ERC20PoolFactory(address(_ajna)); + _impl = _erc20poolFactory.implementation(); + _erc20pool = ERC20Pool(_erc20poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18)); + _pool = Pool(address(_erc20pool)); + + _basicERC20PoolHandler = new BasicERC20PoolHandler( + address(_erc20pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_basicERC20PoolHandler); + + excludeContract(address(_ajna)); + excludeContract(address(_collateral)); + excludeContract(address(_quote)); + excludeContract(address(_erc20poolFactory)); + excludeContract(address(_erc20pool)); + excludeContract(address(_poolInfo)); + excludeContract(address(_impl)); + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_erc20pool), bucketIndex); + previousBucketExchangeRate[bucketIndex] = exchangeRate; + } + + (, previousInterestRateUpdate) = _erc20pool.interestRateInfo(); + + // TODO: Change once this issue is resolved -> https://github.com/foundry-rs/foundry/issues/2963 + targetSender(address(0x1234)); + } + + // checks pools collateral Balance to be equal to collateral pledged + function invariant_collateralBalance_CT1_CT7() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + uint256 totalCollateralPledged; + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + + ( , uint256 borrowerCollateral, ) = _erc20pool.borrowerInfo(borrower); + + totalCollateralPledged += borrowerCollateral; + } + + assertEq(_erc20pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); + + // convert pool collateral balance into WAD + uint256 collateralBalance = _collateral.balanceOf(address(_erc20pool)) * 10**(18 - _collateral.decimals()); + uint256 bucketCollateral; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, uint256 collateral, , , ) = _erc20pool.bucketInfo(bucketIndex); + + bucketCollateral += collateral; + } + + assertGe(collateralBalance, bucketCollateral + _erc20pool.pledgedCollateral()); + } + +} diff --git a/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol new file mode 100644 index 000000000..07c8c3c27 --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationInvariants } from '../base/LiquidationInvariants.t.sol'; +import { BaseInvariants } from '../base/BaseInvariants.sol'; +import { BasicInvariants } from '../base/BasicInvariants.t.sol'; +import { LiquidationERC20PoolHandler } from './handlers/LiquidationERC20PoolHandler.sol'; +import { BasicERC20PoolInvariants } from './BasicERC20PoolInvariants.t.sol'; + +contract LiquidationERC20PoolInvariants is LiquidationInvariants, BasicERC20PoolInvariants { + + LiquidationERC20PoolHandler internal _liquidationERC20PoolHandler; + + function setUp() public override(BaseInvariants, BasicERC20PoolInvariants) virtual{ + + super.setUp(); + + excludeContract(address(_basicERC20PoolHandler)); + + _liquidationERC20PoolHandler = new LiquidationERC20PoolHandler( + address(_erc20pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_liquidationERC20PoolHandler); + } + + function invariant_call_summary() public virtual override(BasicInvariants, LiquidationInvariants) useCurrentTimestamp { + super.invariant_call_summary(); + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol new file mode 100644 index 000000000..d52ef997f --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import "@std/console.sol"; + +import { BaseInvariants } from '../base/BaseInvariants.sol'; +import { LiquidationInvariants } from '../base/LiquidationInvariants.t.sol'; +import { ReserveInvariants } from '../base/ReserveInvariants.t.sol'; +import { ReserveERC20PoolHandler } from './handlers/ReserveERC20PoolHandler.sol'; +import { LiquidationERC20PoolInvariants } from './LiquidationERC20PoolInvariants.t.sol'; + +contract ReserveERC20PoolInvariants is ReserveInvariants, LiquidationERC20PoolInvariants { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + * Reserves + * RE1 : Reserves are unchanged by pledging collateral + * RE2 : Reserves are unchanged by removing collateral + * RE3 : Reserves are unchanged by depositing quote token into a bucket + * RE4 : Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired + * RE5 : Reserves are unchanged by adding collateral token into a bucket + * RE6 : Reserves are unchanged by removing collateral token from a bucket + * RE7 : Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) and increase/decrease by bond penalty/reward on take. + * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. + * RE9 : Reserves increase by 3 months of interest when a loan is kicked + * RE10: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt + * RE11: Reserves decrease by claimableReserves by startClaimableReserveAuction + * RE12: Reserves decrease by amount of reserve used to settle a auction + ****************************************************************************************************************************************/ + + ReserveERC20PoolHandler internal _reserveERC20PoolHandler; + + function setUp() public override(BaseInvariants, LiquidationERC20PoolInvariants) virtual { + + super.setUp(); + + excludeContract(address(_liquidationERC20PoolHandler)); + + _reserveERC20PoolHandler = new ReserveERC20PoolHandler( + address(_erc20pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_reserveERC20PoolHandler); + } + + function invariant_call_summary() public virtual override( LiquidationInvariants, LiquidationERC20PoolInvariants) useCurrentTimestamp {} + +} \ No newline at end of file diff --git a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol similarity index 56% rename from tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol rename to tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 2d8053aa3..8df5f77bc 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol @@ -3,25 +3,26 @@ pragma solidity 0.8.14; import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; - -import 'src/libraries/internal/Maths.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, BORROWER_MIN_BUCKET_INDEX, MIN_AMOUNT, - MAX_AMOUNT, - BaseHandler -} from '../base/BaseHandler.sol'; -import { UnboundedBasicPoolHandler } from '../base/UnboundedBasicPoolHandler.sol'; + MAX_AMOUNT +} from '../../base/handlers/unbounded/BaseHandler.sol'; +import { BasicPoolHandler } from '../../base/handlers/BasicPoolHandler.sol'; +import { UnboundedBasicPoolHandler } from '../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol'; +import { UnboundedBasicERC20PoolHandler } from './unbounded/UnboundedBasicERC20PoolHandler.sol'; +import { BaseERC20PoolHandler } from './unbounded/BaseERC20PoolHandler.sol'; /** * @dev this contract manages multiple actors * @dev methods in this contract are called in random order * @dev randomly selects an actor contract to make a txn */ -contract BasicPoolHandler is UnboundedBasicPoolHandler { +contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandler { constructor( address pool_, @@ -31,7 +32,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { address poolInfo_, uint256 numOfActors_, address testContract_ - ) BaseHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + ) BaseERC20PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { } @@ -39,53 +40,6 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { /*** Lender Test Functions ***/ /*****************************/ - function addQuoteToken( - uint256 actorIndex_, - uint256 amountToAdd_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { - numberOfCalls['BBasicHandler.addQuoteToken']++; - - // Prepare test phase - uint256 boundedAmount = _preAddQuoteToken(amountToAdd_); - - // Action phase - _addQuoteToken(boundedAmount, _lenderBucketIndex); - } - - function removeQuoteToken( - uint256 actorIndex_, - uint256 amountToRemove_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { - numberOfCalls['BBasicHandler.removeQuoteToken']++; - - // Prepare test phase - uint256 boundedAmount = _preRemoveQuoteToken(amountToRemove_); - - // Action phase - _removeQuoteToken(boundedAmount, _lenderBucketIndex); - } - - function moveQuoteToken( - uint256 actorIndex_, - uint256 amountToMove_, - uint256 fromIndex_, - uint256 toIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { - numberOfCalls['BBasicHandler.moveQuoteToken']++; - - // Prepare test phase - ( - uint256 boundedFromIndex, - uint256 boundedToIndex, - uint256 boundedAmount - ) = _preMoveQuoteToken(amountToMove_, fromIndex_, toIndex_); - - // Action phase - _moveQuoteToken(boundedAmount, boundedFromIndex, boundedToIndex); - } - function addCollateral( uint256 actorIndex_, uint256 amountToAdd_, @@ -114,20 +68,6 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { _removeCollateral(boundedAmount, _lenderBucketIndex); } - function transferLps( - uint256 fromActorIndex_, - uint256 toActorIndex_, - uint256 lpsToTransfer_, - uint256 bucketIndex_ - ) external useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { - // Prepare test phase - (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); - - // Action phase - _increaseLPsAllowance(receiver, _lenderBucketIndex, boundedLps); - _transferLps(_actor, receiver, _lenderBucketIndex); - } - /*******************************/ /*** Borrower Test Functions ***/ /*******************************/ @@ -197,43 +137,6 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { /*** Prepare Tests Functions ***/ /*******************************/ - function _preAddQuoteToken( - uint256 amountToAdd_ - ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); - } - - function _preRemoveQuoteToken( - uint256 amountToRemove_ - ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); - - // ensure actor has quote tokens to remove - (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if (lpBalanceBefore == 0) { - _addQuoteToken(boundedAmount_, _lenderBucketIndex); - } - } - - function _preMoveQuoteToken( - uint256 amountToMove_, - 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_AMOUNT, MAX_AMOUNT); - - // ensure actor has LPs to move - (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); - if (lpBalance == 0) _addQuoteToken(boundedAmount_, boundedToIndex_); - - (uint256 lps, ) = _pool.lenderInfo(boundedFromIndex_, _actor); - // restrict amount to move by available deposit inside bucket - uint256 availableDeposit = _poolInfo.lpsToQuoteTokens(address(_pool), lps, boundedFromIndex_); - boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); - } - function _preAddCollateral( uint256 amountToAdd_ ) internal pure returns (uint256 boundedAmount_) { @@ -250,25 +153,10 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { if(lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); } - function _preTransferLps( - uint256 toActorIndex_, - uint256 lpsToTransfer_ - ) internal returns (address receiver_, uint256 boundedLps_) { - // ensure actor has LPs to transfer - (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if(senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); - - (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - - boundedLps_ = constrictToRange(lpsToTransfer_, 0, senderLpBalance); - - receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; - } - function _prePledgeCollateral( uint256 amountToPledge_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, _pool.collateralScale(), MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToPledge_, _erc20Pool.collateralScale(), MAX_AMOUNT); } function _prePullCollateral( @@ -279,7 +167,7 @@ contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preDrawDebt( uint256 amountToBorrow_ - ) internal returns (uint256 boundedAmount_) { + ) internal override returns (uint256 boundedAmount_) { boundedAmount_ = constrictToRange(amountToBorrow_, MIN_AMOUNT, MAX_AMOUNT); // Pre Condition diff --git a/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol new file mode 100644 index 000000000..e7803e730 --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + MIN_AMOUNT, + MAX_AMOUNT +} from '../../base/handlers/unbounded/BaseHandler.sol'; +import { LiquidationPoolHandler } from '../../base/handlers/LiquidationPoolHandler.sol'; +import { UnboundedLiquidationERC20PoolHandler } from './unbounded/UnboundedLiquidationERC20PoolHandler.sol'; +import { BasicERC20PoolHandler } from './BasicERC20PoolHandler.sol'; + +contract LiquidationERC20PoolHandler is UnboundedLiquidationERC20PoolHandler, LiquidationPoolHandler, BasicERC20PoolHandler { + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BasicERC20PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + + } + + /****************************/ + /*** Taker Test Functions ***/ + /****************************/ + + function takeAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 actorIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BLiquidationHandler.takeAuction']++; + + amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); + + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); + + address borrower = actors[borrowerIndex_]; + address taker = _actor; + + ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) _kickAuction(borrowerIndex_, amount_ * 100, actorIndex_); + + changePrank(taker); + // skip time to make auction takeable + vm.warp(block.timestamp + 2 hours); + _takeAuction(borrower, amount_, taker); + } + + /******************************/ + /*** Settler Test Functions ***/ + /******************************/ + + function settleAuction( + uint256 actorIndex_, + uint256 borrowerIndex_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); + bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + + address borrower = actors[borrowerIndex_]; + uint256 maxDepth = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX; + + address actor = _actor; + + ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + + if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); + + changePrank(actor); + // skip time to make auction clearable + vm.warp(block.timestamp + 73 hours); + _settleAuction(borrower, maxDepth); + + _auctionSettleStateReset(borrower); + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol new file mode 100644 index 000000000..73ad3e0f1 --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { UnboundedReservePoolHandler } from '../../base/handlers/unbounded/UnboundedReservePoolHandler.sol'; +import { ReservePoolHandler } from '../../base/handlers/ReservePoolHandler.sol'; +import { MIN_AMOUNT } from '../../base/handlers/unbounded/BaseHandler.sol'; +import { LiquidationERC20PoolHandler } from './LiquidationERC20PoolHandler.sol'; + +contract ReserveERC20PoolHandler is ReservePoolHandler, LiquidationERC20PoolHandler { + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) LiquidationERC20PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) {} + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol new file mode 100644 index 000000000..e695a0c7b --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; + +import { TokenWithNDecimals } from '../../../../utils/Tokens.sol'; + +import { BaseHandler } from '../../../base/handlers/unbounded/BaseHandler.sol'; + +abstract contract BaseERC20PoolHandler is BaseHandler { + + // Token + TokenWithNDecimals internal _collateral; + + // ERC20Pool + ERC20Pool internal _erc20Pool; + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { + // Tokens + _collateral = TokenWithNDecimals(collateral_); + + // Pool + _erc20Pool = ERC20Pool(pool_); + + // Actors + actors = _buildActors(numOfActors_); + } + + /*****************************/ + /*** Pool Helper Functions ***/ + /*****************************/ + + function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { + address[] memory actorsAddress = new address[](noOfActors_); + + for (uint i = 0; i < noOfActors_; i++) { + address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); + actorsAddress[i] = actor; + + vm.startPrank(actor); + + _quote.mint(actor, 1e45); + _quote.approve(address(_pool), 1e45); + + _collateral.mint(actor, 1e45); + _collateral.approve(address(_pool), 1e45); + + vm.stopPrank(); + } + + return actorsAddress; + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol new file mode 100644 index 000000000..261ac4ef7 --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from "src/libraries/internal/Maths.sol"; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX +} from '../../../base/handlers/unbounded/BaseHandler.sol'; +import { UnboundedBasicPoolHandler } from "../../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol"; +import { BaseERC20PoolHandler } from './BaseERC20PoolHandler.sol'; + +/** + * @dev this contract manages multiple lenders + * @dev methods in this contract are called in random order + * @dev randomly selects a lender contract to make a txn + */ +abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, BaseERC20PoolHandler { + + /*******************************/ + /*** Lender Helper Functions ***/ + /*******************************/ + + function _addCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.addCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); + + _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"); + } + + function _removeCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.removeCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); + + 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"); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /*********************************/ + /*** Borrower Helper Functions ***/ + /*********************************/ + + function _pledgeCollateral( + uint256 amount_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pledgeCollateral']++; + + // **R1**: Exchange rates are unchanged by pledging collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + _erc20Pool.drawDebt(_actor, 0, 0, amount_); + } + + function _pullCollateral( + uint256 amount_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pullCollateral']++; + + // **R2**: Exchange rates are unchanged by pulling collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + try _erc20Pool.repayDebt(_actor, 0, amount_, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _drawDebt( + uint256 amount_ + ) internal virtual override updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.drawDebt']++; + + (uint256 poolDebt, , , ) = _erc20Pool.debtInfo(); + + // find bucket to borrow quote token + uint256 bucket = _erc20Pool.depositIndex(amount_ + poolDebt) - 1; + uint256 price = _poolInfo.indexToPrice(bucket); + uint256 collateralToPledge = ((amount_ * 1e18 + price / 2) / price) * 101 / 100 + 1; + + try _erc20Pool.drawDebt(_actor, amount_, 7388, collateralToPledge) { + + (uint256 interestRate, ) = _erc20Pool.interestRateInfo(); + + // **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt + increaseInReserves += Maths.wmul( + amount_, _borrowFeeRate(interestRate) + ); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _repayDebt( + uint256 amountToRepay_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.repayDebt']++; + + try _erc20Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol new file mode 100644 index 000000000..575c4d5ca --- /dev/null +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; +import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from "src/libraries/internal/Maths.sol"; + +import { UnboundedLiquidationPoolHandler } from '../../../base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol'; +import { BaseERC20PoolHandler } from './BaseERC20PoolHandler.sol'; + +abstract contract UnboundedLiquidationERC20PoolHandler is UnboundedLiquidationPoolHandler, BaseERC20PoolHandler { + + /********************************/ + /*** Settler Helper Functions ***/ + /********************************/ + + function _settleAuction( + address borrower_, + uint256 maxDepth_ + ) internal updateLocalStateAndPoolInterest { + ( + uint256 borrowerT0Debt, + uint256 collateral, + ) = _erc20Pool.borrowerInfo(borrower_); + (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); + (uint256 inflator, ) = _erc20Pool.inflatorInfo(); + + try _erc20Pool.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.wmul(collateral, _priceAt(bucketIndex)); + uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; + uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); + + 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.wdiv(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.wdiv(fenwickDeposit, _priceAt(bucketIndex)); + borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); + } + // exchange all collateral with deposit + else { + fenwickDeposits[bucketIndex] -= maxSettleableDebt; + collateral = 0; + borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); + } + } else collateral = 0; + + 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.wdiv(fenwickDeposit, inflator); + } + // bucket deposit is greater than debt + else { + fenwickDeposits[bucketIndex] -= borrowerDebt; + borrowerT0Debt = 0; + } + } + + maxDepth_ -= 1; + } + } + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol new file mode 100644 index 000000000..b10d8ac5f --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import "@std/console.sol"; + +import { Pool } from 'src/base/Pool.sol'; +import { ERC721Pool } from 'src/ERC721Pool.sol'; +import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; + +import { NFTCollateralToken } from '../../utils/Tokens.sol'; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX +} from '../base/handlers/unbounded/BaseHandler.sol'; + +import { BasicERC721PoolHandler } from './handlers/BasicERC721PoolHandler.sol'; +import { BasicInvariants } from '../base/BasicInvariants.t.sol'; +import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; + +// contains invariants for the test +contract BasicERC721PoolInvariants is BasicInvariants { + + /**************************************************************************************************************************************/ + /*** Invariant Tests ***/ + /*************************************************************************************************************************************** + + * Collateral Token + * CT2: number of tokens owned by the pool (Collateral.balanceOf(pool)) * 1e18 = sum of collateral across all borrowers (Borrower.collateral) + sum of claimable collateral across all buckets (Bucket.collateral) + * CT3: number of tokens owned by the pool (Collateral.balanceOf(pool) = length of borrower array token ids (ERC721Pool.borrowerTokenIds.length) + length of buckets array token ids (ERC721Pool.bucketTokenIds.length) + * CT4: number of borrower token ids (ERC721Pool.borrowerTokenIds.length) * 1e18 <= borrower balance (Borrower.collateral) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids + * CT5: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) are owned by pool contract (Collateral.ownerOf(tokenId)) + * CT6: in case of subset pools: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) should have a mapping of True in allowed token ids mapping (ERC721Pool.tokenIdsAllowed) + * CT7: total pledged collateral in pool (PoolBalancesState.pledgedCollateral) = sum of collateral balances across all borrowers (Borrower.collateral) + ****************************************************************************************************************************************/ + + uint256 internal constant NUM_ACTORS = 10; + + NFTCollateralToken internal _collateral; + ERC721Pool internal _erc721pool; + ERC721Pool internal _impl; + ERC721PoolFactory internal _erc721poolFactory; + BasicERC721PoolHandler internal _basicERC721PoolHandler; + + function setUp() public override virtual{ + + super.setUp(); + + uint256[] memory tokenIds; + _collateral = new NFTCollateralToken(); + _erc721poolFactory = new ERC721PoolFactory(address(_ajna)); + _impl = _erc721poolFactory.implementation(); + _erc721pool = ERC721Pool(_erc721poolFactory.deployPool(address(_collateral), address(_quote), tokenIds, 0.05 * 10**18)); + _pool = Pool(address(_erc721pool)); + + _basicERC721PoolHandler = new BasicERC721PoolHandler( + address(_erc721pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_basicERC721PoolHandler); + + excludeContract(address(_ajna)); + excludeContract(address(_collateral)); + excludeContract(address(_quote)); + excludeContract(address(_erc721poolFactory)); + excludeContract(address(_erc721pool)); + excludeContract(address(_poolInfo)); + excludeContract(address(_impl)); + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_erc721pool), bucketIndex); + previousBucketExchangeRate[bucketIndex] = exchangeRate; + } + + (, previousInterestRateUpdate) = _erc721pool.interestRateInfo(); + + // TODO: Change once this issue is resolved -> https://github.com/foundry-rs/foundry/issues/2963 + targetSender(address(0x1234)); + } + + function invariant_CT2() public useCurrentTimestamp { + uint256 collateralBalance = _collateral.balanceOf(address(_erc721pool)) * 1e18; + uint256 bucketCollateral; + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + (, uint256 collateral, , , ) = _erc721pool.bucketInfo(bucketIndex); + + bucketCollateral += collateral; + } + + assertEq(collateralBalance, bucketCollateral + _erc721pool.pledgedCollateral()); + } + + function invariant_CT3() public useCurrentTimestamp { + uint256 collateralBalance = _collateral.balanceOf(address(_erc721pool)); + + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + uint256 borrowerTokens; + + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + borrowerTokens += _erc721pool.totalBorrowerTokens(borrower); + } + + uint256 bucketTokens = _erc721pool.totalBucketTokens(); + assertEq(collateralBalance, borrowerTokens + bucketTokens); + } + + function invariant_CT4() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + uint256 borrowerTokens = _erc721pool.totalBorrowerTokens(borrower); + + (, uint256 borrowerCollateral, ) = _erc721pool.borrowerInfo(borrower); + + assertLe(borrowerTokens * 1e18, borrowerCollateral); + } + } + + function invariant_CT5() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + uint256 borrowerTokens = _erc721pool.totalBorrowerTokens(borrower); + + for (uint256 tokenIndex = 0; tokenIndex < borrowerTokens; tokenIndex++) { + uint256 borrowerTokenId = _erc721pool.borrowerTokenIds(borrower, tokenIndex); + + assertEq(_collateral.ownerOf(borrowerTokenId), address(_erc721pool)); + } + } + + uint256 bucketTokens = _erc721pool.totalBucketTokens(); + for (uint256 tokenIndex = 0; tokenIndex < bucketTokens; tokenIndex++) { + uint256 bucketTokenId = _erc721pool.bucketTokenIds(tokenIndex); + + assertEq(_collateral.ownerOf(bucketTokenId), address(_erc721pool)); + } + } + + function invariant_CT6() public useCurrentTimestamp { + if (_erc721pool.isSubset()) { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + uint256 borrowerTokens = _erc721pool.totalBorrowerTokens(borrower); + + for (uint256 tokenIndex = 0; tokenIndex < borrowerTokens; tokenIndex++) { + uint256 borrowerTokenId = _erc721pool.borrowerTokenIds(borrower, tokenIndex); + + assertTrue(_erc721pool.tokenIdsAllowed(borrowerTokenId)); + } + } + + uint256 bucketTokens = _erc721pool.totalBucketTokens(); + for (uint256 tokenIndex = 0; tokenIndex < bucketTokens; tokenIndex++) { + uint256 bucketTokenId = _erc721pool.bucketTokenIds(tokenIndex); + + assertTrue(_erc721pool.tokenIdsAllowed(bucketTokenId)); + } + } + } + + function invariant_CT7() public useCurrentTimestamp { + uint256 actorCount = IBaseHandler(_handler).getActorsCount(); + + uint256 totalCollateralPledged; + for (uint256 i = 0; i < actorCount; i++) { + address borrower = IBaseHandler(_handler).actors(i); + + (, uint256 borrowerCollateral, ) = _erc721pool.borrowerInfo(borrower); + + totalCollateralPledged += borrowerCollateral; + } + + assertEq(_erc721pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); + } + +} diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol new file mode 100644 index 000000000..b8e2ac3bb --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + BORROWER_MIN_BUCKET_INDEX, + MIN_AMOUNT, + MAX_AMOUNT +} from '../../base/handlers/unbounded/BaseHandler.sol'; +import { BasicPoolHandler } from '../../base/handlers/BasicPoolHandler.sol'; +import { UnboundedBasicPoolHandler } from '../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol'; +import { UnboundedBasicERC721PoolHandler } from './unbounded/UnboundedBasicERC721PoolHandler.sol'; +import { BaseERC721PoolHandler } from './unbounded/BaseERC721PoolHandler.sol'; + +/** + * @dev this contract manages multiple actors + * @dev methods in this contract are called in random order + * @dev randomly selects an actor contract to make a txn + */ +contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHandler { + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BaseERC721PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + + } + + /*****************************/ + /*** Lender Test Functions ***/ + /*****************************/ + + function addCollateral( + uint256 actorIndex_, + uint256 amountToAdd_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.addCollateral']++; + + // Prepare test phase + uint256 boundedAmount = _preAddCollateral(amountToAdd_); + + // Action phase + _addCollateral(boundedAmount, _lenderBucketIndex); + } + + function removeCollateral( + uint256 actorIndex_, + uint256 amountToRemove_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.removeCollateral']++; + + // Prepare test phase + uint256 boundedAmount = _preRemoveCollateral(amountToRemove_); + + // Action phase + _removeCollateral(boundedAmount, _lenderBucketIndex); + } + + /*******************************/ + /*** Borrower Test Functions ***/ + /*******************************/ + + function pledgeCollateral( + uint256 actorIndex_, + uint256 amountToPledge_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.pledgeCollateral']++; + + // Prepare test phase + uint256 boundedAmount = _prePledgeCollateral(amountToPledge_); + + // Action phase + _pledgeCollateral(boundedAmount); + + // Cleanup phase + _auctionSettleStateReset(_actor); + } + + function pullCollateral( + uint256 actorIndex_, + uint256 amountToPull_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.pullCollateral']++; + + // Prepare test phase + uint256 boundedAmount = _prePullCollateral(amountToPull_); + + // Action phase + _pullCollateral(boundedAmount); + } + + function drawDebt( + uint256 actorIndex_, + uint256 amountToBorrow_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.drawDebt']++; + + // Prepare test phase + uint256 boundedAmount = _preDrawDebt(amountToBorrow_); + + // Action phase + _drawDebt(boundedAmount); + + // Cleanup phase + _auctionSettleStateReset(_actor); + } + + function repayDebt( + uint256 actorIndex_, + uint256 amountToRepay_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.repayDebt']++; + + // Prepare test phase + uint256 boundedAmount = _preRepayDebt(amountToRepay_); + + // Action phase + _repayDebt(boundedAmount); + + // Cleanup phase + _auctionSettleStateReset(_actor); + } + + /*******************************/ + /*** Prepare Tests Functions ***/ + /*******************************/ + + function _preAddCollateral( + uint256 amountToAdd_ + ) internal pure returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, 1, 5); + } + + function _preRemoveCollateral( + uint256 amountToRemove_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRemove_, 1, 5); + + // ensure actor has collateral to remove + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if(lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); + } + + function _prePledgeCollateral( + uint256 amountToPledge_ + ) internal pure returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPledge_, 1, 5); + } + + function _prePullCollateral( + uint256 amountToPull_ + ) internal pure returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPull_, 0, MAX_AMOUNT); + } + + function _preDrawDebt( + uint256 amountToBorrow_ + ) internal override returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToBorrow_, MIN_AMOUNT, MAX_AMOUNT); + + // Pre Condition + // 1. borrower's debt should exceed minDebt + // 2. pool needs sufficent quote token to draw debt + // 3. drawDebt should not make borrower under collateralized + + // 1. borrower's debt should exceed minDebt + (uint256 debt, uint256 collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + (uint256 minDebt, , , ) = _poolInfo.poolUtilizationInfo(address(_pool)); + + if (boundedAmount_ < minDebt) boundedAmount_ = minDebt + 1; + + // TODO: Need to constrain amount so LUP > HTP + + // 2. pool needs sufficent quote token to draw debt + uint256 poolQuoteBalance = _quote.balanceOf(address(_pool)); + + if (boundedAmount_ > poolQuoteBalance) { + _addQuoteToken(boundedAmount_ * 2, LENDER_MAX_BUCKET_INDEX); + } + + // 3. drawing of addition debt will make them under collateralized + uint256 lup = _poolInfo.lup(address(_pool)); + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + + if (_collateralization(debt, collateral, lup) < 1) { + _repayDebt(debt); + + (debt, collateral, ) = _poolInfo.borrowerInfo(address(_pool), _actor); + + require(debt == 0, "borrower has debt"); + } + } + + function _preRepayDebt( + uint256 amountToRepay_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); + + // ensure actor has debt to repay + (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); + if (debt == 0) { + boundedAmount_ = _preDrawDebt(boundedAmount_); + _drawDebt(boundedAmount_); + } + } +} diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol new file mode 100644 index 000000000..6506855f1 --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; + +import { ERC721Pool } from 'src/ERC721Pool.sol'; + +import { NFTCollateralToken } from '../../../../utils/Tokens.sol'; + +import { BaseHandler } from '../../../base/handlers/unbounded/BaseHandler.sol'; + +abstract contract BaseERC721PoolHandler is BaseHandler { + + // Token + NFTCollateralToken internal _collateral; + + // ERC721Pool + ERC721Pool internal _erc721Pool; + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { + // Tokens + _collateral = NFTCollateralToken(collateral_); + + // ERC721Pool + _erc721Pool = ERC721Pool(pool_); + + // Actors + actors = _buildActors(numOfActors_); + } + + /*****************************/ + /*** Pool Helper Functions ***/ + /*****************************/ + + function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { + address[] memory actorsAddress = new address[](noOfActors_); + + for (uint i = 0; i < noOfActors_; i++) { + address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); + actorsAddress[i] = actor; + + vm.startPrank(actor); + + _quote.mint(actor, 1e45); + _quote.approve(address(_pool), 1e45); + + _collateral.mint(actor, 100); + _collateral.setApprovalForAll(address(_pool), true); + + vm.stopPrank(); + } + + return actorsAddress; + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol new file mode 100644 index 000000000..f02d91ea4 --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ERC20Pool } from 'src/ERC20Pool.sol'; +import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from "src/libraries/internal/Maths.sol"; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX +} from '../../../base/handlers/unbounded/BaseHandler.sol'; +import { UnboundedBasicPoolHandler } from "../../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol"; +import { BaseERC721PoolHandler } from './BaseERC721PoolHandler.sol'; + +/** + * @dev this contract manages multiple lenders + * @dev methods in this contract are called in random order + * @dev randomly selects a lender contract to make a txn + */ +abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, BaseERC721PoolHandler { + + /*******************************/ + /*** Lender Helper Functions ***/ + /*******************************/ + + function _addCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.addCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); + + uint256[] memory tokenIds = new uint256[](amount_); + for(uint256 i = 0; i < amount_; i++) { + tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); + } + + _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"); + } + + function _removeCollateral( + uint256 amount_, + uint256 bucketIndex_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.removeCollateral']++; + + (uint256 lpBalanceBeforeAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); + + 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"); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + /*********************************/ + /*** Borrower Helper Functions ***/ + /*********************************/ + + function _pledgeCollateral( + uint256 amount_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pledgeCollateral']++; + + // **R1**: Exchange rates are unchanged by pledging collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + uint256[] memory tokenIds = new uint256[](amount_); + for(uint256 i = 0; i < amount_; i++) { + tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); + } + + _erc721Pool.drawDebt(_actor, 0, 0, tokenIds); + } + + function _pullCollateral( + uint256 amount_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.pullCollateral']++; + + // **R2**: Exchange rates are unchanged by pulling collateral + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + exchangeRateShouldNotChange[bucketIndex] = true; + } + + try _erc721Pool.repayDebt(_actor, 0, amount_, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _drawDebt( + uint256 amount_ + ) internal virtual override updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.drawDebt']++; + + (uint256 poolDebt, , , ) = _erc721Pool.debtInfo(); + + // find bucket to borrow quote token + uint256 bucket = _erc721Pool.depositIndex(amount_ + poolDebt) - 1; + uint256 price = _poolInfo.indexToPrice(bucket); + uint256 collateralToPledge = (((amount_ * 1e18 + price / 2) / price) * 101 / 100 ) % 1e18 + 1; + + uint256[] memory tokenIds = new uint256[](collateralToPledge); + for(uint256 i = 0; i < collateralToPledge; i++) { + tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); + } + + try _erc721Pool.drawDebt(_actor, amount_, 7388, tokenIds) { + + (uint256 interestRate, ) = _erc721Pool.interestRateInfo(); + + // **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt + increaseInReserves += Maths.wmul( + amount_, _borrowFeeRate(interestRate) + ); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function _repayDebt( + uint256 amountToRepay_ + ) internal updateLocalStateAndPoolInterest { + numberOfCalls['UBBasicHandler.repayDebt']++; + + try _erc721Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } +} diff --git a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol b/tests/forge/invariants/base/BaseInvariants.sol similarity index 55% rename from tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol rename to tests/forge/invariants/base/BaseInvariants.sol index b5f8751fe..ae5df5f14 100644 --- a/tests/forge/ERC20Pool/invariants/base/InvariantsTestBase.sol +++ b/tests/forge/invariants/base/BaseInvariants.sol @@ -4,24 +4,36 @@ pragma solidity 0.8.14; import '@std/Test.sol'; -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; +import { Pool } from 'src/base/Pool.sol'; + +import { TokenWithNDecimals, BurnableToken } from '../../utils/Tokens.sol'; -import { TokenWithNDecimals, BurnableToken } from '../../../utils/Tokens.sol'; import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol'; -abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { +abstract contract BaseInvariants is InvariantsTestHelpers, Test { TokenWithNDecimals internal _quote; - TokenWithNDecimals internal _collateral; BurnableToken internal _ajna; - ERC20Pool internal _pool; - ERC20Pool internal _impl; + Pool internal _pool; PoolInfoUtils internal _poolInfo; - ERC20PoolFactory internal _poolFactory; + + // bucket exchange rate tracking + mapping(uint256 => uint256) internal previousBucketExchangeRate; + + // inflator tracking + uint256 previousInflator; + uint256 previousInflatorUpdate; + + // interest rate tracking + uint256 previousInterestRateUpdate; + uint256 previousTotalInterestEarned; + uint256 previousTotalInterestEarnedUpdate; + + // address of pool handler + address internal _handler; uint256 public currentTimestamp; @@ -36,13 +48,9 @@ abstract contract InvariantsTestBase is InvariantsTestHelpers, Test { // Tokens _ajna = new BurnableToken("Ajna", "A"); _quote = new TokenWithNDecimals("Quote", "Q", uint8(vm.envUint("QUOTE_PRECISION"))); - _collateral = new TokenWithNDecimals("Collateral", "C", uint8(vm.envUint("COLLATERAL_PRECISION"))); // Pool - _poolFactory = new ERC20PoolFactory(address(_ajna)); - _pool = ERC20Pool(_poolFactory.deployPool(address(_collateral), address(_quote), 0.05 * 10**18)); _poolInfo = new PoolInfoUtils(); - _impl = _poolFactory.implementation(); currentTimestamp = block.timestamp; } diff --git a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol similarity index 84% rename from tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol rename to tests/forge/invariants/base/BasicInvariants.t.sol index 166bf9230..fe778cb88 100644 --- a/tests/forge/ERC20Pool/invariants/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -4,20 +4,15 @@ pragma solidity 0.8.14; import "@std/console.sol"; -import { Maths } from 'src/libraries/internal/Maths.sol'; - +import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; import { LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX, - BasicPoolHandler -} from './handlers/BasicPoolHandler.sol'; - -import { InvariantsTestBase } from './base/InvariantsTestBase.sol'; -import { IBaseHandler } from './interfaces/IBaseHandler.sol'; + LENDER_MAX_BUCKET_INDEX +} from './handlers/unbounded/BaseHandler.sol'; +import { BaseInvariants } from '../base/BaseInvariants.sol'; // contains invariants for the test -contract BasicInvariants is InvariantsTestBase { +abstract contract BasicInvariants is BaseInvariants { /**************************************************************************************************************************************/ /*** Invariant Tests ***/ @@ -33,10 +28,6 @@ contract BasicInvariants is InvariantsTestBase { * Quote Token * QT1: poolQtBal + poolDebt >= totalBondEscrowed + poolDepositSize * QT2: pool t0 debt = sum of all borrower's t0 debt - - * Collateral Token - * CT1: poolCtBal >= sum of all borrower's collateral + sum of all bucket's claimable collateral - * CT7: pool Pledged collateral = sum of all borrower's pledged collateral * Loan * L1: for each Loan in loans array (LoansState.loans) starting from index 1, the corresponding address (Loan.borrower) is not 0x, the threshold price (Loan.thresholdPrice) is different than 0 @@ -56,55 +47,6 @@ contract BasicInvariants is InvariantsTestBase { * F4: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)), where j is the next index from i with deposits != 0 ****************************************************************************************************************************************/ - uint256 internal constant NUM_ACTORS = 10; - BasicPoolHandler internal _basicPoolHandler; - address internal _handler; - - // bucket exchange rate tracking - mapping(uint256 => uint256) internal previousBucketExchangeRate; - - uint256 previousInflator; - uint256 previousInflatorUpdate; - - uint256 previousInterestRateUpdate; - uint256 previousTotalInterestEarned; - uint256 previousTotalInterestEarnedUpdate; - - function setUp() public override virtual{ - - super.setUp(); - - _basicPoolHandler = new BasicPoolHandler( - address(_pool), - address(_ajna), - address(_quote), - address(_collateral), - address(_poolInfo), - NUM_ACTORS, - address(this) - ); - - _handler = address(_basicPoolHandler); - - excludeContract(address(_ajna)); - excludeContract(address(_collateral)); - excludeContract(address(_quote)); - excludeContract(address(_poolFactory)); - excludeContract(address(_pool)); - excludeContract(address(_poolInfo)); - excludeContract(address(_impl)); - - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_pool), bucketIndex); - previousBucketExchangeRate[bucketIndex] = exchangeRate; - } - - (, previousInterestRateUpdate) = _pool.interestRateInfo(); - - // TODO: Change once this issue is resolved -> https://github.com/foundry-rs/foundry/issues/2963 - targetSender(address(0x1234)); - } - // checks pool lps are equal to sum of all lender lps in a bucket function invariant_Lps_B1_B4() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); @@ -189,34 +131,6 @@ contract BasicInvariants is InvariantsTestBase { ); } - // checks pools collateral Balance to be equal to collateral pledged - function invariant_collateralBalance_CT1_CT7() public useCurrentTimestamp { - uint256 actorCount = IBaseHandler(_handler).getActorsCount(); - - uint256 totalCollateralPledged; - for (uint256 i = 0; i < actorCount; i++) { - address borrower = IBaseHandler(_handler).actors(i); - - ( , uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); - - totalCollateralPledged += borrowerCollateral; - } - - assertEq(_pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); - - // convert pool collateral balance into WAD - uint256 collateralBalance = _collateral.balanceOf(address(_pool)) * 10**(18 - _collateral.decimals()); - uint256 bucketCollateral; - - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - (, uint256 collateral, , , ) = _pool.bucketInfo(bucketIndex); - - bucketCollateral += collateral; - } - - assertGe(collateralBalance, bucketCollateral + _pool.pledgedCollateral()); - } - // checks pool debt is equal to sum of all borrowers debt function invariant_pooldebt_QT2() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); @@ -428,7 +342,7 @@ contract BasicInvariants is InvariantsTestBase { } } - function invariant_call_summary() external virtual useCurrentTimestamp { + function invariant_call_summary() public virtual useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); console.log("BBasicHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); diff --git a/tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol b/tests/forge/invariants/base/InvariantsTestHelpers.sol similarity index 100% rename from tests/forge/ERC20Pool/invariants/base/InvariantsTestHelpers.sol rename to tests/forge/invariants/base/InvariantsTestHelpers.sol diff --git a/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol b/tests/forge/invariants/base/LiquidationInvariants.t.sol similarity index 89% rename from tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol rename to tests/forge/invariants/base/LiquidationInvariants.t.sol index a747c7e69..428b2528a 100644 --- a/tests/forge/ERC20Pool/invariants/LiquidationInvariants.t.sol +++ b/tests/forge/invariants/base/LiquidationInvariants.t.sol @@ -4,17 +4,10 @@ pragma solidity 0.8.14; import "@std/console.sol"; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX -} from './handlers/BasicPoolHandler.sol'; - -import { LiquidationPoolHandler } from './handlers/LiquidationPoolHandler.sol'; +import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; import { BasicInvariants } from './BasicInvariants.t.sol'; -import { IBaseHandler } from './interfaces/IBaseHandler.sol'; -contract LiquidationInvariants is BasicInvariants { +abstract contract LiquidationInvariants is BasicInvariants { /**************************************************************************************************************************************/ /*** Invariant Tests ***/ @@ -27,27 +20,6 @@ contract LiquidationInvariants is BasicInvariants { * A5: for each auction, kicker locked bond is more than equal to auction bond * A6: if a Liquidation is not taken then the take flag (Liquidation.alreadyTaken) should be False, if already taken then the take flag should be True ****************************************************************************************************************************************/ - - LiquidationPoolHandler internal _liquidationPoolHandler; - - function setUp() public override virtual{ - - super.setUp(); - - excludeContract(address(_basicPoolHandler)); - - _liquidationPoolHandler = new LiquidationPoolHandler( - address(_pool), - address(_ajna), - address(_quote), - address(_collateral), - address(_poolInfo), - NUM_ACTORS, - address(this) - ); - - _handler = address(_liquidationPoolHandler); - } // checks sum of all borrower's t0debt is equals to total pool t0debtInAuction function invariant_debtInAuction_A1() public useCurrentTimestamp { @@ -146,7 +118,7 @@ contract LiquidationInvariants is BasicInvariants { } } - function invariant_call_summary() external virtual override useCurrentTimestamp { + function invariant_call_summary() public virtual override useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); console.log("BLiquidationHandler.addQuoteToken ", IBaseHandler(_handler).numberOfCalls("BBasicHandler.addQuoteToken")); diff --git a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol b/tests/forge/invariants/base/ReserveInvariants.t.sol similarity index 76% rename from tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol rename to tests/forge/invariants/base/ReserveInvariants.t.sol index df6f3fafc..2b687a14d 100644 --- a/tests/forge/ERC20Pool/invariants/ReserveInvariants.t.sol +++ b/tests/forge/invariants/base/ReserveInvariants.t.sol @@ -2,21 +2,12 @@ pragma solidity 0.8.14; -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; - import "@std/console.sol"; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX -} from './handlers/BasicPoolHandler.sol'; - -import { ReservePoolHandler } from './handlers/ReservePoolHandler.sol'; +import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; import { LiquidationInvariants } from './LiquidationInvariants.t.sol'; -import { IBaseHandler } from './interfaces/IBaseHandler.sol'; -contract ReserveInvariants is LiquidationInvariants { +abstract contract ReserveInvariants is LiquidationInvariants { /**************************************************************************************************************************************/ /*** Invariant Tests ***/ @@ -35,27 +26,6 @@ contract ReserveInvariants is LiquidationInvariants { * RE11: Reserves decrease by claimableReserves by startClaimableReserveAuction * RE12: Reserves decrease by amount of reserve used to settle a auction ****************************************************************************************************************************************/ - - ReservePoolHandler internal _reservePoolHandler; - - function setUp() public override virtual { - - super.setUp(); - - excludeContract(address(_liquidationPoolHandler)); - - _reservePoolHandler = new ReservePoolHandler( - address(_pool), - address(_ajna), - address(_quote), - address(_collateral), - address(_poolInfo), - NUM_ACTORS, - address(this) - ); - - _handler = address(_reservePoolHandler); - } function invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12() public useCurrentTimestamp { diff --git a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol new file mode 100644 index 000000000..28d441a1f --- /dev/null +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { Maths } from 'src/libraries/internal/Maths.sol'; + +import { + LENDER_MIN_BUCKET_INDEX, + LENDER_MAX_BUCKET_INDEX, + MIN_AMOUNT, + MAX_AMOUNT +} from './unbounded/BaseHandler.sol'; +import { UnboundedBasicPoolHandler } from './unbounded/UnboundedBasicPoolHandler.sol'; + +/** + * @dev this contract manages multiple actors + * @dev methods in this contract are called in random order + * @dev randomly selects an actor contract to make a txn + */ +abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { + + /*****************************/ + /*** Lender Test Functions ***/ + /*****************************/ + + function addQuoteToken( + uint256 actorIndex_, + uint256 amountToAdd_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.addQuoteToken']++; + + // Prepare test phase + uint256 boundedAmount = _preAddQuoteToken(amountToAdd_); + + // Action phase + _addQuoteToken(boundedAmount, _lenderBucketIndex); + } + + function removeQuoteToken( + uint256 actorIndex_, + uint256 amountToRemove_, + uint256 bucketIndex_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + numberOfCalls['BBasicHandler.removeQuoteToken']++; + + // Prepare test phase + uint256 boundedAmount = _preRemoveQuoteToken(amountToRemove_); + + // Action phase + _removeQuoteToken(boundedAmount, _lenderBucketIndex); + } + + function moveQuoteToken( + uint256 actorIndex_, + uint256 amountToMove_, + uint256 fromIndex_, + uint256 toIndex_ + ) external useRandomActor(actorIndex_) useTimestamps { + numberOfCalls['BBasicHandler.moveQuoteToken']++; + + // Prepare test phase + ( + uint256 boundedFromIndex, + uint256 boundedToIndex, + uint256 boundedAmount + ) = _preMoveQuoteToken(amountToMove_, fromIndex_, toIndex_); + + // Action phase + _moveQuoteToken(boundedAmount, boundedFromIndex, boundedToIndex); + } + + function transferLps( + uint256 fromActorIndex_, + uint256 toActorIndex_, + uint256 lpsToTransfer_, + uint256 bucketIndex_ + ) external useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + // Prepare test phase + (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); + + // Action phase + _increaseLPsAllowance(receiver, _lenderBucketIndex, boundedLps); + _transferLps(_actor, receiver, _lenderBucketIndex); + } + + /*******************************/ + /*** Prepare Tests Functions ***/ + /*******************************/ + + function _preAddQuoteToken( + uint256 amountToAdd_ + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); + } + + function _preRemoveQuoteToken( + uint256 amountToRemove_ + ) internal returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); + + // ensure actor has quote tokens to remove + (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if (lpBalanceBefore == 0) { + _addQuoteToken(boundedAmount_, _lenderBucketIndex); + } + } + + function _preMoveQuoteToken( + uint256 amountToMove_, + 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_AMOUNT, MAX_AMOUNT); + + // ensure actor has LPs to move + (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); + if (lpBalance == 0) _addQuoteToken(boundedAmount_, boundedToIndex_); + + (uint256 lps, ) = _pool.lenderInfo(boundedFromIndex_, _actor); + // restrict amount to move by available deposit inside bucket + uint256 availableDeposit = _poolInfo.lpsToQuoteTokens(address(_pool), lps, boundedFromIndex_); + boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); + } + + function _preTransferLps( + uint256 toActorIndex_, + uint256 lpsToTransfer_ + ) internal returns (address receiver_, uint256 boundedLps_) { + // ensure actor has LPs to transfer + (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + if(senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); + + (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); + + boundedLps_ = constrictToRange(lpsToTransfer_, 0, senderLpBalance); + + receiver_ = actors[constrictToRange(toActorIndex_, 0, actors.length - 1)]; + } + + function _preDrawDebt( + uint256 amountToBorrow_ + ) internal virtual returns (uint256 boundedAmount_); +} diff --git a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol similarity index 55% rename from tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol rename to tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol index 40f205435..8c24a0ebf 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol @@ -5,28 +5,13 @@ pragma solidity 0.8.14; import { LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX, MIN_AMOUNT, - MAX_AMOUNT, - BaseHandler -} from '../base/BaseHandler.sol'; -import { UnboundedLiquidationPoolHandler } from '../base/UnboundedLiquidationPoolHandler.sol'; + MAX_AMOUNT +} from './unbounded/BaseHandler.sol'; +import { UnboundedLiquidationPoolHandler } from './unbounded/UnboundedLiquidationPoolHandler.sol'; +import { BasicPoolHandler } from './BasicPoolHandler.sol'; -import { BasicPoolHandler } from './BasicPoolHandler.sol'; - -contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHandler { - - constructor( - address pool_, - address ajna_, - address quote_, - address collateral_, - address poolInfo_, - uint256 numOfActors_, - address testContract_ - ) BasicPoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { - - } +abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHandler { /*****************************/ /*** Kicker Test Functions ***/ @@ -58,30 +43,6 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan /*** Taker Test Functions ***/ /****************************/ - function takeAuction( - uint256 borrowerIndex_, - uint256 amount_, - uint256 actorIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { - numberOfCalls['BLiquidationHandler.takeAuction']++; - - amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); - - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - - address borrower = actors[borrowerIndex_]; - address taker = _actor; - - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - - if (kickTime == 0) _kickAuction(borrowerIndex_, amount_ * 100, actorIndex_); - - changePrank(taker); - // skip time to make auction takeable - vm.warp(block.timestamp + 2 hours); - _takeAuction(borrower, amount_, taker); - } - function bucketTake( uint256 borrowerIndex_, uint256 bucketIndex_, @@ -106,35 +67,6 @@ contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, BasicPoolHan _bucketTake(taker, borrower, depositTake_, bucketIndex_); } - /******************************/ - /*** Settler Test Functions ***/ - /******************************/ - - function settleAuction( - uint256 actorIndex_, - uint256 borrowerIndex_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - - address borrower = actors[borrowerIndex_]; - uint256 maxDepth = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX; - - address actor = _actor; - - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - - if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); - - changePrank(actor); - // skip time to make auction clearable - vm.warp(block.timestamp + 73 hours); - _settleAuction(borrower, maxDepth); - - _auctionSettleStateReset(borrower); - } - /************************/ /*** Helper Functions ***/ /************************/ diff --git a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol similarity index 67% rename from tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol rename to tests/forge/invariants/base/handlers/ReservePoolHandler.sol index 0238148b5..ef7d05555 100644 --- a/tests/forge/ERC20Pool/invariants/handlers/ReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol @@ -2,26 +2,13 @@ pragma solidity 0.8.14; -import 'src/libraries/internal/Maths.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; -import { UnboundedReservePoolHandler } from '../base/UnboundedReservePoolHandler.sol'; +import { UnboundedReservePoolHandler } from '../../base/handlers/unbounded/UnboundedReservePoolHandler.sol'; +import { MIN_AMOUNT } from '../../base/handlers/unbounded/BaseHandler.sol'; +import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; -import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; -import { MIN_AMOUNT } from '../base/BaseHandler.sol'; - -contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandler { - - constructor( - address pool_, - address ajna_, - address quote_, - address collateral_, - address poolInfo_, - uint256 numOfActors_, - address testContract_ - ) LiquidationPoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { - - } +abstract contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandler { /*******************************/ /*** Reserves Test Functions ***/ diff --git a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol similarity index 92% rename from tests/forge/ERC20Pool/invariants/base/BaseHandler.sol rename to tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index 0b2ed40ac..f36e7ce75 100644 --- a/tests/forge/ERC20Pool/invariants/base/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -2,12 +2,9 @@ pragma solidity 0.8.14; -import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; - import '@std/Test.sol'; -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; +import { Pool } from 'src/base/Pool.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; import { PoolCommons } from 'src/libraries/external/PoolCommons.sol'; import { @@ -15,11 +12,11 @@ import { MAX_PRICE, MIN_PRICE } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; -import { TokenWithNDecimals, BurnableToken } from '../../../utils/Tokens.sol'; +import { TokenWithNDecimals, BurnableToken } from '../../../../utils/Tokens.sol'; -import 'src/libraries/internal/Maths.sol'; -import '../interfaces/ITestBase.sol'; +import '../../../interfaces/ITestBase.sol'; uint256 constant LENDER_MIN_BUCKET_INDEX = 2570; uint256 constant LENDER_MAX_BUCKET_INDEX = 2572; @@ -34,12 +31,11 @@ abstract contract BaseHandler is Test { // Tokens TokenWithNDecimals internal _quote; - TokenWithNDecimals internal _collateral; BurnableToken internal _ajna; // Pool - ERC20Pool internal _pool; + Pool internal _pool; PoolInfoUtils internal _poolInfo; // Test invariant contract @@ -75,23 +71,17 @@ abstract contract BaseHandler is Test { address pool_, address ajna_, address quote_, - address collateral_, address poolInfo_, - uint256 numOfActors_, address testContract_ ) { // Tokens _ajna = BurnableToken(ajna_); _quote = TokenWithNDecimals(quote_); - _collateral = TokenWithNDecimals(collateral_); // Pool - _pool = ERC20Pool(pool_); + _pool = Pool(pool_); _poolInfo = PoolInfoUtils(poolInfo_); - // Actors - actors = _buildActors(numOfActors_); - // Test invariant contract testContract = ITestBase(testContract_); } @@ -154,27 +144,6 @@ abstract contract BaseHandler is Test { /*** Pool Helper Functions ***/ /*****************************/ - function _buildActors(uint256 noOfActors_) internal returns(address[] memory) { - address[] memory actorsAddress = new address[](noOfActors_); - - for (uint i = 0; i < noOfActors_; i++) { - address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i)))); - actorsAddress[i] = actor; - - vm.startPrank(actor); - - _quote.mint(actor, 1e45); - _quote.approve(address(_pool), 1e45); - - _collateral.mint(actor, 1e45); - _collateral.approve(address(_pool), 1e45); - - vm.stopPrank(); - } - - return actorsAddress; - } - function _updatePoolState() internal { _pool.updateInterest(); } diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol similarity index 54% rename from tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol rename to tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol index f04e77384..03f1bf874 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedBasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol @@ -2,19 +2,10 @@ pragma solidity 0.8.14; -import { ERC20Pool } from 'src/ERC20Pool.sol'; -import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; -import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; +import { _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from "src/libraries/internal/Maths.sol"; -import "src/libraries/internal/Maths.sol"; - -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX, - BaseHandler -} from './BaseHandler.sol'; +import { BaseHandler } from './BaseHandler.sol'; /** * @dev this contract manages multiple lenders @@ -35,8 +26,8 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); (uint256 poolDebt, , ,) = _pool.debtInfo(); - uint256 lupIndex = _pool.depositIndex(poolDebt); - (uint256 interestRate, ) = _pool.interestRateInfo(); + uint256 lupIndex = _pool.depositIndex(poolDebt); + (uint256 interestRate, ) = _pool.interestRateInfo(); try _pool.addQuoteToken(amount_, bucketIndex_, block.timestamp + 1 minutes) { @@ -120,48 +111,6 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { } } - function _addCollateral( - uint256 amount_, - uint256 bucketIndex_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.addCollateral']++; - - (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - - _pool.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, ) = _pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); - } - - function _removeCollateral( - uint256 amount_, - uint256 bucketIndex_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.removeCollateral']++; - - (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - - try _pool.removeCollateral(amount_, bucketIndex_) { - - // **R6**: Exchange rates are unchanged by removing collateral token from a bucket - exchangeRateShouldNotChange[bucketIndex_] = true; - - // Post action condition - (uint256 lpBalanceAfterAction, ) = _pool.lenderInfo(bucketIndex_, _actor); - require(lpBalanceAfterAction < lpBalanceBeforeAction, "LP balance should decrease"); - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } - function _increaseLPsAllowance( address receiver_, uint256 bucketIndex_, @@ -202,75 +151,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { } } - /*********************************/ - /*** Borrower Helper Functions ***/ - /*********************************/ - - function _pledgeCollateral( - uint256 amount_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.pledgeCollateral']++; - - // **R1**: Exchange rates are unchanged by pledging collateral - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - exchangeRateShouldNotChange[bucketIndex] = true; - } - - _pool.drawDebt(_actor, 0, 0, amount_); - } - - function _pullCollateral( - uint256 amount_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.pullCollateral']++; - - // **R2**: Exchange rates are unchanged by pulling collateral - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - exchangeRateShouldNotChange[bucketIndex] = true; - } - - try _pool.repayDebt(_actor, 0, amount_, _actor, 7388) { - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } - function _drawDebt( uint256 amount_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.drawDebt']++; - - (uint256 poolDebt, , ,) = _pool.debtInfo(); - - // find bucket to borrow quote token - uint256 bucket = _pool.depositIndex(amount_ + poolDebt) - 1; - uint256 price = _poolInfo.indexToPrice(bucket); - uint256 collateralToPledge = ((amount_ * 1e18 + price / 2) / price) * 101 / 100 + 1; - - try _pool.drawDebt(_actor, amount_, 7388, collateralToPledge) { - - (uint256 interestRate, ) = _pool.interestRateInfo(); - - // **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt - increaseInReserves += Maths.wmul( - amount_, _borrowFeeRate(interestRate) - ); - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } - - function _repayDebt( - uint256 amountToRepay_ - ) internal updateLocalStateAndPoolInterest { - numberOfCalls['UBBasicHandler.repayDebt']++; - - try _pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } + ) internal virtual; } diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol similarity index 58% rename from tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol rename to tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol index f1b639512..ec9fdde17 100644 --- a/tests/forge/ERC20Pool/invariants/base/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -2,16 +2,9 @@ pragma solidity 0.8.14; -import { _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; -import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; +import { Maths } from 'src/libraries/internal/Maths.sol'; -import 'src/libraries/internal/Maths.sol'; - -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BaseHandler -} from './BaseHandler.sol'; +import { BaseHandler } from './BaseHandler.sol'; abstract contract UnboundedLiquidationPoolHandler is BaseHandler { @@ -175,91 +168,4 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } } - /********************************/ - /*** Settler Helper Functions ***/ - /********************************/ - - function _settleAuction( - address borrower_, - uint256 maxDepth_ - ) internal updateLocalStateAndPoolInterest { - ( - uint256 borrowerT0Debt, - uint256 collateral, - ) = _pool.borrowerInfo(borrower_); - (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - (uint256 inflator, ) = _pool.inflatorInfo(); - - try _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.wmul(collateral, _priceAt(bucketIndex)); - uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); - - 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.wdiv(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.wdiv(fenwickDeposit, _priceAt(bucketIndex)); - borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); - } - // exchange all collateral with deposit - else { - fenwickDeposits[bucketIndex] -= maxSettleableDebt; - collateral = 0; - borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); - } - } else collateral = 0; - - 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.wdiv(fenwickDeposit, inflator); - } - // bucket deposit is greater than debt - else { - fenwickDeposits[bucketIndex] -= borrowerDebt; - borrowerT0Debt = 0; - } - } - - maxDepth_ -= 1; - } - } - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } } diff --git a/tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol similarity index 100% rename from tests/forge/ERC20Pool/invariants/base/UnboundedReservePoolHandler.sol rename to tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol diff --git a/tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol b/tests/forge/invariants/interfaces/IBaseHandler.sol similarity index 100% rename from tests/forge/ERC20Pool/invariants/interfaces/IBaseHandler.sol rename to tests/forge/invariants/interfaces/IBaseHandler.sol diff --git a/tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol b/tests/forge/invariants/interfaces/ITestBase.sol similarity index 100% rename from tests/forge/ERC20Pool/invariants/interfaces/ITestBase.sol rename to tests/forge/invariants/interfaces/ITestBase.sol diff --git a/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol new file mode 100644 index 000000000..8d89bc290 --- /dev/null +++ b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { BasicERC20PoolInvariants } from "../../invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol"; + +contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_Underflow_1() external { + _basicERC20PoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000); + + // check invariants hold true + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_exchange_rate_1() external { + _basicERC20PoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809); + _basicERC20PoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592); + _basicERC20PoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_2() external { + _basicERC20PoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); + _basicERC20PoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _basicERC20PoolHandler.removeCollateral(1, 1e36, 2570); + _basicERC20PoolHandler.removeQuoteToken(1, 1e36, 2570); + _basicERC20PoolHandler.removeCollateral(2, 1e36, 2570); + _basicERC20PoolHandler.removeQuoteToken(2, 1e36, 2570); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test will fail when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_3() external { + _basicERC20PoolHandler.addQuoteToken(2842, 304, 2468594405605444095992); + _basicERC20PoolHandler.addCollateral(0, 1, 3); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _basicERC20PoolHandler.removeCollateral(0, 1, 3); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_4() external { + _basicERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 587135207579305083672251579076072787077); + _basicERC20PoolHandler.removeCollateral(712291886391993882782748602346033231793324080118979183300958, 673221151277569661050873992210938589, 999999997387885196930781163353866909746906615); + _basicERC20PoolHandler.removeCollateral(4434852123445331038838, 92373980881732279172264, 16357203); + _basicERC20PoolHandler.addQuoteToken(6532756, 16338, 2488340072929715905208495398161339232954907500634); + _basicERC20PoolHandler.removeCollateral(934473801621702106582064701468475360, 999999998588451849650292641565069384488310108, 2726105246641027837873401505120164058057757115396); + _basicERC20PoolHandler.addQuoteToken(0, 3272, 688437777000000000); + _basicERC20PoolHandler.removeQuoteToken(36653992905059663682442427, 3272, 688437777000000000); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_5() external { + _basicERC20PoolHandler.drawDebt(1156, 1686); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicERC20PoolHandler.addQuoteToken(711, 2161, 2012); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 1, buckets = [2570] + function test_regression_exchange_rate_6() external { + _basicERC20PoolHandler.addCollateral(999999999000000000000000081002632733724231666, 999999999243662968633890481597751057821356823, 1827379824097500721086759239664926559); + _basicERC20PoolHandler.addQuoteToken(108018811574020559, 3, 617501271956497833026154369680502407518122199901237699791086943); + _basicERC20PoolHandler.addCollateral(95036573736725249741129171676163161793295193492729984020, 5009341426566798172861627799, 2); + _basicERC20PoolHandler.removeCollateral(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 5814100241); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + // Fixed with commit -> https://github.com/ajna-finance/contracts/pull/613/commits/f106f0f7c96c1662325bdb5151fd745544e6dce0 + function test_regression_exchange_rate_7() external { + _basicERC20PoolHandler.addCollateral(999999999249784004703856120761629301735818638, 15200, 2324618456838396048595845067026807532884041462750983926777912015561); + _basicERC20PoolHandler.addQuoteToken(0, 2, 60971449684543878); + _basicERC20PoolHandler.addCollateral(0, 648001392760875820320327007315181208349883976901103343226563974622543668416, 38134304133913510899173609232567613); + _basicERC20PoolHandler.removeCollateral(0, 1290407354289435191451647900348688457414638662069174249777953, 125945131546441554612275631955778759442752893948134984981883798); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 + function test_regression_exchange_rate_8() external { + _basicERC20PoolHandler.drawDebt(0, 10430); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicERC20PoolHandler.addCollateral(86808428701435509359888008280539191473421, 35, 89260656586096811497271673595050); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_exchange_rate_9() external { + _basicERC20PoolHandler.addQuoteToken(179828875014111774829603408358905079754763388655646874, 39999923045226513122629818514849844245682430, 12649859691422584279364490330583846883); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicERC20PoolHandler.addCollateral(472, 2100, 11836); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + _basicERC20PoolHandler.pledgeCollateral(7289, 8216); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_fenwick_deposit_1() external { + _basicERC20PoolHandler.addQuoteToken(60321923115154876306287876901335341390357684483818363750, 2, 0); + _basicERC20PoolHandler.repayDebt(58055409653178, 2); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_fenwick_deposit_2() external { + _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2146593659305556796718319988088528090847459411703413796483450011160); + _basicERC20PoolHandler.addCollateral(16885296866566559818671993560820380984757301691657405859955072474117, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 7764878663795446754367); + _basicERC20PoolHandler.removeCollateral(999999997000000000000000000000000000000756426, 7366, 4723); + _basicERC20PoolHandler.addQuoteToken(5673, 8294, 11316); + _basicERC20PoolHandler.moveQuoteToken(919997327910338711763724656061931477, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3933155006830995444792575696); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_fenwick_deposit_3() external { + _basicERC20PoolHandler.pullCollateral(64217420783566726909348297066823202824683000164554083, 651944294303386510182040138076901697073); + _basicERC20PoolHandler.removeQuoteToken(172614182, 2999, 725); + _basicERC20PoolHandler.addQuoteToken(52646814442098488638488433580148374391481084017027388775686120188766352301, 5021, 16410); + _basicERC20PoolHandler.moveQuoteToken(2, 1, 3, 11769823729834119405789456482320067049929344685247053661486); + _basicERC20PoolHandler.moveQuoteToken(1, 2833727997543655292227288672285470, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 7962018528962356191057551420322350); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_4() external { + _basicERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 2, 1267); + _basicERC20PoolHandler.pledgeCollateral(1700127358962530, 0); + _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_5() external { + _basicERC20PoolHandler.repayDebt(281, 1502); + _basicERC20PoolHandler.addCollateral(5529, 1090, 5431); + _basicERC20PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_deposit_6() external { + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _basicERC20PoolHandler.addQuoteToken(1000000000000000, 19319, 308); + _basicERC20PoolHandler.pullCollateral(4218, 4175); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_fenwick_prefixSum_1() external { + _basicERC20PoolHandler.addQuoteToken(5851, 999999999999999999999999999999000087, 1938); + _basicERC20PoolHandler.addCollateral(135454721201807374404103595951250949, 172411742705067521609848985260337891060745418778973, 3); + _basicERC20PoolHandler.pledgeCollateral(2, 185978674898652363737734333012844452989790885966093618883814734917759475); + _basicERC20PoolHandler.moveQuoteToken(976453319, 2825105681459470134743617749102858205411027017903767825282483319, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 145056082857394229503325854914710239303685607721150607568547620026); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_fenwick_index_1() external { + _basicERC20PoolHandler.addQuoteToken(3056, 915, 1594); + _basicERC20PoolHandler.pullCollateral(274694202801760577094218807, 1); + _basicERC20PoolHandler.addQuoteToken(1088, 3407, 3555); + _basicERC20PoolHandler.addCollateral(1557, 13472, 15303); + _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 40692552539277917058910464963); + _basicERC20PoolHandler.pullCollateral(1131485716992204156645660898919702, 30971207810832254868222941038507448); + _basicERC20PoolHandler.removeCollateral(27428712668923640148402320299830959263828759458932482391338247903954077260349, 1136, 3944); + _basicERC20PoolHandler.moveQuoteToken(9746204317995874651524496302383356801834068305156642323380998069579800880, 1723109236200550802774859945265636287, 3213180193920898024510373220802133410941904907229061207617048152428481, 0); + + invariant_fenwick_bucket_index_F3(); + } + + function test_regression_transferLps_1() external { + _basicERC20PoolHandler.transferLps(0, 1, 200, 2570); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_transferLps_2() external { + _basicERC20PoolHandler.transferLps(37233021465377552730514154972012012669272, 45957263314208417069590941186697869465410494677646946058359554, 405, 89727160292150007024940); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_transferLps_3() external { + _basicERC20PoolHandler.transferLps(1795, 6198, 3110, 11449); + + invariant_Bucket_deposit_time_B5_B6_B7(); + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_pull_collateral_when_encumbered_greater_than_pledged() external { + _basicERC20PoolHandler.drawDebt(1535776046383997344779595, 5191646246012456798576386242824793107669233); + _basicERC20PoolHandler.transferLps(17293, 19210, 227780, 999999999999999999999999999999999999999999997); + _basicERC20PoolHandler.removeQuoteToken(0, 0, 2); + _basicERC20PoolHandler.pullCollateral(115, 149220); + } + + function test_regression_incorrect_zero_deposit_buckets_1() external { + _basicERC20PoolHandler.repayDebt(15119, 6786); + _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1578322581132549441186648538841, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_fenwick_index_2() external { + uint256 depositAt2570 = 570036521745120847917211; + uint256 depositAt2571 = _basicERC20PoolHandler.constrictToRange(2578324552477056269186646552413, 1e6, 1e28); + uint256 depositAt2572 = _basicERC20PoolHandler.constrictToRange(1212, 1e6, 1e28); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2570, 2570); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2571, 2571); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2572, 2572); + assertEq(_pool.depositIndex(depositAt2570), 2570); + assertEq(_pool.depositIndex(depositAt2570 + depositAt2571), 2571); + assertEq(_pool.depositIndex(depositAt2570 + depositAt2571 + depositAt2572), 2572); + } + + function test_regression_collateralBalance_CT1_CT7() external { + _basicERC20PoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _basicERC20PoolHandler.repayDebt(2712912126128356234217, 251720382485531952743041849848); + _basicERC20PoolHandler.addQuoteToken(253022590763482364356576159, 999999999999999273028438503236995092261608400, 712808213364422679443324012750); + _basicERC20PoolHandler.removeQuoteToken(121890555084215923472733925382, 0, 3); + + invariant_collateralBalance_CT1_CT7(); + } + + // TODO: poolBalance + poolDebt >= _pool.depositSize fails by 1 unit of WAD (1.000115588871659711 >= 1.000115588871659712) + function test_regression_invariant_quoteTokenBalance_QT1() external { + _basicERC20PoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098); + _basicERC20PoolHandler.repayDebt(1, 2); + _basicERC20PoolHandler.removeCollateral(200953640940463935290718680397023889633667961549, 2481, 3); + _basicERC20PoolHandler.moveQuoteToken(695230664226651211376892782958299806602599384639648126900062519785408512, 1000115588871659705, 22812, 1955101796782211288928817909562); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639932, 103); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_fenwick_deposit_8() external { + _basicERC20PoolHandler.drawDebt(226719918559509764892175185709, 228676957600917178383525685311331); + + invariant_fenwick_depositAtIndex_F1(); + } +} diff --git a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol new file mode 100644 index 000000000..c97b3615a --- /dev/null +++ b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationERC20PoolInvariants } from "../../invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol"; + +contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_quote_token() external { + _liquidationERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_arithmetic_overflow() external { + _liquidationERC20PoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903); + _liquidationERC20PoolHandler.addQuoteToken(3487, 871, 1654); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_bucket_take_lps() external { + _liquidationERC20PoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.addQuoteToken(1, 20033186019073, 1); + _liquidationERC20PoolHandler.bucketTake(0, 0, false, 2876997751); + + invariant_Lps_B1_B4(); + } + + function test_regression_interest_rate() external { + _liquidationERC20PoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769); + + invariant_interest_rate_I1(); + } + + function test_regression_incorrect_no_of_borrowers() external { + _liquidationERC20PoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513); + _liquidationERC20PoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271); + _liquidationERC20PoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294); + _liquidationERC20PoolHandler.moveQuoteToken(47205392335275917691737183012282140599753693978176314740917, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 164043848691337333691028718232); + _liquidationERC20PoolHandler.kickAuction(184206711567329609153924955630229148705869686378631519380021040314, 78351, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.kickAuction(3, 199726916764352560035199423206927461876998880387108455962754538835220966553, 3); + _liquidationERC20PoolHandler.removeQuoteToken(999999991828440064944955196599190431639924811, 2781559202773230142346489450532860130, 3000000005240421579956496007310960085855569344); + _liquidationERC20PoolHandler.pullCollateral(48768502867710912107594904694036421700, 275047566877984818806178837359260100); + _liquidationERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, false, 8154570107391684241724530527782571978369827827856399749867491880); + _liquidationERC20PoolHandler.removeCollateral(43733538637150108518954934566131291302796656384802361118757432084573, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.addQuoteToken(1, 2, 2); + _liquidationERC20PoolHandler.repayDebt(647805461526201272, 0); + _liquidationERC20PoolHandler.kickAuction(1019259585194528028904148545812353964867041444572537077023497678982801, 58796345025472936970320, 131319002678489819637546489086162345032717166507611595521); + _liquidationERC20PoolHandler.moveQuoteToken(2, 2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC20PoolHandler.moveQuoteToken(6164937621056362865643346803975636714, 4, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 315548939052682258); + _liquidationERC20PoolHandler.repayDebt(2987067394366841692658, 170206016570563384086766968869520628); + _liquidationERC20PoolHandler.pledgeCollateral(3558446182295495994762049031, 0); + _liquidationERC20PoolHandler.drawDebt(4525700839008283200312069904720925039, 3000000000753374912785563581177665475703155339); + _liquidationERC20PoolHandler.kickAuction(1, 3559779948348618822016735773117619950447774, 218801416747720); + _liquidationERC20PoolHandler.addQuoteToken(1469716416900282992357252011629715552, 13037214114647887147246343731476169800, 984665637618013480616943810604306792); + _liquidationERC20PoolHandler.pullCollateral(438961419917818200942534689247815826455600131, 64633474453314038763068322072915580384442279897841981); + + invariant_auctions_A3_A4(); + } + + // test was failing due to deposit time update even if kicker lp reward is 0. + // resolved with PR: https://github.com/ajna-finance/contracts/pull/674 + function test_regression_bucket_deposit_time() external { + _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 2079356830967144967054363629631641573895835179323954988585146991431, 233005625580787863707944); + _liquidationERC20PoolHandler.bucketTake(21616, 1047473235778002354, false, 1062098588952039043823357); + _liquidationERC20PoolHandler.bucketTake(1673497622984405133414814181152, 94526073941076989987362055170246, false, 1462); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_transfer_taker_lps_bucket_deposit_time() external { + _liquidationERC20PoolHandler.settleAuction(3637866246331061119113494215, 0, 6163485280468362485998190762304829820899757798629605592174295845105660515); + _liquidationERC20PoolHandler.transferLps(1610, 1000000000018496758270674070884, 168395863093969200027183125335, 2799494920515362640996160058); + _liquidationERC20PoolHandler.bucketTake(0, 10619296457595008969473693936299982020664977642271808785891719078511288, true, 1681500683437506364426133778273769573223975355182845498494263153646356302); + + invariant_Bucket_deposit_time_B5_B6_B7(); + } + + function test_regression_invariant_fenwick_depositAtIndex_F1() external { + _liquidationERC20PoolHandler.moveQuoteToken(4058, 2725046678043704335543997294802562, 16226066, 4284); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_depositKick() external { + _liquidationERC20PoolHandler.repayDebt(13418, 1160); + _liquidationERC20PoolHandler.kickWithDeposit(143703836638834364678, 470133688850921941603); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_incorrect_take_2() external { + _liquidationERC20PoolHandler.kickAuction(13452, 7198, 11328); + _liquidationERC20PoolHandler.takeAuction(6772, 18720, 6668); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1666258487708695528254610529989951, 490873240291829575083322665078478117042861655783753); + + invariant_auction_taken_A6(); + } + + function test_regression_invariant_exchange_rate_bucket_take_1() external { + _liquidationERC20PoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); + _liquidationERC20PoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); + _liquidationERC20PoolHandler.bucketTake(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_invariant_exchange_rate_bucket_take_2() external { + _liquidationERC20PoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); + _liquidationERC20PoolHandler.settleAuction(186120755740, 2, 59199623628501455128); + _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); + _liquidationERC20PoolHandler.bucketTake(2, 259574184, true, 248534890472324170412180243783490514876275); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + + function test_regression_quote_token_2() external { + _liquidationERC20PoolHandler.kickAuction(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.kickAuction(416882035302092397436677640325827, 7379, 253058086367250264569525665396366); + _liquidationERC20PoolHandler.kickAuction(95740057146806695735694068330212313517380414204596464841344800376300745, 15462030827034, 17811087070659573835739283446817); + _liquidationERC20PoolHandler.drawDebt(91685640224888183606335500279, 3284161781338443742266950748717011); + _liquidationERC20PoolHandler.settleAuction(366366807138151363686, 2, 39227118695514892784493088788799944161631371060); + + invariant_quoteTokenBalance_QT1(); + } + function test_regression_invariant_settle_F1_1() external { + _liquidationERC20PoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980); + _liquidationERC20PoolHandler.kickAuction(5135, 1752, 6350); + _liquidationERC20PoolHandler.kickAuction(142699, 4496, 4356); + _liquidationERC20PoolHandler.moveQuoteToken(1173, 1445, 792325212, 447); + _liquidationERC20PoolHandler.settleAuction(18308, 3145, 947); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F1_2() external { + _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); + _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); + _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F1_3() external { + _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); + _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F2_1() external { + _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); + _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); + _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_invariant_settle_F2_2() external { + _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); + _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_invariant_settle_F1_4() external { + _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); + _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); + _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); + _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); + _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_settle_F2_3() external { + _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); + _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); + _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); + _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); + _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_invariant_F3_1() external { + _liquidationERC20PoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3731592205777443374190, 2); + _liquidationERC20PoolHandler.takeAuction(3554599780774102176805971372130467746, 140835031537485528703906318530162192, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.repayDebt(2692074105646752292572533908391, 1968526964305399089154844418825); + _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 4553829); + _liquidationERC20PoolHandler.bucketTake(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 0); + _liquidationERC20PoolHandler.drawDebt(626971501456142588551128155365, 816763288150043968438676); + _liquidationERC20PoolHandler.pullCollateral(381299861468989210101433912, 999999999999997998400442008957368645662570165); + + invariant_fenwick_bucket_index_F3(); + } + + function test_regression_invariant_F3_2() external { + _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); + _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); + _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); + _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_fenwick_bucket_index_F3(); + } + + function test_regression_invariant_F4_1() external { + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 127546297848367334892478587751, 723921922395815633171615243621131242188407029895233162931857565302); + _liquidationERC20PoolHandler.removeQuoteToken(2, 2, 7361820555); + _liquidationERC20PoolHandler.takeAuction(85885591922376805486065427318859822458293427950603, 8526258315228761831408142393759013524255378290706574861831877477, 1267004887455971938409309909682740381503049590444968840223); + _liquidationERC20PoolHandler.drawDebt(663777721413606329209923101072, 946300054291644291801213511570); + _liquidationERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); + _liquidationERC20PoolHandler.addQuoteToken(9360900796482582322800, 694431436637841996793959397509, 553923154643858021986449189292); + _liquidationERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 34469655866078951331675076928366708920312931751567797); + _liquidationERC20PoolHandler.bucketTake(0, 1, false, 3); + _liquidationERC20PoolHandler.bucketTake(1190209291225920034207711400729307351194726, 2492241351445208059551299524117408972943752042954, false, 3385052658235853990473420226123930971); + _liquidationERC20PoolHandler.settleAuction(2693191148227658159823862814074, 44032195641927234172430384447, 2992758194960713897487381207167); + _liquidationERC20PoolHandler.removeQuoteToken(3, 34308174710409047450205135565, 2); + _liquidationERC20PoolHandler.takeAuction(235062105582030911119033338, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_2() external { + _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); + _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); + _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); + _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_3() external { + _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 88); + _liquidationERC20PoolHandler.kickWithDeposit(454046303796091226235, 1); + _liquidationERC20PoolHandler.addQuoteToken(22366532024867500041595597535594488494092956872779970834638, 2056702511, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.takeAuction(7409458575819003489055485098, 19999999999999999999998047232, 160427188541373972791114); + _liquidationERC20PoolHandler.drawDebt(54, 1078707919809097500728008); + _liquidationERC20PoolHandler.takeAuction(2, 11014481, 0); + _liquidationERC20PoolHandler.kickWithDeposit(6261145081390052923416, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.repayDebt(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.repayDebt(19522111312004366551699434321235702562902449, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.removeQuoteToken(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.kickAuction(1, 2109173590696846176713716365608775182694735853511202473079, 1); + _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_F4_4() external { + _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_invariant_bucketlps_B2_B3() external { + + _liquidationERC20PoolHandler.takeAuction(267050932349, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3887647445238399127687813856507958874); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 103646259621272362812910538669334394369354710213939195837836110291707517186914, 22729901925249217583); + _liquidationERC20PoolHandler.takeAuction(100662313874952447676789537887446294, 36755077739534085766246321257993, 20000000001077187985112900413); + _liquidationERC20PoolHandler.settleAuction(999999999999999970610520171679024221920138860, 4339, 19021013243589608614756959415948670046791); + _liquidationERC20PoolHandler.removeQuoteToken(7393406237507791712904627, 1097992169037390343, 30); + + invariant_Buckets_B2_B3(); + } + +} \ No newline at end of file diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol new file mode 100644 index 000000000..8d6804459 --- /dev/null +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ReserveERC20PoolInvariants } from "../../invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol"; + +contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_reserve_1() external { + _reserveERC20PoolHandler.kickAuction(3833, 15167, 15812); + _reserveERC20PoolHandler.removeQuoteToken(3841, 5339, 3672); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + // test was failing due to error in local fenwickAccureInterest method + function test_regression_reserve_2() external { + _reserveERC20PoolHandler.bucketTake(19730, 10740, false, 15745); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + _reserveERC20PoolHandler.addCollateral(14982, 18415, 2079); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_3() external { + _reserveERC20PoolHandler.repayDebt(404759030515771436961484, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + _reserveERC20PoolHandler.removeQuoteToken(1, 48462143332689486187207611220503504, 3016379223696706064676286307759709760607418884028758142005949880337746); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_4() external { + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_5() external { + _reserveERC20PoolHandler.addQuoteToken(16175599156223678030374425049208907710, 7790130564765920091364739351727, 3); + _reserveERC20PoolHandler.takeReserves(5189, 15843); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, false, 32141946615464); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_6() external { + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.removeQuoteToken(3, 76598848420614737624527356706527, 0); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_7() external { + _reserveERC20PoolHandler.addQuoteToken(3457, 669447918254181815570046125126321316, 999999999837564549363536522206516458546098684); + _reserveERC20PoolHandler.takeReserves(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.takeAuction(1340780, 50855928079819281347583122859151761721081932621621575848930363902528865907253, 1955849966715168052511460257792969975295827229642304100359774335664); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_8() external { + _reserveERC20PoolHandler.addQuoteToken(0, 16517235514828622102184417372650002297563613398679232953, 3); + _reserveERC20PoolHandler.takeReserves(1, 824651); + _reserveERC20PoolHandler.kickAuction(353274873012743605831170677893, 0, 297442424590491337560428021161844134441441035247561757); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_9() external { + _reserveERC20PoolHandler.addQuoteToken(8167, 13910, 6572); + _reserveERC20PoolHandler.removeQuoteToken(450224344766393467188006446127940623592343232978, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3); + _reserveERC20PoolHandler.addQuoteToken(1338758958425242459263005073411197235389119160018038412507867175716953081924, 0, 3); + _reserveERC20PoolHandler.removeQuoteToken(13684, 7152374202712184607581797, 37874588407625287908455929174); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_10() external { + _reserveERC20PoolHandler.drawDebt(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.takeAuction(57952503477150200455919212210202824, 59396836510148646246120666527, 253313800651499290076173012431766464943796699909751081638812681630219); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_11() external { + _reserveERC20PoolHandler.drawDebt(121976811044722028186086534321386307, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.removeQuoteToken(22099, 75368688232971077945057, 1089607217901154741924938851595); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_12() external { + _reserveERC20PoolHandler.drawDebt(7201, 13634); + _reserveERC20PoolHandler.startClaimableReserveAuction(4584); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_13() external { + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 540213858694280098848655811354140073005); + _reserveERC20PoolHandler.takeAuction(0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 16744276840254269931315148200783781329474); + _reserveERC20PoolHandler.settleAuction(1052055081946638635908683442568, 2, 3); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_reserve_14() external { + _reserveERC20PoolHandler.settleAuction(437841947740231831335707997666789355668988087441752683415964733126988332082, 147808166723925302409649247274, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + // FIXME: Seems to be issue with rounding to nearest in Desposits.unscaledAdd() in addQuoteToken + function test_regression_reserve_16() external { + _reserveERC20PoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); + _reserveERC20PoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); + _reserveERC20PoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231); + _reserveERC20PoolHandler.takeReserves(3052809529665022333893308239466671666604242469878272137069, 2); + _reserveERC20PoolHandler.settleAuction(56829802927206056542134152487104, 1, 16551256); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 4559892907266199616760, true, 92132592320410512639572628067656882480659844625060229234412683145); + _reserveERC20PoolHandler.addQuoteToken(26659, 27252796304289191617124780530313880584663397025838797405583704016009646047240, 8174069071114126926049883726727); + _reserveERC20PoolHandler.settleAuction(7416752279321695807446009676282848840713503167567654621163487831711306738, 42429259698839522507819580090756, 4353185348715295869540288672); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.takeReserves(7414584624540108578389380660398591567646816233407392320795021351932076518, 119186585263660671065239170291646549528129172578); + _reserveERC20PoolHandler.takeReserves(14604452466686952199052773378, 15308); + _reserveERC20PoolHandler.moveQuoteToken(2, 7113439765, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 101839127799233627783); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); + _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 175006273713916823228319530732179, 3); + _reserveERC20PoolHandler.kickAuction(999999999999999989948035804259829580593704779, 2999999999999999995605838724439103323477035837, 567178035339127142779327214); + _reserveERC20PoolHandler.kickWithDeposit(17028734043909648834002499445, 9578925065330517200577552073309); + _reserveERC20PoolHandler.addQuoteToken(6672165, 3776221923932077947607417775990788567, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_fenwick_deposits_1() external { + _reserveERC20PoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_incorrect_zero_deposit_buckets_1() external { + _reserveERC20PoolHandler.addQuoteToken(26716, 792071517553389595371632366275, 1999999999999999449873579333598595527312558403); + + invariant_fenwick_prefixSumIndex_F4(); + _reserveERC20PoolHandler.takeAuction(3383098792294835418337099631478603398072656037191240558595006969488860, 23280466048203500609787983860018797249195596837096487660362732305, 999999999999999999999999012359); + + invariant_fenwick_prefixSumIndex_F4(); + } + + function test_regression_incorrect_zero_deposit_buckets_2() external { + _reserveERC20PoolHandler.addCollateral(9093188371345232280759885514931620, 736370925, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.removeQuoteToken(2, 743823342719479363729966668312423206558602, 6003791801508574660825548152233943700089469549364090309); + _reserveERC20PoolHandler.removeQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); + _reserveERC20PoolHandler.addQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.addQuoteToken(22558, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 26798251134); + _reserveERC20PoolHandler.moveQuoteToken(699684583201376669946795465695023954383, 871337618071093223322748209250657757655686665685488924893819949988, 6856667370119202181100844692321254723509125063768335, 2); + + invariant_fenwick_prefixSumIndex_F4(); + + } + + function test_regression_incorrect_bond() external { + _reserveERC20PoolHandler.settleAuction(18129, 6125, 756); + + invariant_bond_A2(); + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_reserves_fenwick_depositAtIndex_F1() external { + _reserveERC20PoolHandler.kickAuction(14062, 13380, 20332); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 250713412144308447525906089113510093407014793436690623); + _reserveERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_fenwick_depositAtIndex_F1(); + } + + function test_regression_invariant_reserves_settle_1() external { + _reserveERC20PoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886); + _reserveERC20PoolHandler.startClaimableReserveAuction(18407833277983020451007887294192863287187933); + _reserveERC20PoolHandler.settleAuction(2720, 3319, 516); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_reserves_settle_2() external { + _reserveERC20PoolHandler.takeAuction(3, 214198155653990209702223102757081411626927025, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.repayDebt(36, 19087); + _reserveERC20PoolHandler.drawDebt(2550145944163683156825587547113715005197220288637184, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_1() external { + _reserveERC20PoolHandler.kickAuction(22771, 1111716442170237736883602263032, 7068); + _reserveERC20PoolHandler.addCollateral(450013003559446434159001584489461823249847174057443177111241841181931, 312804075096415570730723645176181753809227168111076176815108, 0); + _reserveERC20PoolHandler.pledgeCollateral(1985831902099838153679635097394320832859625435, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.takeAuction(159178586166894, 2, 2); + _reserveERC20PoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653); + _reserveERC20PoolHandler.startClaimableReserveAuction(2162); + _reserveERC20PoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_2() external { + _reserveERC20PoolHandler.drawDebt(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.pullCollateral(8213783947977569843117913236674123519747026, 26007879196259510050186964175498569516185804333067186877); + _reserveERC20PoolHandler.drawDebt(2301679051848045604, 2599238865); + _reserveERC20PoolHandler.addCollateral(4242066606167690018840733069974159, 2308657525655903223461843364795, 65478701235782653506998474972558); + _reserveERC20PoolHandler.kickAuction(69087967303211947138147234149237227681311399268590256122007, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 61509477439); + _reserveERC20PoolHandler.bucketTake(72107205250762587233492136850, 1244277915808615586782916545843, false, 39013151190969055659579687996); + _reserveERC20PoolHandler.transferLps(235427298074932216827475360756961, 2730975142229662626738653393718571, 1801094436838792863068211758488417, 879376648610435813515943108046); + _reserveERC20PoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208); + _reserveERC20PoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765); + _reserveERC20PoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383); + _reserveERC20PoolHandler.startClaimableReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589); + _reserveERC20PoolHandler.drawDebt(3, 2); + _reserveERC20PoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855); + _reserveERC20PoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.startClaimableReserveAuction(0); + _reserveERC20PoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_3() external { + _reserveERC20PoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442); + _reserveERC20PoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189); + _reserveERC20PoolHandler.startClaimableReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); + _reserveERC20PoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067); + _reserveERC20PoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2); + _reserveERC20PoolHandler.settleAuction(2475430586786710276861336070835, 2600907908657087816392951766665339, 618867463233346276220185869); + _reserveERC20PoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417); + _reserveERC20PoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615); + _reserveERC20PoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417); + _reserveERC20PoolHandler.startClaimableReserveAuction(1); + _reserveERC20PoolHandler.bucketTake(3, 1, true, 2); + _reserveERC20PoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893); + _reserveERC20PoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.pullCollateral(2, 2); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 60110048782249025340, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_4() external { + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.repayDebt(123785744463475277851, 431477); + _reserveERC20PoolHandler.transferLps(8349868629210939854344368826901611192, 2050523511941068426657597285533, 482178822629563486190079445656644, 113294184847064316812952522804); + _reserveERC20PoolHandler.kickWithDeposit(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + _reserveERC20PoolHandler.settleAuction(2, 60232917818899277216367937385395389606, 109871490879953029603376159938904259489696033217506136); + _reserveERC20PoolHandler.repayDebt(11000946587948121111587595267746251370302202324589596297423219199459160, 1640564753028103680512592653747); + _reserveERC20PoolHandler.kickAuction(3981871706795545560915874060150150667177950440617972926122855684987, 198277768150818655020367, 2892877132676919180494078569276042); + _reserveERC20PoolHandler.addCollateral(1263277608, 63278488014355910828533249093658068159654702008400, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.pullCollateral(2673207612857671157084473752324442, 2000121050152966887141053752381); + _reserveERC20PoolHandler.removeCollateral(17512256671104333742254942029, 940622488995047370832475, 17490); + _reserveERC20PoolHandler.takeReserves(4664936529054748613171449032640911546982046023628226142220220474, 12228144613454452340256380805978754348438442703119); + + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_5() external { + _reserveERC20PoolHandler.settleAuction(841361270493647884419014561906636, 98291268956781519518581599501066994252857442823583923678216713962377882453983, 1406581758883); + _reserveERC20PoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0); + _reserveERC20PoolHandler.repayDebt(46968019084877, 3); + _reserveERC20PoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3); + // _reserveERC20PoolHandler.bucketTake(17377, 2748873005452892812548622619587, false, 999999999999999989712357375741033502535274466); + skip(2 hours); + _pool.updateInterest(); + /* + TODO: Check why deposit change is more than debt change in accrue interest in "updateInterest" + debt change --> 236352821760996207141053 + deposit change --> 236352821761181451576056 + */ + currentTimestamp = block.timestamp; + invariant_quoteTokenBalance_QT1(); + } + + function test_regression_invariant_reserves_settle_3() external { + _reserveERC20PoolHandler.bucketTake(38522325070060518315904717784000000000, 74804166371079302281493396778, false, 243284095655821418741726406906); + _reserveERC20PoolHandler.removeQuoteToken(63300517263709739718213296806, 544282601310994378458621785271097, 93004761485750531023207874); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 10850580031398165201080403693039642, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.takeAuction(1006654503300439100037731502194, 999999999999999820916638470184939411687495097, 2999999999999999849116243910762621146260836956); + _reserveERC20PoolHandler.settleAuction(513358560825207984200760701, 527826952804937875408570995575150, 3075); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_reserves_settle_4() external { + _reserveERC20PoolHandler.kickAuction(999999999999999886611844846637902655009191722, 809319421722186623206028334686443, 33424777291596678039713); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 2503088493515274266); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 16755); + _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 2); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_1() external { + _reserveERC20PoolHandler.drawDebt(3, 2472487412192096145519673462983934503); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 50482403089838632034016548451617756782); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_2() external { + _reserveERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.takeReserves(9990, 2); + _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 32167191465467724730024789812); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_3() external { + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 7863893832813740178393566165935290555711); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 672940003103495713632014456312899612181893075117989217767500902); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_take_reserves_4() external { + _reserveERC20PoolHandler.bucketTake(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.takeReserves(2000008144440715646777241504589, 695559613732339828463793224249); + _reserveERC20PoolHandler.takeAuction(5260, 3000000000000000000000010654836333921317470662, 6571232818648673809695471386); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + + function test_regression_invariant_repayDebt_F2_1() external { + _reserveERC20PoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045); + _reserveERC20PoolHandler.kickAuction(1000000000372489032271805343253, 33527, 2999999999999998999627510967728193679786334003); + _reserveERC20PoolHandler.takeAuction(30442763437987671335943625876181535412080651070033770037765737902267600059, 0, 62793434148368637031717982910725); + _reserveERC20PoolHandler.drawDebt(1, 2); + _reserveERC20PoolHandler.takeAuction(28478785935025462058931686388528614452411453327852591879599088, 1426479312070353, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.drawDebt(10933, 2937); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 6122968755523040); + _reserveERC20PoolHandler.repayDebt(10917282482493108186780095138347753666882231491750232316870663654516774564, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_invariant_takeAuction_F3() external { + _reserveERC20PoolHandler.drawDebt(66012189296213, 3501011380219996136241089195497); + _reserveERC20PoolHandler.kickAuction(5022297903775350684886398975, 20526, 2902853749630275072725962069); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 4391496802861267555764811220); + _reserveERC20PoolHandler.moveQuoteToken(26018560, 3192, 25995484456155391449642016017, 22537); + _reserveERC20PoolHandler.transferLps(10763986310328530217005920827655704540417291683469924162879658, 4634, 8842, 3); + _reserveERC20PoolHandler.settleAuction(2913861884801667469428509650, 17685440748964982730500143988068465999241920952718023027278539889735696458314, 744860398079104642573120377479575543713282684535849403581932752660396046); + _reserveERC20PoolHandler.takeReserves(9546428924610247071820016, 1); + _reserveERC20PoolHandler.kickAuction(1021712469506287128291988, 470273052888220, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.kickWithDeposit(21372131561480654576901520848, 583255095299263976575486908); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 219682941, 6398456408984021365251851328837461998816613070677747503909692892499751257833); + _reserveERC20PoolHandler.moveQuoteToken(8413969458442105899430554342773, 42973831423907508485458560352, 14483994975746621772566970294, 27693669185946254354714892761); + _reserveERC20PoolHandler.bucketTake(0, 1, false, 1); + _reserveERC20PoolHandler.takeAuction(2760306433008897416497, 35178760526536102733112750779, 307455027758822287663945712); + + invariant_fenwick_bucket_index_F3(); + invariant_fenwick_prefixSumIndex_F4(); + } + + // FIXME: Seems to be an issue with Deposits.mult() in accrue interest or some issue with timestamp in invariant setup + function _test_regression_kick_F1_F2() external { + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); + _reserveERC20PoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); + _reserveERC20PoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); + _reserveERC20PoolHandler.takeAuction(296258719633565160185329, 490859840095298219320862, 16604700944401714968833692676); + _reserveERC20PoolHandler.kickAuction(1007024558278734662013991074770, 12316238, 8522190612260582802728723964891359810344750053801981528212387048); + _reserveERC20PoolHandler.takeAuction(999999999999999990212662818220103017885508577, 13644265990130681739980240101, 365402912996683431395427167362586262781607554542513822722975820380813222232); + _reserveERC20PoolHandler.takeAuction(999999999999999990000000000000000000000993018, 31506548945590221240114018464, 1016963456957222995035464545); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, false, 30294494991681513847857232418933803770638682537); + _reserveERC20PoolHandler.kickAuction(2324631542950979206383056100280239271207523734887421, 1, 23494016960770235530146856844201861803189848725938507629); + + invariant_fenwick_depositAtIndex_F1(); + invariant_fenwick_depositsTillIndex_F2(); + } + + function test_regression_remove_R1() external { + _reserveERC20PoolHandler.takeAuction(1000000000147122258, 3919731510820678131056801, 158441107709132461742605107); + _reserveERC20PoolHandler.repayDebt(15097247704276523502490912, 5821681489746654725611665637); + _reserveERC20PoolHandler.addQuoteToken(409278183265946161107935122, 13459778251101474251175765782, 17131651646875762675637482511491680925564181440856864512); + _reserveERC20PoolHandler.kickWithDeposit(3000000000000000000003060052276861736589117902, 10971651541557993591476169); + _reserveERC20PoolHandler.drawDebt(99176811231448450752542388131222351, 4756085816094695387473840); + _reserveERC20PoolHandler.transferLps(345464481275697722, 1, 1571, 636770839146216364947817981246144824780203402016795537219680499840300283500); + _reserveERC20PoolHandler.takeReserves(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.removeQuoteToken(2921676640197348125883567882, 110429299813004951706741973, 5838113258459267571531065497); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + +} diff --git a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20DSTestPlus.sol rename to tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol index 558fef4aa..2a746cbb1 100644 --- a/tests/forge/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol @@ -5,8 +5,8 @@ pragma solidity 0.8.14; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -import { DSTestPlus } from '../utils/DSTestPlus.sol'; -import { Token } from '../utils/Tokens.sol'; +import { DSTestPlus } from '../../utils/DSTestPlus.sol'; +import { Token } from '../../utils/Tokens.sol'; import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolBorrow.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolCollateral.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolFactory.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol index b34a709b3..1181d6076 100644 --- a/tests/forge/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; -import { Token } from '../utils/Tokens.sol'; +import { Token } from '../../utils/Tokens.sol'; import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol index 861eff072..0b34fae15 100644 --- a/tests/forge/ERC20Pool/ERC20PoolFlashloan.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol @@ -8,7 +8,7 @@ import { FlashloanBorrower, SomeDefiStrategy, SomeDefiStrategyWithRepayment -} from '../utils/FlashloanBorrower.sol'; +} from '../../utils/FlashloanBorrower.sol'; import 'src/libraries/helpers/PoolHelper.sol'; import 'src/ERC20Pool.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolGasLoadTest.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolGasLoadTest.t.sol index 4144b0e53..9b61c5458 100644 --- a/tests/forge/ERC20Pool/ERC20PoolGasLoadTest.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolGasLoadTest.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; import { ERC20DSTestPlus } from './ERC20DSTestPlus.sol'; -import '../utils/Tokens.sol'; +import '../../utils/Tokens.sol'; import 'src/ERC20Pool.sol'; import 'src/ERC20PoolFactory.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol index 60729c360..997c62eed 100644 --- a/tests/forge/ERC20Pool/ERC20PoolInfoUtils.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.14; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; -import { Token } from '../utils/Tokens.sol'; +import { Token } from '../../utils/Tokens.sol'; import 'src/libraries/helpers/PoolHelper.sol'; import 'src/interfaces/pool/erc20/IERC20Pool.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolInputValidation.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInputValidation.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolInputValidation.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolInputValidation.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsKick.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol index 0a860489b..398442c4a 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol @@ -6,7 +6,7 @@ import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import { ERC20DSTestPlus } from './ERC20DSTestPlus.sol'; import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; -import { TokenWithNDecimals } from '../utils/Tokens.sol'; +import { TokenWithNDecimals } from '../../utils/Tokens.sol'; import 'src/PoolInfoUtils.sol'; import 'src/libraries/helpers/PoolHelper.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolLoanHeap.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLoanHeap.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolLoanHeap.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolLoanHeap.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolMulticall.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolMulticall.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolMulticall.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol similarity index 99% rename from tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol index 1d032baeb..4bf475a45 100644 --- a/tests/forge/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.14; import { ERC20DSTestPlus } from './ERC20DSTestPlus.sol'; -import { TokenWithNDecimals } from '../utils/Tokens.sol'; +import { TokenWithNDecimals } from '../../utils/Tokens.sol'; import 'src/ERC20Pool.sol'; import 'src/ERC20PoolFactory.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolPurchaseQuote.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolQuoteToken.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol diff --git a/tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol similarity index 97% rename from tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol index 0b52da557..495e59e90 100644 --- a/tests/forge/ERC20Pool/ERC20PoolReserveAuction.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.14; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; -import { FlashloanBorrower, SomeDefiStrategy } from '../utils/FlashloanBorrower.sol'; +import { FlashloanBorrower, SomeDefiStrategy } from '../../utils/FlashloanBorrower.sol'; import 'src/libraries/helpers/PoolHelper.sol'; import 'src/ERC20Pool.sol'; diff --git a/tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20PoolTransferLPs.t.sol rename to tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol diff --git a/tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol b/tests/forge/unit/ERC20Pool/ERC20SafeTransferTokens.sol similarity index 100% rename from tests/forge/ERC20Pool/ERC20SafeTransferTokens.sol rename to tests/forge/unit/ERC20Pool/ERC20SafeTransferTokens.sol diff --git a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol similarity index 99% rename from tests/forge/ERC721Pool/ERC721DSTestPlus.sol rename to tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol index b8a3a3a74..5a2dd7015 100644 --- a/tests/forge/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol @@ -5,8 +5,8 @@ import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -import { DSTestPlus } from '../utils/DSTestPlus.sol'; -import { NFTCollateralToken, Token, TokenWithNDecimals } from '../utils/Tokens.sol'; +import { DSTestPlus } from '../../utils/DSTestPlus.sol'; +import { NFTCollateralToken, Token, TokenWithNDecimals } from '../../utils/Tokens.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; diff --git a/tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolBorrow.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolEMAs.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol similarity index 99% rename from tests/forge/ERC721Pool/ERC721PoolFactory.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol index 5947697a2..b4a12f555 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; -import { NFTCollateralToken, TokenWithNDecimals } from '../utils/Tokens.sol'; +import { NFTCollateralToken, TokenWithNDecimals } from '../../utils/Tokens.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; import { ERC721PoolFactory } from 'src/ERC721PoolFactory.sol'; diff --git a/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol similarity index 98% rename from tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol index 61bc96714..9bf2fe4af 100644 --- a/tests/forge/ERC721Pool/ERC721PoolFlashloan.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from './ERC721DSTestPlus.sol'; -import { FlashloanBorrower, SomeDefiStrategy } from '../utils/FlashloanBorrower.sol'; +import { FlashloanBorrower, SomeDefiStrategy } from '../../utils/FlashloanBorrower.sol'; import 'src/ERC721Pool.sol'; import 'src/libraries/internal/Maths.sol'; diff --git a/tests/forge/ERC721Pool/ERC721PoolInputValidation.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolInputValidation.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolInputValidation.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolInputValidation.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolInterest.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolLiquidationsKick.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol similarity index 99% rename from tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index b3be0c679..9cb9a9117 100644 --- a/tests/forge/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; import { ERC721HelperContract } from "./ERC721DSTestPlus.sol"; -import { NFTNoopTakeExample } from "../interactions/NFTTakeExample.sol"; +import { NFTNoopTakeExample } from "../../interactions/NFTTakeExample.sol"; import 'src/libraries/helpers/PoolHelper.sol'; diff --git a/tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolPurchaseQuote.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol diff --git a/tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol similarity index 100% rename from tests/forge/ERC721Pool/ERC721PoolReserveAuction.t.sol rename to tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol diff --git a/tests/forge/Rewards/RewardsDSTestPlus.sol b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol similarity index 99% rename from tests/forge/Rewards/RewardsDSTestPlus.sol rename to tests/forge/unit/Rewards/RewardsDSTestPlus.sol index a2b2d5d7b..38174d166 100644 --- a/tests/forge/Rewards/RewardsDSTestPlus.sol +++ b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol @@ -10,7 +10,7 @@ import 'src/interfaces/position/IPositionManager.sol'; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -import { Token } from '../utils/Tokens.sol'; +import { Token } from '../../utils/Tokens.sol'; import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; diff --git a/tests/forge/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol similarity index 100% rename from tests/forge/Rewards/RewardsManager.t.sol rename to tests/forge/unit/Rewards/RewardsManager.t.sol diff --git a/tests/forge/utils/Tokens.sol b/tests/forge/utils/Tokens.sol index dea0b67ef..d23ea0648 100644 --- a/tests/forge/utils/Tokens.sol +++ b/tests/forge/utils/Tokens.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; +import '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol'; import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol'; contract Token is ERC20 { @@ -14,7 +15,7 @@ contract Token is ERC20 { } } -contract NFTCollateralToken is ERC721 { +contract NFTCollateralToken is ERC721Enumerable { /// @dev The ID of the next token that will be minted. Skips 0 uint176 private _nextId = 1; @@ -27,7 +28,7 @@ contract NFTCollateralToken is ERC721 { } } - function totalSupply() public view returns (uint256) { + function totalSupply() public view override returns (uint256) { return _nextId - 1; } } From 29eb9daa418d5f64485464ca59082db0701b3d89 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Wed, 5 Apr 2023 12:55:39 +0530 Subject: [PATCH 43/70] Move tests into unit test directory (#728) --- Makefile | 2 +- .../forge/invariants/test-invariant-erc20-precision.sh | 0 tests/forge/{ => unit}/Auctions.t.sol | 2 +- tests/forge/{ => unit}/FenwickTree.t.sol | 4 ++-- tests/forge/{ => unit}/Heap.t.sol | 4 ++-- tests/forge/{ => unit}/MathTest.t.sol | 2 +- tests/forge/{ => unit}/PoolCommonsTest.t.sol | 2 +- tests/forge/{ => unit}/PoolHelperTest.t.sol | 2 +- tests/forge/{ => unit}/PositionManager.t.sol | 8 ++++---- tests/forge/{ => unit}/SafeTokenNamer.t.sol | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) rename test-invariant-erc20-precision.sh => tests/forge/invariants/test-invariant-erc20-precision.sh (100%) rename tests/forge/{ => unit}/Auctions.t.sol (99%) rename tests/forge/{ => unit}/FenwickTree.t.sol (99%) rename tests/forge/{ => unit}/Heap.t.sol (99%) rename tests/forge/{ => unit}/MathTest.t.sol (98%) rename tests/forge/{ => unit}/PoolCommonsTest.t.sol (98%) rename tests/forge/{ => unit}/PoolHelperTest.t.sol (99%) rename tests/forge/{ => unit}/PositionManager.t.sol (99%) rename tests/forge/{ => unit}/SafeTokenNamer.t.sol (97%) diff --git a/Makefile b/Makefile index c466f8404..84c9cc44f 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLAT test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression --mc ERC20 test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt test_regression --mc ERC721 coverage :; forge coverage --no-match-test "testLoad|invariant" -test-invariant-erc20-precision :; ./test-invariant-erc20-precision.sh +test-invariant-erc20-precision :; ./tests/forge/invariants/test-invariant-erc20-precision.sh # Generate Gas Snapshots snapshot :; forge clean && forge snapshot diff --git a/test-invariant-erc20-precision.sh b/tests/forge/invariants/test-invariant-erc20-precision.sh similarity index 100% rename from test-invariant-erc20-precision.sh rename to tests/forge/invariants/test-invariant-erc20-precision.sh diff --git a/tests/forge/Auctions.t.sol b/tests/forge/unit/Auctions.t.sol similarity index 99% rename from tests/forge/Auctions.t.sol rename to tests/forge/unit/Auctions.t.sol index f63e1896e..bc0cfa7f9 100644 --- a/tests/forge/Auctions.t.sol +++ b/tests/forge/unit/Auctions.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; +import '../utils/DSTestPlus.sol'; import 'src/libraries/external/Auctions.sol'; diff --git a/tests/forge/FenwickTree.t.sol b/tests/forge/unit/FenwickTree.t.sol similarity index 99% rename from tests/forge/FenwickTree.t.sol rename to tests/forge/unit/FenwickTree.t.sol index 6072b6851..a9797cadf 100644 --- a/tests/forge/FenwickTree.t.sol +++ b/tests/forge/unit/FenwickTree.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; -import './utils/FenwickTreeInstance.sol'; +import '../utils/DSTestPlus.sol'; +import '../utils/FenwickTreeInstance.sol'; import 'src/libraries/internal/Maths.sol'; diff --git a/tests/forge/Heap.t.sol b/tests/forge/unit/Heap.t.sol similarity index 99% rename from tests/forge/Heap.t.sol rename to tests/forge/unit/Heap.t.sol index b2f93ee85..c137323ca 100644 --- a/tests/forge/Heap.t.sol +++ b/tests/forge/unit/Heap.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; -import './utils/HeapInstance.sol'; +import '../utils/DSTestPlus.sol'; +import '../utils/HeapInstance.sol'; contract HeapTest is DSTestPlus { HeapInstance private _loans; diff --git a/tests/forge/MathTest.t.sol b/tests/forge/unit/MathTest.t.sol similarity index 98% rename from tests/forge/MathTest.t.sol rename to tests/forge/unit/MathTest.t.sol index 28ecc6aa2..43de46457 100644 --- a/tests/forge/MathTest.t.sol +++ b/tests/forge/unit/MathTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; +import '../utils/DSTestPlus.sol'; import '@prb-math/contracts/PRBMathSD59x18.sol'; import '@prb-math/contracts/PRBMathUD60x18.sol'; diff --git a/tests/forge/PoolCommonsTest.t.sol b/tests/forge/unit/PoolCommonsTest.t.sol similarity index 98% rename from tests/forge/PoolCommonsTest.t.sol rename to tests/forge/unit/PoolCommonsTest.t.sol index ee3ddbb92..b3695efb9 100644 --- a/tests/forge/PoolCommonsTest.t.sol +++ b/tests/forge/unit/PoolCommonsTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; +import '../utils/DSTestPlus.sol'; import 'src/libraries/external/PoolCommons.sol'; diff --git a/tests/forge/PoolHelperTest.t.sol b/tests/forge/unit/PoolHelperTest.t.sol similarity index 99% rename from tests/forge/PoolHelperTest.t.sol rename to tests/forge/unit/PoolHelperTest.t.sol index ce65e41ab..78ea6d396 100644 --- a/tests/forge/PoolHelperTest.t.sol +++ b/tests/forge/unit/PoolHelperTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; +import '../utils/DSTestPlus.sol'; import 'src/libraries/helpers/PoolHelper.sol'; diff --git a/tests/forge/PositionManager.t.sol b/tests/forge/unit/PositionManager.t.sol similarity index 99% rename from tests/forge/PositionManager.t.sol rename to tests/forge/unit/PositionManager.t.sol index bf960776d..9e6e8bf31 100644 --- a/tests/forge/PositionManager.t.sol +++ b/tests/forge/unit/PositionManager.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.14; import { Base64 } from '@base64-sol/base64.sol'; -import { ERC20HelperContract } from './unit/ERC20Pool/ERC20DSTestPlus.sol'; -import { ERC721HelperContract } from './unit/ERC721Pool/ERC721DSTestPlus.sol'; +import { ERC20HelperContract } from '../unit/ERC20Pool/ERC20DSTestPlus.sol'; +import { ERC721HelperContract } from '../unit/ERC721Pool/ERC721DSTestPlus.sol'; import 'src/interfaces/position/IPositionManager.sol'; import 'src/PositionManager.sol'; @@ -13,8 +13,8 @@ import 'src/libraries/helpers/PoolHelper.sol'; import 'src/interfaces/pool/commons/IPoolErrors.sol'; -import './utils/ContractNFTRecipient.sol'; -import './utils/ContractNFTSpender.sol'; +import '../utils/ContractNFTRecipient.sol'; +import '../utils/ContractNFTSpender.sol'; abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract { diff --git a/tests/forge/SafeTokenNamer.t.sol b/tests/forge/unit/SafeTokenNamer.t.sol similarity index 97% rename from tests/forge/SafeTokenNamer.t.sol rename to tests/forge/unit/SafeTokenNamer.t.sol index 76544c5d8..0e779006c 100644 --- a/tests/forge/SafeTokenNamer.t.sol +++ b/tests/forge/unit/SafeTokenNamer.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.14; -import './utils/DSTestPlus.sol'; -import './utils/Tokens.sol'; +import '../utils/DSTestPlus.sol'; +import '../utils/Tokens.sol'; import 'src/libraries/helpers/SafeTokenNamer.sol'; From 599b2b574ab3acdfbd4f15132fb7b179574676f0 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 6 Apr 2023 14:29:48 +0300 Subject: [PATCH 44/70] ToB improvement: UPDATE_CLAIM_REWARD state var should be constant (#729) --- src/RewardsManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 43ace72a2..af1ec1d01 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -56,7 +56,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @notice Reward factor by which to scale rewards earned for updating a buckets exchange rate. */ - uint256 internal UPDATE_CLAIM_REWARD = 0.05 * 1e18; + uint256 internal constant UPDATE_CLAIM_REWARD = 0.05 * 1e18; /** * @notice Time period after a burn event in which buckets exchange rates can be updated. */ From 7b183da0e1972a37285f008f2c724b8d7bc183f2 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 6 Apr 2023 18:19:23 +0300 Subject: [PATCH 45/70] ToB improvement: check for 0x address in RewardsManager constructor (#732) - is ajna token address passed is 0x then revert with DeployWithZeroAddress error --- src/RewardsManager.sol | 2 ++ .../rewards/IRewardsManagerErrors.sol | 5 +++++ tests/forge/unit/Rewards/RewardsDSTestPlus.sol | 18 +++++++++--------- tests/forge/unit/Rewards/RewardsManager.t.sol | 7 +++++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index af1ec1d01..728580169 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -87,6 +87,8 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /*******************/ constructor(address ajnaToken_, IPositionManager positionManager_) { + if (ajnaToken_ == address(0)) revert DeployWithZeroAddress(); + ajnaToken = ajnaToken_; positionManager = positionManager_; } diff --git a/src/interfaces/rewards/IRewardsManagerErrors.sol b/src/interfaces/rewards/IRewardsManagerErrors.sol index 81590f837..dbfa58320 100644 --- a/src/interfaces/rewards/IRewardsManagerErrors.sol +++ b/src/interfaces/rewards/IRewardsManagerErrors.sol @@ -25,4 +25,9 @@ interface IRewardsManagerErrors { * @notice User attempted to interact with an NFT they aren't the owner of. */ error NotOwnerOfDeposit(); + + /** + * @notice Can't deploy with Ajna token address 0x0 address. + */ + error DeployWithZeroAddress(); } \ No newline at end of file diff --git a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol index 38174d166..6003127e2 100644 --- a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol +++ b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol @@ -22,17 +22,17 @@ import { IRewardsManagerEvents } from 'src/interfaces/rewards/IRewardsManagerEve abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContract { - address internal _minterOne; - address internal _minterTwo; - address internal _minterThree; - address internal _minterFour; - address internal _minterFive; + address internal _minterOne; + address internal _minterTwo; + address internal _minterThree; + address internal _minterFour; + address internal _minterFive; - ERC20 internal _ajnaToken; + ERC20 internal _ajnaToken; - IPool internal _poolTwo; - IRewardsManager internal _rewardsManager; - IPositionManager internal _positionManager; + IPool internal _poolTwo; + IRewardsManager internal _rewardsManager; + IPositionManager internal _positionManager; uint256 internal REWARDS_CAP = 0.8 * 1e18; diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index af18e4417..ac56b78bc 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.14; import 'src/PoolInfoUtils.sol'; +import 'src/RewardsManager.sol'; import 'src/PositionManager.sol'; import 'src/interfaces/rewards/IRewardsManager.sol'; @@ -67,6 +68,12 @@ contract RewardsManagerTest is RewardsHelperContract { _mintQuoteAndApproveTokens(_minterTwo, 500_000_000 * 1e18); } + function testDeployWith0xAddressRevert() external { + PositionManager positionManager = new PositionManager(_poolFactory, new ERC721PoolFactory(_ajna)); + + vm.expectRevert(IRewardsManagerErrors.DeployWithZeroAddress.selector); + new RewardsManager(address(0), positionManager); + } function testStakeToken() external { skip(10); From 0a0f58fd913bcdd34b13c8f7c2938241a0f621ea Mon Sep 17 00:00:00 2001 From: mattcushman <36414299+mattcushman@users.noreply.github.com> Date: Fri, 7 Apr 2023 01:44:27 -0400 Subject: [PATCH 46/70] Fenwick Rounding Improvements (Invariant fix F3 F4) (#719) * fixes * regression test pass * Reg test test_regression_reserve_exchange_rate_2 failing for small economic reason * Before removeQuoteTokenChange * fixed add collateral rounding * updated some baselines, still 29 tests fail * Passing depth 200 * Polish * remove unused inverserate function * Cleanup * Style * rounding change in collateralToLPs to avoid overflow * Remove FIXME * Modify multiplyByExchangeRate to allow for higher scaled values= * working through, down to 21 tests failing * Fix more tests * Rolled back collateral rounding to use exchange rates, modified RE test, increased ranges of randomizer * Fix tests * Revert LenderAction colalteral change, set invariant to 1000 runs --------- Co-authored-by: mwc Co-authored-by: grandizzy --- foundry.toml | 2 +- src/libraries/internal/Deposits.sol | 8 ++--- .../invariants/base/BasicInvariants.t.sol | 30 ++++++++++++++----- .../base/handlers/unbounded/BaseHandler.sol | 6 ++-- .../RegressionTestReservesERC20Pool.t.sol | 15 +++++++++- .../unit/ERC20Pool/ERC20PoolQuoteToken.t.sol | 2 +- .../ERC721Pool/ERC721PoolCollateral.t.sol | 2 +- .../ERC721PoolLiquidationsSettleAuction.t.sol | 6 ++-- .../ERC721Pool/ERC721PoolPurchaseQuote.t.sol | 2 +- 9 files changed, 50 insertions(+), 23 deletions(-) diff --git a/foundry.toml b/foundry.toml index c0afa1df8..db207bdd5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -27,6 +27,6 @@ runs = 300 [invariant] runs = 1000 # Number of times that a sequence of function calls is generated and run -depth = 30 # Number of function calls made in a given run. +depth = 200 # Number of function calls made in a given run. call_override = false # Override calls fail_on_revert = false # Fail the test if the contract reverts \ No newline at end of file diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index 1da848440..fb9e43bd2 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -90,7 +90,7 @@ library Deposits { // Compute sum up to sumIndex_ + i uint256 scaledValue = lowerIndexSum + - (scaling != 0 ? Maths.wmul(Maths.wmul(runningScale, scaling), value) : Maths.wmul(runningScale, value)); + (scaling != 0 ? (runningScale * scaling * value + 5e35) / 1e36 : Maths.wmul(runningScale, value)); if (scaledValue < targetSum_) { // Target value is too small, need to consider increasing sumIndex_ still @@ -102,7 +102,7 @@ library Deposits { } else { // Target index has this bit set to 0 // scaling == 0 means scale factor == 1, otherwise scale factor == scaling - if (scaling != 0) runningScale = Maths.wmul(runningScale, scaling); + if (scaling != 0) runningScale = Maths.floorWmul(runningScale, scaling); // Current scaledValue is <= targetSum_, it's a candidate value for sumIndexSum_ sumIndexSum_ = scaledValue; @@ -241,7 +241,7 @@ library Deposits { uint256 value = deposits_.values[index+j]; // Accumulate in sum_, recall that scaled==0 means that the scale factor is actually 1 - sum_ += scaled != 0 ? Maths.wmul(Maths.wmul(runningScale, scaled), value) : Maths.wmul(runningScale, value); + sum_ += scaled != 0 ? (runningScale * scaled * value + 5e35) / 1e36 : Maths.wmul(runningScale, value); // Build up index bit by bit index += j; @@ -249,7 +249,7 @@ library Deposits { if (index == sumIndex_) break; } else { // node is not included in sum, but its scale needs to be included for subsequent sums - if (scaled != 0) runningScale = Maths.wmul(runningScale, scaled); + if (scaled != 0) runningScale = Maths.floorWmul(runningScale, scaled); } // shift j to consider next less signficant bit j = j >> 1; diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index fe778cb88..81ff66669 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.14; import "@std/console.sol"; +import { Maths } from 'src/libraries/internal/Maths.sol'; + import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; import { LENDER_MIN_BUCKET_INDEX, @@ -151,6 +153,7 @@ abstract contract BasicInvariants is BaseInvariants { function invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { uint256 currentExchangeRate = _pool.bucketExchangeRate(bucketIndex); + (uint256 bucketLps, , , , ) = _pool.bucketInfo(bucketIndex); if (IBaseHandler(_handler).exchangeRateShouldNotChange(bucketIndex)) { uint256 previousExchangeRate = IBaseHandler(_handler).previousExchangeRate(bucketIndex); @@ -159,14 +162,25 @@ abstract contract BasicInvariants is BaseInvariants { console.log("Bucket Index -->", bucketIndex); console.log("Previous exchange Rate -->", previousExchangeRate); console.log("Current exchange Rate -->", currentExchangeRate); + console.log("Current bucket lps -->", bucketLps); console.log("======================================"); - requireWithinDiff( - currentExchangeRate, - previousExchangeRate, - 1e17, - "Incorrect exchange Rate changed" - ); + + if (bucketLps < 1e12) { + requireWithinDiff( + Maths.wmul(currentExchangeRate, bucketLps), + Maths.wmul(previousExchangeRate, bucketLps), + 1e16, // allow changes up to 0.01 qt in value if bucket LPs < 1e-6 + "Incorrect exchange Rate changed" + ); + } else { + requireWithinDiff( + currentExchangeRate, + previousExchangeRate, + 1e12, // otherwise require exchange rates to be within 1e-6 + "Incorrect exchange Rate changed" + ); + } } } } @@ -308,7 +322,7 @@ abstract contract BasicInvariants is BaseInvariants { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { (, , , uint256 depositAtIndex, ) = _pool.bucketInfo(bucketIndex); uint256 prefixSum = _pool.depositUpToIndex(bucketIndex); - uint256 bucketIndexFromDeposit = _pool.depositIndex(prefixSum); + uint256 bucketIndexFromDeposit = _pool.depositIndex(Maths.wmul(prefixSum, 1e18 + 1e1)); if (depositAtIndex != 0) { console.log("===================Bucket Index : ", bucketIndex, " ==================="); @@ -328,7 +342,7 @@ abstract contract BasicInvariants is BaseInvariants { console.log("Next nonzero bucket: ", nextNonzeroBucket); for(uint256 j = bucketIndex + 1; j < nextNonzeroBucket && j < LENDER_MAX_BUCKET_INDEX; j++) { (, , , uint256 depositAtJ, ) = _pool.bucketInfo(j); - // console.log("Deposit at %s is %s", j, depositAtJ); + console.log("Deposit at %s is %s", j, depositAtJ); require( depositAtJ == 0, "F4: incorrect buckets with 0 deposit" diff --git a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index f36e7ce75..3f5a9d4ad 100644 --- a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -24,8 +24,8 @@ uint256 constant LENDER_MAX_BUCKET_INDEX = 2572; uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; -uint256 constant MIN_AMOUNT = 1e6; -uint256 constant MAX_AMOUNT = 1e28; +uint256 constant MIN_AMOUNT = 1e3; +uint256 constant MAX_AMOUNT = 1e30; abstract contract BaseHandler is Test { @@ -396,4 +396,4 @@ abstract contract BaseHandler is Test { if (max_ == type(uint256).max && x_ != 0) result_++; } -} \ No newline at end of file +} diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 8d6804459..349dad593 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -117,7 +117,6 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } - // FIXME: Seems to be issue with rounding to nearest in Desposits.unscaledAdd() in addQuoteToken function test_regression_reserve_16() external { _reserveERC20PoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); _reserveERC20PoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); @@ -407,4 +406,18 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } + function test_regression_exrate_accuracy_R1() external { + + _reserveERC20PoolHandler.moveQuoteToken(0, 1095007911, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 10069220832808935); + _reserveERC20PoolHandler.addQuoteToken(3, 7147794914605263842183751281, 1); + _reserveERC20PoolHandler.kickAuction(2235318869033878164575850860, 25288937613311342943333937133, 83682862171707962382757764951); + _reserveERC20PoolHandler.addQuoteToken(30407840226053146238360101576, 5079250760342527521048689786712, 46176028454573721360694961068); + _reserveERC20PoolHandler.addQuoteToken(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 4050069994634); + _reserveERC20PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2732924787785177969603119436532655486434403999080563574919756204077541); + _reserveERC20PoolHandler.kickAuction(2970471299408419676825342051, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); + _reserveERC20PoolHandler.bucketTake(0, 11522913996, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); + } + } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol index 7af66816d..16bec678b 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -1206,7 +1206,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { assertGt(_quote.balanceOf(_lender), 200_000 * 1e18); } - function testAddRemoveQuoteTokenBucketExchangeRateInvariantDifferentActor() tearDown external { + function testAddRemoveQuoteTokenBucketExchangeRateInvariantDifferentActor() external tearDown { _mintQuoteAndApproveTokens(_lender, 1000000000000000000 * 1e18); uint256 initialLenderBalance = _quote.balanceOf(_lender); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol index 16a7d05f0..2e74bf29c 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol @@ -1163,7 +1163,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 50.000004575591110080 * 1e18, + poolSize: 50.000004575591109996 * 1e18, pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol index d0f221b99..9d632cd74 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -408,8 +408,8 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { index: 2501, lpBalance: 2_000 * 1e18, collateral: 0.110649792292326223 * 1e18, - deposit: 1_575.729756218596961193 * 1e18, - exchangeRate: 1.000557690748354813 * 1e18 + deposit: 1_575.729756218596960768 * 1e18, + exchangeRate: 1.000557690748354812 * 1e18 }); _assertBucket({ index: MAX_FENWICK_INDEX, @@ -1134,7 +1134,7 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { }); } - function testDepositTakeAndSettleBySettleSubsetPool() external { + function testDepositTakeAndSettleBySettleSubsetPool() external tearDown { // the 2 token ids are owned by borrower before settle assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol index 89b7db78e..61f3394f0 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol @@ -186,7 +186,7 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { * Attempts to remove more collateral than available given lp balance. * Attempts to remove collateral not in the bucket. */ - function testSubsetPurchaseQuoteWithDebt() external { + function testSubsetPurchaseQuoteWithDebt() external tearDown { // lenders add liquidity _addInitialLiquidity({ from: _lender, From ed92e49519fb62ec825d120725f16b760bd98a6a Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 7 Apr 2023 22:22:04 +0300 Subject: [PATCH 47/70] TOB-AJNA-4: Reset interest rate if debtEma < 5% of depositEma and pool rate > 10% (#733) # Interest rates can become extreme, potentially opening up DOS vectors ## High level * - introduce rate reset functionality: if the EMA of debt is less than 5% of the EMA of Meaningful Actual Deposit at any time, reset the rate to 10% but *only if* the current rate is higher than 10% already # Description of bug or vulnerability and solution * See https://github.com/ajna-finance/contracts/issues/698 * interest rate is automatically reset to 10% if if the EMA of debt is less than 5% of the EMA of Meaningful Actual Deposit * raise `ResetInterestRate` event # Contract size ## Pre Change ``` PoolCommons - 9,202B (37.44%) ``` ## Post Change ``` PoolCommons - 9,320B (37.92%) ``` # Gas usage ``` | Function Name | min | avg | median | max | # calls | | addQuoteToken | 119315 | 176427 | 161115 | 706716 | 60004 | | bucketTake | 322003 | 342801 | 342741 | 363719 | 4 | | drawDebt | 248277 | 293651 | 268224 | 797643 | 136003 | | kick | 195595 | 221811 | 220838 | 1030647 | 48000 | | kickWithDeposit | 242218 | 276761 | 271562 | 995701 | 8000 | | moveQuoteToken | 268072 | 268072 | 268072 | 268072 | 1 | | removeQuoteToken | 143395 | 165040 | 169087 | 191621 | 4000 | | repayDebt | 569028 | 601171 | 591067 | 749338 | 16002 | | settle | 350558 | 350558 | 350558 | 350558 | 1 | | take | 97007 | 101873 | 101550 | 324906 | 15998 | ``` ## Post Change ``` | Function Name | min | avg | median | max | # calls | | addQuoteToken | 119370 | 176471 | 161170 | 706601 | 60004 | | bucketTake | 321911 | 342709 | 342649 | 363627 | 4 | | drawDebt | 248332 | 293696 | 268279 | 797528 | 136003 | | kick | 195650 | 221866 | 220893 | 1030532 | 48000 | | kickWithDeposit | 242273 | 276816 | 271617 | 995586 | 8000 | | moveQuoteToken | 277580 | 277580 | 277580 | 277580 | 1 | | removeQuoteToken | 143303 | 164936 | 168939 | 191440 | 4000 | | repayDebt | 568913 | 601056 | 590952 | 749223 | 16002 | | settle | 350466 | 350466 | 350466 | 350466 | 1 | | take | 97051 | 101917 | 101594 | 324950 | 15998 | ``` --- src/interfaces/pool/commons/IPoolEvents.sol | 10 +++ src/libraries/external/PoolCommons.sol | 20 +++-- .../unit/ERC20Pool/ERC20PoolBorrow.t.sol | 83 +++++++++++++++++++ .../ERC20PoolInterestRateAndEMAs.t.sol | 18 ++-- 4 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index e2f745a1a..292176075 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -323,6 +323,16 @@ interface IPoolEvents { address indexed borrower ); + /** + * @notice Emitted when pool interest rate is reset. This happens when interest rate > 10% and debtEma < 5% of depositEma + * @param oldRate Old pool interest rate. + * @param newRate New pool interest rate. + */ + event ResetInterestRate( + uint256 oldRate, + uint256 newRate + ); + /** * @notice Emitted when pool interest rate is updated. * @param oldRate Old pool interest rate. diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 16b103a58..b45682468 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -40,6 +40,7 @@ library PoolCommons { /**************/ // See `IPoolEvents` for descriptions + event ResetInterestRate(uint256 oldRate, uint256 newRate); event UpdateInterestRate(uint256 oldRate, uint256 newRate); /*************************/ @@ -151,11 +152,21 @@ library PoolCommons { emaParams_.emaUpdate = block.timestamp; } - // calculate and update interest rate if it has been more than 12 hours since the last update - if (block.timestamp - interestParams_.interestRateUpdate > 12 hours) { + // reset interest rate if pool rate > 10% and debtEma < 5% of depositEma + if ( + poolState_.rate > 0.1 * 1e18 + && + vars.debtEma < Maths.wmul(vars.depositEma, 0.05 * 1e18) + ) { + interestParams_.interestRate = uint208(0.1 * 1e18); + interestParams_.interestRateUpdate = uint48(block.timestamp); + + emit ResetInterestRate(poolState_.rate, 0.1 * 1e18); + } + // otherwise calculate and update interest rate if it has been more than 12 hours since the last update + else if (block.timestamp - interestParams_.interestRateUpdate > 12 hours) { vars.newInterestRate = _calculateInterestRate( poolState_, - interestParams_.interestRate, vars.debtEma, vars.depositEma, vars.debtColEma, @@ -235,7 +246,6 @@ library PoolCommons { */ function _calculateInterestRate( PoolState memory poolState_, - uint256 interestRate_, uint256 debtEma_, uint256 depositEma_, uint256 debtColEma_, @@ -256,8 +266,6 @@ library PoolCommons { int256 tu = (lupt0DebtEma_ != 0) ? int256(Maths.wdiv(debtColEma_, lupt0DebtEma_)) : int(Maths.WAD); - if (!poolState_.isNewInterestAccrued) poolState_.rate = interestRate_; - newInterestRate_ = poolState_.rate; // raise rates if 4*(tu-1.02*mau) < (tu+1.02*mau-1)^2-1 diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol index b28a1323e..42f74eaa1 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol @@ -1319,4 +1319,87 @@ contract ERC20PoolBorrowFuzzyTest is ERC20FuzzyHelperContract { assertEq(_poolUtils.lup(address(_pool)), MAX_PRICE); } + function testPOCOverCollateralized_SingleBorrower() external { + // _borrower borrows 1,000 USDC collateralized by 100 eth + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 1000 * 1e18, + limitIndex: 3_000, + collateralToPledge: 100 * 1e18, + newLup:0 + }); + (uint interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.05 * 1e18); // 5% initial interest rate + + //pay down a little ($10) every 12 hours to trigger interestRate update + for (uint index; index < 8; ++index) { + skip(12.01 hours); // actually needs to be > 12 hours to trigger interestRate update + _repayDebt({ + from: _borrower, + borrower: _borrower, + amountToRepay: 10 * 1e18, + amountRepaid: 10 * 1e18, + collateralToPull: 0, + newLup: 0 + }); + } + (interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.107179440500000000 * 1e18); // interest rate increased over 10% to 10.7% + + // lender can reset interest rate to 10% (even if 12 hours not passed) by triggering any action + changePrank(_lender); + vm.expectEmit(true, true, true, true); + emit ResetInterestRate(0.107179440500000000 * 1e18, 0.1 * 1e18); + _pool.updateInterest(); + (interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.1 * 1e18); // interest rate resetted to 10% + } + + function testPOCOverCollateralized_MultipleBorrowers_LowDebt() external { + + // 10 borrowers borrow 120 usdc collateralized by 10 eth + address[] memory otherBorrowers = new address[](10); + for (uint index; index < 10; ++index) { + otherBorrowers[index] = address(bytes20(keccak256(abi.encodePacked(index + 0x1000)))); + vm.stopPrank(); // test helper contains a startPrank without a stopPrank + + _mintCollateralAndApproveTokens(otherBorrowers[index], 100 * 1e18); + _drawDebt({ + from: otherBorrowers[index], + borrower: otherBorrowers[index], + amountToBorrow: 120 * 1e18, // borrow 120 usdc + limitIndex: 3_000, + collateralToPledge: 10 * 1e18, // collateralized by 10 eth + newLup:0 + }); + } + + (uint interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.05 * 1e18); // 5% initial interest rate + + //pay down a little ($1) every 12 hours to trigger interestRate update + for (uint index; index < 8; ++index) { + skip(12.01 hours); // actually needs to be > 12 hours to trigger interestRate update + _repayDebt({ + from: otherBorrowers[0], + borrower: otherBorrowers[0], + amountToRepay: 1 * 1e18, + amountRepaid: 1 * 1e18, + collateralToPull: 0, + newLup: 0 + }); + } + (interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.107179440500000000 * 1e18); // interest rate increased over 10% to 10.7% + + // lender can reset interest rate to 10% (even if 12 hours not passed) by triggering any action + changePrank(_lender); + vm.expectEmit(true, true, true, true); + emit ResetInterestRate(0.107179440500000000 * 1e18, 0.1 * 1e18); + _pool.updateInterest(); + (interestRate,) = _pool.interestRateInfo(); + assertEq(interestRate, 0.1 * 1e18); // interest rate resetted to 10% + } + } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index 97f8a5f07..e787b6f2a 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -432,21 +432,21 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { unchecked { ++i; } } - // show the rate maxed out at 50000% + // show the rate reset to 10% _assertPool( PoolParams({ - htp: 0.001443490644739886 * 1e18, + htp: 1028117286, lup: _p1505_26, - poolSize: 10_012.269795822390450000 * 1e18, + poolSize: 10_000.000000230822950000 * 1e18, pledgedCollateral: 10_000.0 * 1e18, - encumberedCollateral: 0.009589619533804370 * 1e18, - poolDebt: 14.434906454054174087 * 1e18, - actualUtilization: 0.000516541356456424 * 1e18, - targetUtilization: 0.000000093921320113 * 1e18, - minDebtAmount: 1.443490645405417409 * 1e18, + encumberedCollateral: 0.000000006830147214 * 1e18, + poolDebt: 0.000010281172862060 * 1e18, + actualUtilization: 0.000000001027819578 * 1e18, + targetUtilization: 0.000000000000681949 * 1e18, + minDebtAmount: 0.000001028117286206 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 500.0 * 1e18, + interestRate: 0.1 * 1e18, // rate reset to 10% interestRateUpdate: _startTime + (194 * 12 hours) }) ); From 0c64f0e07797f72f2beaacf3b5609fbfa53a93ae Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Sun, 9 Apr 2023 16:19:08 -0400 Subject: [PATCH 48/70] Restrict Deployment of Matching Quote and Collateral tokens (#735) # Description of change ## High level * added a revert in the `canDeploy` pool modifier: `DeployQuoteCollateralSameToken()`. * The revert fires if the `quote` and `collateral` addresses are equal to one another. # Description of bug or vulnerability and solution * Protocol allowed for pools that had the same `quote` and `collateral` token. * Not a bug but an undesired user experience and could result in UI confusion / error # Contract size ## Pre Change ``` ERC721PoolFactory - 3,340B (13.59%) ERC20PoolFactory - 2,473B (10.06%) ``` ## Post Change ``` ERC721PoolFactory - 3,390B (13.79%) ERC20PoolFactory - 2,523B (10.27%) ``` # Gas usage ## Pre Change ``` | src/ERC20PoolFactory.sol:ERC20PoolFactory contract | | | | | | |----------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 5393302 | 26753 | | | | | | Function Name | min | avg | median | max | # calls | | deployPool | 602 | 226907 | 232241 | 255710 | 252 | | deployedPools | 854 | 2417 | 2854 | 2854 | 55 | | deployedPoolsList | 703 | 703 | 703 | 703 | 3 | | src/ERC721PoolFactory.sol:ERC721PoolFactory contract | | | | | | |------------------------------------------------------|-----------------|-------------------|--------|---------------------|---------| | Deployment Cost | Deployment Size | | | | | | 5692525 | 28251 | | | | | | Function Name | min | avg | median | max | # calls | | deployPool | 926 | 66202914522633298 | 303974 | 8937393460516718734 | 135 | | deployedPools | 898 | 2461 | 2898 | 2898 | 55 | | deployedPoolsList | 614 | 614 | 614 | 614 | 1 | ``` ## Post Change ``` | src/ERC20PoolFactory.sol:ERC20PoolFactory contract | | | | | | |----------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 5403305 | 26803 | | | | | | Function Name | min | avg | median | max | # calls | | deployPool | 603 | 226105 | 232300 | 255769 | 254 | | deployedPools | 854 | 1970 | 2854 | 2854 | 77 | | deployedPoolsList | 703 | 703 | 703 | 703 | 3 | | src/ERC721PoolFactory.sol:ERC721PoolFactory contract | | | | | | |------------------------------------------------------|-----------------|-------------------|--------|---------------------|---------| | Deployment Cost | Deployment Size | | | | | | 5702528 | 28301 | | | | | | Function Name | min | avg | median | max | # calls | | deployPool | 927 | 64297794680261978 | 304033 | 8937393460516718735 | 139 | | deployedPools | 898 | 2014 | 2898 | 2898 | 77 | | deployedPoolsList | 614 | 614 | 614 | 614 | 1 | ``` --------- Co-authored-by: Ian Harvey --- src/base/PoolDeployer.sol | 5 +++-- src/interfaces/pool/IPoolFactory.sol | 4 ++++ .../unit/ERC20Pool/ERC20PoolFactory.t.sol | 11 +++++++++- .../unit/ERC721Pool/ERC721PoolFactory.t.sol | 22 ++++++++++++++----- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/base/PoolDeployer.sol b/src/base/PoolDeployer.sol index 2ae7459c6..7be189a28 100644 --- a/src/base/PoolDeployer.sol +++ b/src/base/PoolDeployer.sol @@ -36,8 +36,9 @@ abstract contract PoolDeployer { * @dev Used by both ERC20, and ERC721 pool factory types. */ modifier canDeploy(address collateral_, address quote_, uint256 interestRate_) { - if (collateral_ == address(0) || quote_ == address(0)) revert IPoolFactory.DeployWithZeroAddress(); - if (MIN_RATE > interestRate_ || interestRate_ > MAX_RATE) revert IPoolFactory.PoolInterestRateInvalid(); + if (collateral_ == quote_) revert IPoolFactory.DeployQuoteCollateralSameToken(); + if (collateral_ == address(0) || quote_ == address(0)) revert IPoolFactory.DeployWithZeroAddress(); + if (MIN_RATE > interestRate_ || interestRate_ > MAX_RATE) revert IPoolFactory.PoolInterestRateInvalid(); _; } diff --git a/src/interfaces/pool/IPoolFactory.sol b/src/interfaces/pool/IPoolFactory.sol index 993c4eeef..bcf64a367 100644 --- a/src/interfaces/pool/IPoolFactory.sol +++ b/src/interfaces/pool/IPoolFactory.sol @@ -11,6 +11,10 @@ interface IPoolFactory { /**************/ /*** Errors ***/ /**************/ + /** + * @notice Can't deploy if quote and collateral are the same token. + */ + error DeployQuoteCollateralSameToken(); /** * @notice Can't deploy with one of the args pointing to the 0x0 address. diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol index 1181d6076..a4bcc5fe6 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol @@ -74,7 +74,8 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { }); // should deploy different pool - address poolTwo = _poolFactory.deployPool(address(_collateral), address(_collateral), 0.05 * 10**18); + address compAddress = 0xc00e94Cb662C3520282E6f5717214004A7f26888; + address poolTwo = _poolFactory.deployPool(address(_collateral), compAddress, 0.05 * 10**18); assertFalse(poolOne == poolTwo); // check tracking of deployed pools @@ -240,4 +241,12 @@ contract ERC20PoolFactoryTest is ERC20HelperContract { ERC20Pool(pool).initialize(0.05 * 10**18); } + function testDeployERC20SameQuoteCollateral() external { + skip(333); + + address usdcAddress = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + vm.expectRevert(IPoolFactory.DeployQuoteCollateralSameToken.selector); + _poolFactory.deployPool(usdcAddress, usdcAddress, 0.0543 * 1e18 ); + } + } diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol index b4a12f555..71189fe7a 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol @@ -20,6 +20,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { ERC721PoolFactory internal _factory; uint256[] internal _tokenIdsSubsetOne; uint256[] internal _tokenIdsSubsetTwo; + uint256[] internal tokenIds; function setUp() external { _startTime = block.timestamp; @@ -30,7 +31,6 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { _factory = new ERC721PoolFactory(_ajna); // deploy NFT collection pool - uint256[] memory tokenIds; _NFTCollectionPoolAddress = _factory.deployPool(address(_collateral), address(_quote), tokenIds, 0.05 * 10**18); _NFTCollectionPool = ERC721Pool(_NFTCollectionPoolAddress); @@ -114,7 +114,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { // should revert if trying to deploy with interest rate lower than accepted _assertDeployWithInvalidRateRevert({ poolFactory: address(_factory), - collateral: address(_quote), + collateral: address(new NFTCollateralToken()), quote: address(_quote), interestRate: 10**18 }); @@ -122,7 +122,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { // should revert if trying to deploy with interest rate higher than accepted _assertDeployWithInvalidRateRevert({ poolFactory: address(_factory), - collateral: address(_quote), + collateral: address(new NFTCollateralToken()), quote: address(_quote), interestRate: 2 * 10**18 }); @@ -153,7 +153,6 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { } function testDeployERC721PoolWithMinRate() external { - uint256[] memory tokenIds = new uint256[](0); _factory.deployPool( address(new NFTCollateralToken()), address(new TokenWithNDecimals("Quote", "Q1", 18)), @@ -167,7 +166,6 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { } function testDeployERC721PoolWithMaxRate() external { - uint256[] memory tokenIds = new uint256[](0); _factory.deployPool( address(new NFTCollateralToken()), address(new TokenWithNDecimals("Quote", "Q1", 18)), @@ -318,4 +316,18 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { assertFalse(_NFTSubsetOnePool.tokenIdsAllowed(10)); } + function testDeployERC721SameQuoteCollateral() external { + skip(333); + + address NFTCollectionAddress = address(new NFTCollateralToken()); + + vm.expectRevert(IPoolFactory.DeployQuoteCollateralSameToken.selector); + _factory.deployPool( + address(NFTCollectionAddress), + address(NFTCollectionAddress), + tokenIds, + 0.5 * 10**18 + ); + } + } From 05363b9455c1b69044f8a0538d1ed242b2d3cca1 Mon Sep 17 00:00:00 2001 From: mattcushman <36414299+mattcushman@users.noreply.github.com> Date: Mon, 10 Apr 2023 01:28:37 -0400 Subject: [PATCH 49/70] Mau overflow regression fix (fix for issue #724) (#726) * Fix ema overflow * Updated baselines * Reuse calculated pool debt when calculating newMeaningfulDeposit * Changes after review: fix comment * Remove comment --------- Co-authored-by: mwc Co-authored-by: grandizzy --- src/libraries/external/PoolCommons.sol | 16 +++++++++----- .../RegressionTestReservesERC20Pool.t.sol | 3 +-- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 6 ++--- ...ERC20PoolLiquidationsKickWithDeposit.t.sol | 2 +- .../ERC20Pool/ERC20PoolLiquidationsMisc.t.sol | 4 ++-- .../ERC20PoolLiquidationsSettle.t.sol | 22 +++++++++---------- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 2 +- .../ERC721Pool/ERC721PoolCollateral.t.sol | 4 ++-- .../ERC721PoolLiquidationsSettleAuction.t.sol | 8 +++---- 9 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index b45682468..d096fb1ea 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -95,13 +95,17 @@ library PoolCommons { vars.t0Debt2ToCollateral = interestParams_.t0Debt2ToCollateral; // calculate new interest params - vars.newMeaningfulDeposit = _meaningfulDeposit( - deposits_, - poolState_.t0Debt, - poolState_.inflator, - vars.t0Debt2ToCollateral + vars.newDebt = poolState_.debt; + // new meaningful deposit cannot be less than pool's debt + vars.newMeaningfulDeposit = Maths.max( + _meaningfulDeposit( + deposits_, + poolState_.t0Debt, + poolState_.inflator, + vars.t0Debt2ToCollateral + ), + vars.newDebt ); - vars.newDebt = poolState_.debt; vars.newDebtCol = Maths.wmul(poolState_.inflator, vars.t0Debt2ToCollateral); vars.newLupt0Debt = Maths.wmul(lup_, poolState_.t0Debt); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 349dad593..526aad653 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -377,8 +377,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_fenwick_prefixSumIndex_F4(); } - // FIXME: Seems to be an issue with Deposits.mult() in accrue interest or some issue with timestamp in invariant setup - function _test_regression_kick_F1_F2() external { + function test_regression_kick_F1_F2() external { _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); _reserveERC20PoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); _reserveERC20PoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 1c89bf997..83a526a71 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -752,14 +752,14 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.364405977643667984 * 1e18, poolDebt: 9_997.034647576329686631 * 1e18, - actualUtilization: 1.246708347279785558 * 1e18, + actualUtilization: 0.737099854301327286 * 1e18, targetUtilization: 1.026218700245164092 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), - interestRate: 0.0495 * 1e18, + interestRate: 0.0405 * 1e18, interestRateUpdate: _startTime + 100 days + 14 hours }) ); } -} \ No newline at end of file +} diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol index 9911c3d4c..2b7f74dae 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol @@ -1240,7 +1240,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { pledgedCollateral: 3_978.965725315792902720 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 1.005561359294055756 * 1e18, + actualUtilization: 0.804376804158714743 * 1e18, targetUtilization: 203_119_829.249967715714383718 * 1e18, minDebtAmount: 0, loans: 0, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol index 6697806b0..21bc32f71 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol @@ -514,7 +514,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 82_124_923_660.837160770168974387 * 1e18, poolDebt: 8_199.047110922993196875 * 1e18, - actualUtilization: 1.007788860397780716 * 1e18, + actualUtilization: 0.999255137826161539 * 1e18, targetUtilization: 0.912833017390738664 * 1e18, minDebtAmount: 0, loans: 0, @@ -552,7 +552,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 1.007788860397780716 * 1e18, + actualUtilization: 0.999255137826161539 * 1e18, targetUtilization: 0.912833017390738664 * 1e18, minDebtAmount: 0, loans: 0, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index 459e0f54c..d7de2626c 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -379,7 +379,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_807.556879218687263264 * 1e18, + deposit: 8_807.556879218687263263 * 1e18, exchangeRate: 0.800686989019880660 * 1e18 }); _assertBucket({ @@ -393,11 +393,11 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { PoolParams({ htp: 9.771304290202671377 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_807.556879218687263264 * 1e18, + poolSize: 63_807.556879218687263263 * 1e18, pledgedCollateral: 2 * 1e18, encumberedCollateral: 2.010288427770370775 * 1e18, poolDebt: 19.542608580405342754 * 1e18, - actualUtilization: 68.294962938223935750 * 1e18, // check why + actualUtilization: 0.993531222480240308 * 1e18, targetUtilization: 2.940996143103583563 * 1e18, minDebtAmount: 1.954260858040534275 * 1e18, loans: 1, @@ -867,8 +867,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0 * 1e18, - deposit: 9_036.878045597692505047 * 1e18, - exchangeRate: 0.821534367781608410 * 1e18 + deposit: 9_036.878038705612120074 * 1e18, + exchangeRate: 0.821534367155055647 * 1e18 }); _pool.moveQuoteToken(10000000000 * 1e18, _i9_72, _i9_91, type(uint256).max); @@ -996,7 +996,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { _kick({ from: actor4, borrower: actor2, - debt: 58_679_160_247.182050965227801655 * 1e18, + debt: 58_824_226_156.322179644993506974 * 1e18, collateral: 21_009_851.171858165566322122 * 1e18, bond: 580_263_636.560514719062821277 * 1e18, transferAmount: 580_263_636.560514719062821277 * 1e18 @@ -1007,7 +1007,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).updateInterest(); _startClaimableReserveAuction({ from: actor1, - remainingReserves: 642_374_224.754246627157382301 * 1e18, + remainingReserves: 785_271_398.552730383160590325 * 1e18, price: 1_000_000_000 * 1e18, epoch: 1 }); @@ -1031,21 +1031,21 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).updateInterest(); (uint256 borrowerDebt, , ) = _poolUtils.borrowerInfo(address(_pool), actor2); - assertEq(borrowerDebt, 60_144_029_463.415046012797744619 * 1e18); + assertEq(borrowerDebt, 60_623_994_637.102713529810847735 * 1e18); (uint256 reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); - assertEq(reserves, 513_130_796.902799297066317156 * 1e18); + assertEq(reserves, 294_122_188.473918671419077285 * 1e18); // settle auction with reserves _settle({ from: actor6, borrower: actor2, maxDepth: 2, - settledDebt: 57_093_334_850.248360626382636508 * 1e18 + settledDebt: 57_234_480_301.052435683555399515 * 1e18 }); (reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); - assertEq(reserves, 3); + assertEq(reserves, 1); } } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 2dc3e4e16..1a10ed2fb 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -1013,7 +1013,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 992.0 * 1e18, encumberedCollateral: 925.265940856763249327 * 1e18, poolDebt: 8_994.783964905591719091 * 1e18, - actualUtilization: 0.621424447057623174 * 1e18, + actualUtilization: 0.581968058078338251 * 1e18, targetUtilization: 1.023051673698045482 * 1e18, minDebtAmount: 449.739198245279585955 * 1e18, loans: 2, diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol index 2e74bf29c..d1f1a8015 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol @@ -882,7 +882,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { pledgedCollateral: 1.139719990175231268 * 1e18, encumberedCollateral: 4403983968.683524073950025244 * 1e18, poolDebt: 439.677389340513210329 * 1e18, - actualUtilization: 18.990844967339121167 * 1e18, + actualUtilization: 1.117110216223813782 * 1e18, targetUtilization: 2_995_775_262.887499112057200872 * 1e18, minDebtAmount: 0, loans: 0, @@ -1167,7 +1167,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 808.632216044431490433 * 1e18, + actualUtilization: 1.002468098960724093 * 1e18, targetUtilization: 13_424_722_854.598244140780064966 * 1e18, minDebtAmount: 0, loans: 0, diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol index 9d632cd74..098503001 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -408,8 +408,8 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { index: 2501, lpBalance: 2_000 * 1e18, collateral: 0.110649792292326223 * 1e18, - deposit: 1_575.729756218596960768 * 1e18, - exchangeRate: 1.000557690748354812 * 1e18 + deposit: 1_575.729229030217308768 * 1e18, + exchangeRate: 1.000557427154164986 * 1e18 }); _assertBucket({ index: MAX_FENWICK_INDEX, @@ -1242,8 +1242,8 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { index: 2500, lpBalance: 4_997.115384615384614000 * 1e18, collateral: 0.886287035829988542 * 1e18, - deposit: 1_574.490070004654380295 * 1e18, - exchangeRate: 1.000336486815856336 * 1e18 + deposit: 1_574.488096437633785035 * 1e18, + exchangeRate: 1.000336091874601493 * 1e18 }); _assertBucket({ index: 2502, From a375bf7f53c071e3f8f66b1d60a3e1968478c950 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 10 Apr 2023 20:03:25 +0300 Subject: [PATCH 50/70] ToB improvements (#736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ToB improvement: Ensure that comments in code always reflect the code’s intended behavior * Constant variable WAD is declared but not used in Maths.sol. * More external view functions * Add test for Maths.minInt and Maths.maxInt * In LenderActions.addQuoteToken use Buckets.addLenderLPs helper function * Changes after review: added description of requiresd calls prior memorializePositions * Fix Position Manager functions descriptions --- README.md | 1 - src/ERC721Pool.sol | 6 +++--- src/base/Pool.sol | 6 +++--- src/base/PoolDeployer.sol | 6 +++--- .../position/IPositionManagerOwnerActions.sol | 4 ++-- src/libraries/external/Auctions.sol | 2 -- src/libraries/external/BorrowerActions.sol | 2 +- src/libraries/external/LenderActions.sol | 7 +------ src/libraries/external/PositionNFTSVG.sol | 6 +++--- src/libraries/internal/Deposits.sol | 2 +- src/libraries/internal/Maths.sol | 10 +++++----- tests/forge/unit/MathTest.t.sol | 11 +++++++++++ 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 5f38e0b7a..9abab940a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ The Ajna protocol is a non-custodial, peer-to-peer, permissionless lending, borr ## Accepted tokens: - Fungible tokens (following the [ERC20 token standard](https://eips.ethereum.org/EIPS/eip-20)). - Non-fungible tokens (following the [ERC721 token standard](https://eips.ethereum.org/EIPS/eip-721)) -- Special considerations have been made to support specific NFTs with nonstandard ERC721 implementations, including _CryptoPunks_ and _CryptoKitties_. This support is limited to Ethereum mainnet. ### Token limitations - The following types of tokens are incompatible with Ajna, and no countermeasures exist to explicitly prevent creating a pool with such tokens, actors should use them at their own risk: diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 4c1e12117..d8217359f 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -650,9 +650,9 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { IERC721Token(_getArgAddress(COLLATERAL_ADDRESS)).transferFrom(from_, to_, tokenId_); } - /**************************/ - /*** External Functions ***/ - /**************************/ + /*******************************/ + /*** External View Functions ***/ + /*******************************/ /// @inheritdoc IERC721PoolState function totalBorrowerTokens(address borrower_) external view override returns(uint256) { diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 0b4f4d1fc..63b62f379 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -645,9 +645,9 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { return _priceAt(Deposits.findIndexOfSum(deposits, debt_)); } - /**************************/ - /*** External Functions ***/ - /**************************/ + /*******************************/ + /*** External View Functions ***/ + /*******************************/ /// @inheritdoc IPoolState function auctionInfo( diff --git a/src/base/PoolDeployer.sol b/src/base/PoolDeployer.sol index 7be189a28..977c04f7b 100644 --- a/src/base/PoolDeployer.sol +++ b/src/base/PoolDeployer.sol @@ -42,9 +42,9 @@ abstract contract PoolDeployer { _; } - /**************************/ - /*** External Functions ***/ - /**************************/ + /*******************************/ + /*** External View Functions ***/ + /*******************************/ /** * @notice Returns the list of all deployed pools. diff --git a/src/interfaces/position/IPositionManagerOwnerActions.sol b/src/interfaces/position/IPositionManagerOwnerActions.sol index 0b2802ff4..60418951c 100644 --- a/src/interfaces/position/IPositionManagerOwnerActions.sol +++ b/src/interfaces/position/IPositionManagerOwnerActions.sol @@ -21,7 +21,7 @@ interface IPositionManagerOwnerActions { * @dev The array of buckets is expected to be constructed off chain by scanning events for that lender. * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. * @dev An additional call is made to the pool to transfer the LPs from their previous owner, to the Position Manager. - * @dev Pool.setPositionOwner() must be called prior to calling this method. + * @dev `Pool.increaseLPsAllowance` must be called prior to calling this method in order to allow Position manager contract to transfer LPs to be memorialized. * @param params Calldata struct supplying inputs required to conduct the memorialization. */ function memorializePositions( @@ -51,7 +51,7 @@ interface IPositionManagerOwnerActions { * @dev The array of buckets is expected to be constructed off chain by scanning events for that lender. * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. * @dev An additional call is made to the pool to transfer the LPs Position Manager to owner. - * @dev Pool.setPositionOwner() must be called prior to calling this method. + * @dev `Pool.approveLPsTransferors` must be called prior to calling this method in order to allow Position manager contract to transfer redeemed LPs. * @param params Calldata struct supplying inputs required to conduct the redeem. */ function reedemPositions( diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol index 73b28b178..6c42b5f97 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -1248,7 +1248,6 @@ library Auctions { * @dev write state: * - borrower -> liquidation mapping update * - increment auctions count accumulator - * - increment auctions.totalBondEscrowed accumulator * - updates auction queue state * @param borrowerAddress_ Address of the borrower that is kicked. * @param bondSize_ Bond size to cover newly kicked auction. @@ -1300,7 +1299,6 @@ library Auctions { * @dev write state: * - decrement kicker locked accumulator, increment kicker claimable accumumlator * - decrement auctions count accumulator - * - decrement auctions.totalBondEscrowed accumulator * - update auction queue state * @param borrower_ Auctioned borrower address. */ diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index 7c31123b7..3512143dd 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -289,7 +289,7 @@ library BorrowerActions { vars.repay = maxQuoteTokenAmountToRepay_ != 0; vars.pull = collateralAmountToPull_ != 0; - // revert if no amount to pledge or borrow + // revert if no amount to pull or repay if (!vars.repay && !vars.pull) revert InvalidAmount(); Borrower memory borrower = loans_.borrowers[borrowerAddress_]; diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index 71eb7e7c6..c893aa9b3 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -190,12 +190,7 @@ library LenderActions { Deposits.unscaledAdd(deposits_, params_.index, Maths.wdiv(addedAmount, bucketScale)); // update lender LPs - Lender storage lender = bucket.lenders[msg.sender]; - - if (bankruptcyTime >= lender.depositTime) lender.lps = bucketLPs_; - else lender.lps += bucketLPs_; - - lender.depositTime = block.timestamp; + Buckets.addLenderLPs(bucket, bankruptcyTime, msg.sender, bucketLPs_); // update bucket LPs bucket.lps += bucketLPs_; diff --git a/src/libraries/external/PositionNFTSVG.sol b/src/libraries/external/PositionNFTSVG.sol index 279cc4ad8..971453ee6 100644 --- a/src/libraries/external/PositionNFTSVG.sol +++ b/src/libraries/external/PositionNFTSVG.sol @@ -26,9 +26,9 @@ library PositionNFTSVG { uint256[] indexes; // the array of price buckets index with LPs to be tracked by the NFT } - /**************************/ - /*** External Functions ***/ - /**************************/ + /*******************************/ + /*** External View Functions ***/ + /*******************************/ function constructTokenURI(ConstructTokenURIParams memory params_) external pure returns (string memory) { // set token metadata diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index fb9e43bd2..c66e55fda 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -192,7 +192,7 @@ library Deposits { // Unset the bit in index to continue traversing up the Fenwick tree index_ -= bit; } else { - // Case 1 above. superRangeIndex is the index of the node to consider that + // Case 2 above. superRangeIndex is the index of the node to consider that // contains the sub range that was already scaled in prior iteration uint256 superRangeIndex = index_ + bit; diff --git a/src/libraries/internal/Maths.sol b/src/libraries/internal/Maths.sol index 369f563bf..8f2187f81 100644 --- a/src/libraries/internal/Maths.sol +++ b/src/libraries/internal/Maths.sol @@ -11,19 +11,19 @@ library Maths { uint256 internal constant WAD = 1e18; function wmul(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * y + 1e18 / 2) / 1e18; + return (x * y + WAD / 2) / WAD; } function floorWmul(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * y) / 1e18; + return (x * y) / WAD; } function wdiv(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e18 + y / 2) / y; + return (x * WAD + y / 2) / y; } function floorWdiv(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * 1e18) / y; + return (x * WAD) / y; } function max(uint256 x, uint256 y) internal pure returns (uint256) { @@ -35,7 +35,7 @@ library Maths { } function wad(uint256 x) internal pure returns (uint256) { - return x * 1e18; + return x * WAD; } function rmul(uint256 x, uint256 y) internal pure returns (uint256) { diff --git a/tests/forge/unit/MathTest.t.sol b/tests/forge/unit/MathTest.t.sol index 43de46457..88075e6ba 100644 --- a/tests/forge/unit/MathTest.t.sol +++ b/tests/forge/unit/MathTest.t.sol @@ -68,4 +68,15 @@ contract MathTest is DSTestPlus { assertEq(Maths.wmul( 1.4534563955 * 1e18, 0.112224325121212178 * 1e18), 0.163113163078097153 * 1e18); assertEq(Maths.floorWmul(1.4534563955 * 1e18, 0.112224325121212178 * 1e18), 0.163113163078097152 * 1e18); } + + function testMaxMinInt() external { + assertEq(Maths.maxInt(-1, 2), 2); + assertEq(Maths.minInt(-1, 2), -1); + + assertEq(Maths.maxInt(-1, -1), -1); + assertEq(Maths.minInt(-1, -1), -1); + + assertEq(Maths.maxInt(2, -1), 2); + assertEq(Maths.minInt(2, -1), -1); + } } From eed9f63a6ee660282b692d0ee63a8965331877a7 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 11 Apr 2023 08:03:00 +0300 Subject: [PATCH 51/70] ToB improvement: emit event when Flashloan taken (#731) * ToB improvement: emit event when Flashloan taken * Changes after review - add comment * Set optimizers run to 100 --- Makefile | 4 ++-- brownie-config.yml | 1 + foundry.toml | 6 ++---- src/base/FlashloanablePool.sol | 4 +++- src/interfaces/pool/commons/IPoolEvents.sol | 12 ++++++++++++ tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol | 11 ++++++++++- .../forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol | 3 +++ 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 84c9cc44f..0b7dc49cc 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,8 @@ build :; forge clean && forge build # Tests test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM -test-with-gas-report :; FOUNDRY_PROFILE=optimized forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM -test-load :; FOUNDRY_PROFILE=optimized forge test --match-test testLoad --gas-report +test-with-gas-report :; forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM +test-load :; forge test --match-test testLoad --gas-report test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC20 test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC721 diff --git a/brownie-config.yml b/brownie-config.yml index 714c8d7de..9171503eb 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -17,6 +17,7 @@ compiler: version: 0.8.14 optimizer: enabled: true + runs: 100 remappings: - "@ds-math=lib/ds-math/src/" - "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts" diff --git a/foundry.toml b/foundry.toml index db207bdd5..b60dd03d0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,10 +17,8 @@ block_timestamp = 1_672_372_127 block_number = 16_295_000 fork_block_number = 16_295_000 rpc_storage_caching = { chains = ["mainnet"], endpoints = "all" } - -[profile.optimized] -optimizer = true -optimizer_runs = 200 +optimizer = true +optimizer_runs = 100 [fuzz] runs = 300 diff --git a/src/base/FlashloanablePool.sol b/src/base/FlashloanablePool.sol index 22e70da69..53a109a97 100644 --- a/src/base/FlashloanablePool.sol +++ b/src/base/FlashloanablePool.sol @@ -21,7 +21,7 @@ abstract contract FlashloanablePool is Pool { * @notice Called by flashloan borrowers to borrow liquidity which must be repaid in the same transaction. * @param receiver_ Address of the contract which implements the appropriate interface to receive tokens. * @param token_ Address of the ERC20 token caller wants to borrow. - * @param amount_ The amount of tokens to borrow. + * @param amount_ The denormalized amount (dependent upon token precision) of tokens to borrow. * @param data_ User-defined calldata passed to the receiver. * @return success_ True if flashloan was successful. */ @@ -54,6 +54,8 @@ abstract contract FlashloanablePool is Pool { if (tokenContract.balanceOf(address(this)) != initialBalance) revert FlashloanIncorrectBalance(); success_ = true; + + emit Flashloan(address(receiver_), token_, amount_); } /** diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index 292176075..7f3c0e000 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -315,6 +315,18 @@ interface IPoolEvents { uint256 lpForfeited ); + /** + * @notice Emitted when a flashloan is taken from pool. + * @param receiver The address receiving the flashloan. + * @param token The address of token flashloaned from pool. + * @param amount The amount of tokens flashloaned from pool. + */ + event Flashloan( + address indexed receiver, + address indexed token, + uint256 amount + ); + /** * @notice Emitted when a loan Neutral Price is restamped. * @param borrower Identifies the loan to update the Neutral Price. diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol index 0b34fae15..48266d7bb 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFlashloan.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; +import { ERC20HelperContract } from './ERC20DSTestPlus.sol'; import { FlashloanBorrower, SomeDefiStrategy, @@ -79,6 +79,9 @@ contract ERC20PoolFlashloanTest is ERC20HelperContract { // Use a flashloan to interact with the strategy assertEq(_collateral.balanceOf(address(flasher)), 0); assertTrue(!flasher.callbackInvoked()); + + vm.expectEmit(true, true, false, true); + emit Flashloan(address(flasher), address(_collateral), loanAmount); _pool.flashLoan(flasher, address(_collateral), loanAmount, new bytes(0)); assertTrue(flasher.callbackInvoked()); assertEq(_collateral.balanceOf(address(flasher)), 3.5 * 1e18); @@ -228,6 +231,9 @@ contract ERC20PoolFlashloanPrecisionTest is ERC20HelperContract { // Use a flashloan to interact with the strategy assertEq(WBTC.balanceOf(address(flasher)), 0); assertTrue(!flasher.callbackInvoked()); + + vm.expectEmit(true, true, false, true); + emit Flashloan(address(flasher), address(WBTC), loanAmount); _pool.flashLoan(flasher, address(WBTC), loanAmount, new bytes(0)); assertTrue(flasher.callbackInvoked()); assertEq(WBTC.balanceOf(address(flasher)), 0.35 * 1e8); @@ -258,6 +264,9 @@ contract ERC20PoolFlashloanPrecisionTest is ERC20HelperContract { // Use a flashloan to interact with the strategy assertEq(USDC.balanceOf(address(flasher)), 0); assertTrue(!flasher.callbackInvoked()); + + vm.expectEmit(true, true, false, true); + emit Flashloan(address(flasher), address(USDC), loanAmount); _pool.flashLoan(flasher, address(USDC), loanAmount, new bytes(0)); assertTrue(flasher.callbackInvoked()); assertEq(USDC.balanceOf(address(flasher)), 350 * 1e6); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol index 9bf2fe4af..c05ac0c97 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFlashloan.t.sol @@ -78,6 +78,9 @@ contract ERC721PoolFlashloanTest is ERC721HelperContract { // Use a flashloan to interact with the strategy assertEq(_quote.balanceOf(address(flasher)), 0); assertTrue(!flasher.callbackInvoked()); + + vm.expectEmit(true, true, false, true); + emit Flashloan(address(flasher), address(_quote), loanAmount); _pool.flashLoan(flasher, address(_quote), loanAmount, new bytes(0)); assertTrue(flasher.callbackInvoked()); assertEq(_quote.balanceOf(address(flasher)), 3.5 * 1e18); From 704a7a332ef86222ff1ec183703119448d0f594e Mon Sep 17 00:00:00 2001 From: mattcushman <36414299+mattcushman@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:02:34 -0400 Subject: [PATCH 52/70] Ensure pool debt doesn't exceed deposits when remove quote token (#738) * Limit max amount to remove by pool quote token balance * Update optimizer runs to 100 * Remove optimized profile in foundry.toml * lup return 0 if debt exceeds deposit * Fix tests, undo contract size / optimizer runs changes * Revert foundry.toml default profile change * Check of pool debt greater than deposits sum outside lup calculation * Add condition to check pool debt and more comments * Change after review: comment and reuse of assert lup below htp helper --------- Co-authored-by: prateek105 Co-authored-by: mwc Co-authored-by: grandizzy --- src/libraries/external/LenderActions.sol | 11 ++- .../unit/ERC20Pool/ERC20PoolQuoteToken.t.sol | 90 +++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index c893aa9b3..4e5db7cf1 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -20,7 +20,6 @@ import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Maths } from '../internal/Maths.sol'; - /** @title LenderActions library @notice External library containing logic for pool actors: @@ -394,8 +393,14 @@ library LenderActions { uint256 htp = Maths.wmul(params_.thresholdPrice, poolState_.inflator); - // check loan book's htp against new lup - if (htp > lup_) revert LUPBelowHTP(); + if ( + // check loan book's htp doesn't exceed new lup + htp > lup_ + || + // ensure that pool debt < deposits after removal + // this can happen if lup and htp are less than min bucket price and htp > lup (since LUP is capped at min bucket price) + (poolState_.debt != 0 && poolState_.debt > Deposits.treeSum(deposits_)) + ) revert LUPBelowHTP(); uint256 lpsRemaining = removeParams.bucketLPs - redeemedLPs_; diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol index 16bec678b..e766d2bd6 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -1298,4 +1298,94 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { assertEq(_quote.balanceOf(_lender), initialLenderBalance); } + + function testRemoveQuoteTokenPoolBalanceLimit() external tearDown { + _addLiquidity({ + from: _lender, + amount: 0.000000059754288926 * 1e18, + index: 852, + lpAward: 0.000000059754288926 * 1e18, + newLup: MAX_PRICE + }); + + _assertLenderLpBalance({ + lender: _lender, + index: 852, + lpBalance: 0.000000059754288926 * 1e18, + depositTime: _startTime + }); + + _assertPool( + PoolParams({ + htp: 0, + lup: MAX_PRICE, + poolSize: 0.000000059754288926 * 1e18, + pledgedCollateral: 0, + encumberedCollateral: 0, + poolDebt: 0, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 0, + loans: 0, + maxBorrower: address(0), + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + + _drawDebt({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 0.000000029877144463 * 1e18, + limitIndex: 7388, + collateralToPledge: 1 * 1e18, + newLup: 14_343_926.246295999585280544 * 1e18 + }); + + _assertPool( + PoolParams({ + htp: 0.000000029905872487 * 1e18, + lup: 14_343_926.246295999585280544 * 1e18, + poolSize: 0.000000059754288926 * 1e18, + pledgedCollateral: 1 * 1e18, + encumberedCollateral: 2085, + poolDebt: 0.000000029905872487 * 1e18, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 0.000000002990587249 * 1e18, + loans: 1, + maxBorrower: _borrower, + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + + skip(200 days); + + _assertPool( + PoolParams({ + htp: 0.000000029905872487 * 1e18, + lup: 14_343_926.246295999585280544 * 1e18, + poolSize: 0.000000059754288926 * 1e18, + pledgedCollateral: 1 * 1e18, + encumberedCollateral: 2143, + poolDebt: 0.000000030736538487 * 1e18, + actualUtilization: 0, + targetUtilization: 1e18, + minDebtAmount: 0.000000003073653849 * 1e18, + loans: 1, + maxBorrower: _borrower, + interestRate: 0.05 * 1e18, + interestRateUpdate: _startTime + }) + ); + + assertEq(_quote.balanceOf(address(_pool)), 0.000000029877144463 * 1e18); + + // removeQuoteToken should revert as LUP is bellow HTP + _assertRemoveAllLiquidityLupBelowHtpRevert({ + from: _lender, + index: 852 + }); + } } From a65bd98bfdd0c4dc863e6a6827e228abe3d72f80 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Wed, 12 Apr 2023 07:53:11 -0400 Subject: [PATCH 53/70] update PositionManager.MoveLiquidity event to provide all necessary details (#734) --- src/PositionManager.sol | 4 ++- .../position/IPositionManagerEvents.sol | 14 +++++++---- tests/forge/unit/PositionManager.t.sol | 12 ++++++--- .../forge/unit/Rewards/RewardsDSTestPlus.sol | 4 ++- tests/forge/unit/Rewards/RewardsManager.t.sol | 25 ++++++++++++++++++- tests/forge/utils/DSTestPlus.sol | 2 +- 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/PositionManager.sol b/src/PositionManager.sol index c1aa578d3..30b8bedfa 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -313,7 +313,9 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R ownerOf(params_.tokenId), params_.tokenId, params_.fromIndex, - params_.toIndex + params_.toIndex, + vars.lpbAmountFrom, + vars.lpbAmountTo ); } diff --git a/src/interfaces/position/IPositionManagerEvents.sol b/src/interfaces/position/IPositionManagerEvents.sol index 097109d51..f8ebe89e7 100644 --- a/src/interfaces/position/IPositionManagerEvents.sol +++ b/src/interfaces/position/IPositionManagerEvents.sol @@ -42,16 +42,20 @@ interface IPositionManagerEvents { /** * @notice Emitted when a position's liquidity is moved between buckets. - * @param lender Lender address. - * @param tokenId The tokenId of the newly minted NFT. - * @param fromIndex Index of bucket from where liquidity is moved. - * @param toIndex Index of bucket where liquidity is moved. + * @param lender Lender address. + * @param tokenId The tokenId of the newly minted NFT. + * @param fromIndex Index of bucket from where liquidity is moved. + * @param toIndex Index of bucket where liquidity is moved. + * @param lpRedeemedFrom Amount of LP removed from the `from` bucket. + * @param lpAwardedTo Amount of LP credited to the `to` bucket. */ event MoveLiquidity( address indexed lender, uint256 tokenId, uint256 fromIndex, - uint256 toIndex + uint256 toIndex, + uint256 lpRedeemedFrom, + uint256 lpAwardedTo ); /** diff --git a/tests/forge/unit/PositionManager.t.sol b/tests/forge/unit/PositionManager.t.sol index 9e6e8bf31..6a5832e1a 100644 --- a/tests/forge/unit/PositionManager.t.sol +++ b/tests/forge/unit/PositionManager.t.sol @@ -1656,8 +1656,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract ); // move liquidity called by testAddress1 owner + uint256 lpRedeemed = 2_500 * 1e18; + uint256 lpAwarded = 2_500 * 1e18; vm.expectEmit(true, true, true, true); - emit MoveLiquidity(testAddress1, tokenId1, mintIndex, moveIndex); + emit MoveLiquidity(testAddress1, tokenId1, mintIndex, moveIndex, lpRedeemed, lpAwarded); changePrank(address(testAddress1)); _positionManager.moveLiquidity(moveLiquidityParams); @@ -1782,8 +1784,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // move liquidity called by testAddress2 owner + lpRedeemed = 5_500 * 1e18; + lpAwarded = 5_500 * 1e18; vm.expectEmit(true, true, true, true); - emit MoveLiquidity(testAddress2, tokenId2, mintIndex, moveIndex); + emit MoveLiquidity(testAddress2, tokenId2, mintIndex, moveIndex, lpRedeemed, lpAwarded); changePrank(address(testAddress2)); _positionManager.moveLiquidity(moveLiquidityParams); @@ -3050,8 +3054,10 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac ); // move liquidity called by testAddress1 + uint256 lpRedeemed = 4_000 * 1e18; + uint256 lpAwarded = 4_000 * 1e18; vm.expectEmit(true, true, true, true); - emit MoveLiquidity(testAddress1, tokenId, indexes[0], indexes[1]); + emit MoveLiquidity(testAddress1, tokenId, indexes[0], indexes[1], lpRedeemed, lpAwarded); changePrank(testAddress1); _positionManager.moveLiquidity(moveLiquidityParams); diff --git a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol index 6003127e2..c380808fc 100644 --- a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol +++ b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol @@ -171,8 +171,10 @@ abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContrac address from, uint256 tokenId, uint256[] memory fromIndexes, + uint256[] memory lpsRedeemed, bool fromIndStaked, uint256[] memory toIndexes, + uint256[] memory lpsAwarded, uint256 expiry ) internal { @@ -181,7 +183,7 @@ abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContrac // check MoveLiquidity emits for (uint256 i = 0; i < fromIndexes.length; ++i) { vm.expectEmit(true, true, true, true); - emit MoveLiquidity(address(_rewardsManager), tokenId, fromIndexes[i], toIndexes[i]); + emit MoveLiquidity(address(_rewardsManager), tokenId, fromIndexes[i], toIndexes[i], lpsRedeemed[i], lpsAwarded[i]); } vm.expectEmit(true, true, true, true); diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index ac56b78bc..58a24afdb 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -1032,13 +1032,27 @@ contract RewardsManagerTest is RewardsHelperContract { secondIndexes[2] = 2558; secondIndexes[3] = 2559; secondIndexes[4] = 2560; + uint256[] memory secondLpsRedeemed = new uint256[](5); + secondLpsRedeemed[0] = 1_000 * 1e18; + secondLpsRedeemed[1] = 1_000 * 1e18; + secondLpsRedeemed[2] = 1_000 * 1e18; + secondLpsRedeemed[3] = 1_000 * 1e18; + secondLpsRedeemed[4] = 1_000 * 1e18; + uint256[] memory secondLpsAwarded = new uint256[](5); + secondLpsAwarded[0] = 1_000 * 1e18; + secondLpsAwarded[1] = 1_000 * 1e18; + secondLpsAwarded[2] = 1_000 * 1e18; + secondLpsAwarded[3] = 1_000 * 1e18; + secondLpsAwarded[4] = 1_000 * 1e18; _moveStakedLiquidity({ from: _minterOne, tokenId: tokenIdOne, fromIndexes: depositIndexes, + lpsRedeemed: secondLpsRedeemed, fromIndStaked: false, toIndexes: secondIndexes, + lpsAwarded: secondLpsAwarded, expiry: block.timestamp + 1000 }); @@ -1060,16 +1074,25 @@ contract RewardsManagerTest is RewardsHelperContract { /***********************/ // retrieve the position managers index set, recreating typical tx flow since positionIndexes are stored unordered in EnnumerableSets - secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); + secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); + secondLpsAwarded[0] = 1_000.000164743206012000 * 1e18; + secondLpsAwarded[1] = 1_006.443804104948552000 * 1e18; + secondLpsAwarded[2] = 1_000.000164743206012000 * 1e18; + secondLpsAwarded[3] = 1_000.000164743206012000 * 1e18; + secondLpsAwarded[4] = 1_000.000164743206012000 * 1e18; _moveStakedLiquidity({ from: _minterOne, tokenId: tokenIdOne, fromIndexes: secondIndexes, + lpsRedeemed: secondLpsRedeemed, fromIndStaked: true, toIndexes: depositIndexes, + lpsAwarded: secondLpsAwarded, expiry: block.timestamp + 1000 }); + return; + /******************************/ /*** Second Reserve Auction ***/ diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index af907558e..f6a4f9f8f 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -1366,7 +1366,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { event Burn(address indexed lender_, uint256 indexed price_); event MemorializePosition(address indexed lender_, uint256 tokenId_, uint256[] indexes_); event Mint(address indexed lender_, address indexed pool_, uint256 tokenId_); - event MoveLiquidity(address indexed owner_, uint256 tokenId_, uint256 fromIndex_, uint256 toIndex_); + event MoveLiquidity(address indexed owner_, uint256 tokenId_, uint256 fromIndex_, uint256 toIndex_, uint256 lpRedeemedFrom_, uint256 lpAwardedTo_); event RedeemPosition(address indexed lender_, uint256 tokenId_, uint256[] indexes_); From b5d1cd14ac4251ccdeff00f17f063579b58f4b84 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 13 Apr 2023 05:53:35 +0300 Subject: [PATCH 54/70] Libraries consistency: (#669) * Refactor LPs in funciton and events naming to LP * Consistent naming: rename start reserve auction to kick reserve auction. add KickReserveAuction event * Calculate LUP in single place Deposits.getLup(), remove duplicate functions to calculate LUP * Split Auctions library in KickerActions, TakerActions and SettlerActions libraries * Split LenderActions in LenderActions and LPOwnerActions external libs * cleanup interfaces, restructure them based on actors and actions: - IPoolLPOwnerActions interface split from IPoolLenderActions - IPoolLiquidationActions and IPoolReserveAuctionActions interfaces restructured in IPoolSettlerActions, IPoolKickerActions, IPoolTakerActions to match functionality of external libs * Deploy new libraries in brownie scripts * Apply same style for all events. Emit BucketBankruptcy after storage bucket update * Changes after review: rename from LPOwnerActions to LPActions, update comment * Update kickMomp description * More LPs to LP refactor - change function names, NAT specs and events / errors --- docs/Functions.md | 34 +- scripts/ajna_setup.py | 5 +- src/ERC20Pool.sol | 29 +- src/ERC721Pool.sol | 31 +- src/PoolInfoUtils.sol | 28 +- src/PositionManager.sol | 36 +- src/RewardsManager.sol | 8 +- src/base/Pool.sol | 90 +- src/interfaces/pool/IPool.sol | 29 +- src/interfaces/pool/commons/IPoolErrors.sol | 16 +- src/interfaces/pool/commons/IPoolEvents.sol | 78 +- .../pool/commons/IPoolInternals.sol | 11 +- .../pool/commons/IPoolKickerActions.sol | 52 + .../pool/commons/IPoolLPActions.sol | 76 + .../pool/commons/IPoolLenderActions.sol | 78 +- .../pool/commons/IPoolLiquidationActions.sol | 77 - .../commons/IPoolReserveAuctionActions.sol | 33 - .../pool/commons/IPoolSettlerActions.sol | 21 + src/interfaces/pool/commons/IPoolState.sol | 18 +- .../pool/commons/IPoolTakerActions.sol | 51 + .../pool/erc20/IERC20PoolLenderActions.sol | 2 +- .../pool/erc721/IERC721PoolEvents.sol | 2 +- .../pool/erc721/IERC721PoolLenderActions.sol | 4 +- .../position/IPositionManagerDerivedState.sol | 4 +- .../position/IPositionManagerOwnerActions.sol | 8 +- .../position/IPositionManagerState.sol | 2 +- .../rewards/IRewardsManagerState.sol | 2 +- src/libraries/external/Auctions.sol | 1579 ----------------- src/libraries/external/BorrowerActions.sol | 35 +- src/libraries/external/KickerActions.sol | 487 +++++ src/libraries/external/LPActions.sol | 280 +++ src/libraries/external/LenderActions.sol | 365 +--- src/libraries/external/PoolCommons.sol | 10 +- src/libraries/external/PositionNFTSVG.sol | 2 +- src/libraries/external/SettlerActions.sol | 399 +++++ src/libraries/external/TakerActions.sol | 762 ++++++++ src/libraries/helpers/PoolHelper.sol | 122 +- src/libraries/internal/Buckets.sol | 54 +- src/libraries/internal/Deposits.sol | 12 + tests/INVARIANTS.md | 12 +- tests/brownie/sdk/ajna_protocol.py | 14 +- tests/brownie/test_invariants.py | 6 +- tests/forge/interactions/NFTTakeExample.sol | 1 - .../forge/interactions/UniswapTakeExample.sol | 1 - .../ReserveERC20PoolInvariants.t.sol | 2 +- .../invariants/base/BasicInvariants.t.sol | 8 +- .../invariants/base/ReserveInvariants.t.sol | 2 +- .../base/handlers/BasicPoolHandler.sol | 8 +- .../base/handlers/ReservePoolHandler.sol | 6 +- .../base/handlers/unbounded/BaseHandler.sol | 2 +- .../unbounded/UnboundedBasicPoolHandler.sol | 10 +- .../UnboundedLiquidationPoolHandler.sol | 4 +- .../unbounded/UnboundedReservePoolHandler.sol | 6 +- .../RegressionTestReservesERC20Pool.t.sol | 14 +- tests/forge/unit/Auctions.t.sol | 32 +- .../forge/unit/ERC20Pool/ERC20DSTestPlus.sol | 16 +- .../unit/ERC20Pool/ERC20PoolCollateral.t.sol | 18 +- .../unit/ERC20Pool/ERC20PoolFactory.t.sol | 2 +- .../unit/ERC20Pool/ERC20PoolInfoUtils.t.sol | 12 +- .../ERC20PoolLiquidationsArbTake.t.sol | 8 +- .../ERC20PoolLiquidationsDepositTake.t.sol | 2 +- ...ERC20PoolLiquidationsKickWithDeposit.t.sol | 32 +- .../ERC20PoolLiquidationsScaled.t.sol | 19 +- .../ERC20PoolLiquidationsSettle.t.sol | 6 +- .../unit/ERC20Pool/ERC20PoolPrecision.t.sol | 6 +- .../ERC20Pool/ERC20PoolPurchaseQuote.t.sol | 2 +- .../unit/ERC20Pool/ERC20PoolQuoteToken.t.sol | 4 +- .../ERC20Pool/ERC20PoolReserveAuction.t.sol | 2 +- .../unit/ERC20Pool/ERC20PoolTransferLPs.t.sol | 120 +- .../unit/ERC721Pool/ERC721DSTestPlus.sol | 8 +- .../ERC721Pool/ERC721PoolCollateral.t.sol | 16 +- .../ERC721PoolLiquidationsDepositTake.t.sol | 2 +- .../ERC721Pool/ERC721PoolPurchaseQuote.t.sol | 2 +- .../ERC721Pool/ERC721PoolReserveAuction.t.sol | 10 +- tests/forge/unit/PositionManager.t.sol | 284 +-- .../forge/unit/Rewards/RewardsDSTestPlus.sol | 16 +- tests/forge/unit/Rewards/RewardsManager.t.sol | 8 +- tests/forge/utils/DSTestPlus.sol | 18 +- 78 files changed, 2977 insertions(+), 2696 deletions(-) create mode 100644 src/interfaces/pool/commons/IPoolKickerActions.sol create mode 100644 src/interfaces/pool/commons/IPoolLPActions.sol delete mode 100644 src/interfaces/pool/commons/IPoolLiquidationActions.sol delete mode 100644 src/interfaces/pool/commons/IPoolReserveAuctionActions.sol create mode 100644 src/interfaces/pool/commons/IPoolSettlerActions.sol create mode 100644 src/interfaces/pool/commons/IPoolTakerActions.sol delete mode 100644 src/libraries/external/Auctions.sol create mode 100644 src/libraries/external/KickerActions.sol create mode 100644 src/libraries/external/LPActions.sol create mode 100644 src/libraries/external/SettlerActions.sol create mode 100644 src/libraries/external/TakerActions.sol diff --git a/docs/Functions.md b/docs/Functions.md index a42d1d717..3efa321a7 100644 --- a/docs/Functions.md +++ b/docs/Functions.md @@ -103,7 +103,7 @@ reverts on: - deposits locked RemoveDepositLockedByAuctionDebt() - LenderActions.removeQuoteToken(): - - no LPs NoClaim() + - no LP NoClaim() - LUP lower than HTP LUPBelowHTP() emit events: - LenderActions.removeQuoteToken(): @@ -112,24 +112,24 @@ - PoolCommons.updateInterestRate(): - UpdateInterestRate -### transferLPs +### transferLP external libraries call: - - LenderActions.transferLPs() + - LenderActions.transferLP() write state: - - LenderActions.transferLPs(): + - LenderActions.transferLP(): - delete allowance mapping - increment new lender.lps accumulator and lender.depositTime state - delete old lender from bucket -> lender mapping reverts on: - - LenderActions.transferLPs(): + - LenderActions.transferLP(): - invalid index InvalidIndex() - no allowance NoAllowance() emit events: - - LenderActions.transferLPs(): - - TransferLPs + - LenderActions.transferLP(): + - TransferLP ### kick external libraries call: @@ -232,12 +232,12 @@ emit events: - BondWithdrawn -### startClaimableReserveAuction +### kickReserveAuction external libraries call: - - Auctions.startClaimableReserveAuction() + - Auctions.kickReserveAuction() write state: - - Auctions.startClaimableReserveAuction(): + - Auctions.kickReserveAuction(): - update reserveAuction.unclaimed accumulator - update reserveAuction.kicked timestamp state - increment latestBurnEpoch counter @@ -245,12 +245,12 @@ reverts on: - 2 weeks not passed ReserveAuctionTooSoon() - - Auctions.startClaimableReserveAuction(): + - Auctions.kickReserveAuction(): - no reserves to claim NoReserves() emit events: - - Auctions.startClaimableReserveAuction(): - - ReserveAuction + - Auctions.kickReserveAuction(): + - KickReserveAuction ### takeReserves @@ -488,7 +488,7 @@ - update values array state - Buckets.addCollateral(): - increment bucket.collateral and bucket.lps accumulator - - addLenderLPs(): + - addLenderLP(): - increment lender.lps accumulator and lender.depositTime state - Auctions._settleAuction(): - _removeAuction(): @@ -594,7 +594,7 @@ - _prepareTake(): - update liquidation.alreadyTaken state - _rewardBucketTake(): - - Buckets.addLenderLPs: + - Buckets.addLenderLP: - increment taker lender.lps accumulator and lender.depositTime state - increment kicker lender.lps accumulator and lender.depositTime state - update liquidation bond size accumulator @@ -659,7 +659,7 @@ - LenderActions.addCollateral(): - Buckets.addCollateral(): - increment bucket.collateral and bucket.lps accumulator - - addLenderLPs(): + - addLenderLP(): - increment lender.lps accumulator and lender.depositTime state - _updateInterestState(): - PoolCommons.updateInterestRate(): @@ -700,7 +700,7 @@ reverts on: - LenderActions.removeCollateral(): - not enough collateral InsufficientCollateral() - - insufficient LPs InsufficientLPs() + - insufficient LP InsufficientLP() emit events: - RemoveCollateral diff --git a/scripts/ajna_setup.py b/scripts/ajna_setup.py index 45209ccc4..a88b9f448 100644 --- a/scripts/ajna_setup.py +++ b/scripts/ajna_setup.py @@ -10,8 +10,11 @@ def main(): Deposits.deploy({"from": accounts[0]}) PoolCommons.deploy({"from": accounts[0]}) LenderActions.deploy({"from": accounts[0]}) + LPActions.deploy({"from": accounts[0]}) BorrowerActions.deploy({"from": accounts[0]}) - Auctions.deploy({"from": accounts[0]}) + KickerActions.deploy({"from": accounts[0]}}) + TakerActions.deploy({"from": accounts[0]}}) + SettlerActions.deploy({"from": accounts[0]}}) erc20_pool_factory = ERC20PoolFactory.deploy(ajna_address, {"from": accounts[0]}) erc721_pool_factory = ERC721PoolFactory.deploy(ajna_address, {"from": accounts[0]}) pool_utils = PoolInfoUtils.deploy({"from": accounts[0]}) diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 2bbe9bbf1..eaad51730 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -15,7 +15,9 @@ import { IERC20Taker } from './interfaces/pool/erc20/IERC20Taker.sol'; import { IPoolLenderActions, - IPoolLiquidationActions + IPoolKickerActions, + IPoolTakerActions, + IPoolSettlerActions } from './interfaces/pool/IPool.sol'; import { IERC3156FlashBorrower, @@ -50,12 +52,13 @@ import { Maths } from './libraries/internal/Maths.sol'; import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; import { LenderActions } from './libraries/external/LenderActions.sol'; -import { Auctions } from './libraries/external/Auctions.sol'; +import { SettlerActions } from './libraries/external/SettlerActions.sol'; +import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC20 Pool contract * @notice Entrypoint of ERC20 Pool actions for pool actors: - * - Lenders: add, remove and move quote tokens; transfer LPs + * - Lenders: add, remove and move quote tokens; transfer LP * - Borrowers: draw and repay debt * - Traders: add, remove and move quote tokens; add and remove collateral * - Kickers: kick undercollateralized loans; settle auctions; claim bond rewards @@ -64,7 +67,7 @@ import { Auctions } from './libraries/external/Auctions.sol'; * - Flash borrowers: initiate flash loans on quote tokens and collateral * @dev Contract is FlashloanablePool with flash loan logic. * @dev Contract is base Pool with logic to handle ERC20 collateral. - * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and Auctions libraries. + * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and auction actions libraries. */ contract ERC20Pool is FlashloanablePool, IERC20Pool { using SafeERC20 for IERC20; @@ -286,7 +289,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLPs_); // update pool interest rate state - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move required collateral from sender to pool _transferCollateralFrom(msg.sender, amountToAdd_); @@ -318,7 +321,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit RemoveCollateral(msg.sender, index_, collateralAmount_, lpAmount_); // update pool interest rate state - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move collateral from pool to lender _transferCollateral(msg.sender, collateralAmount_); @@ -329,7 +332,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /*******************************/ /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolSettlerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -341,7 +344,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); - SettleResult memory result = Auctions.settlePoolDebt( + SettleResult memory result = SettlerActions.settlePoolDebt( auctions, buckets, deposits, @@ -372,11 +375,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { poolState.debt -= Maths.wmul(result.t0DebtSettled, poolState.inflator); poolState.t0Debt -= result.t0DebtSettled; poolState.collateral -= result.collateralSettled; - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolTakerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -395,7 +398,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // round requested collateral to an amount which can actually be transferred collateral_ = _roundToScale(collateral_, collateralDust); - TakeResult memory result = Auctions.take( + TakeResult memory result = TakerActions.take( auctions, buckets, deposits, @@ -445,7 +448,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolTakerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -459,7 +462,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { PoolState memory poolState = _accruePoolInterest(); - BucketTakeResult memory result = Auctions.bucketTake( + BucketTakeResult memory result = TakerActions.bucketTake( auctions, buckets, deposits, diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index d8217359f..6afac8a37 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -6,7 +6,9 @@ import { IERC721Token, IPoolErrors, IPoolLenderActions, - IPoolLiquidationActions + IPoolKickerActions, + IPoolTakerActions, + IPoolSettlerActions } from './interfaces/pool/IPool.sol'; import { BucketTakeResult, @@ -38,14 +40,15 @@ import { Maths } from './libraries/internal/Maths.sol'; import { Deposits } from './libraries/internal/Deposits.sol'; import { Loans } from './libraries/internal/Loans.sol'; -import { Auctions } from './libraries/external/Auctions.sol'; import { LenderActions } from './libraries/external/LenderActions.sol'; import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; +import { SettlerActions } from './libraries/external/SettlerActions.sol'; +import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC721 Pool contract * @notice Entrypoint of ERC721 Pool actions for pool actors: - * - Lenders: add, remove and move quote tokens; transfer LPs + * - Lenders: add, remove and move quote tokens; transfer LP * - Borrowers: draw and repay debt * - Traders: add, remove and move quote tokens; add and remove collateral * - Kickers: auction undercollateralized loans; settle auctions; claim bond rewards @@ -54,7 +57,7 @@ import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; * - Flash borrowers: initiate flash loans on ERC20 quote tokens * @dev Contract is FlashloanablePool with flash loan logic. * @dev Contract is base Pool with logic to handle ERC721 collateral. - * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and Auctions libraries. + * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and auction actions libraries. */ contract ERC721Pool is FlashloanablePool, IERC721Pool { @@ -286,7 +289,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit AddCollateralNFT(msg.sender, index_, tokenIdsToAdd_, bucketLPs_); // update pool interest rate state - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move required collateral from sender to pool _transferFromSenderToPool(bucketTokenIds, tokenIdsToAdd_); @@ -323,7 +326,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit MergeOrRemoveCollateralNFT(msg.sender, collateralMerged_, bucketLPs_); // update pool interest rate state - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); if (collateralMerged_ == collateralAmount) { // Total collateral in buckets meets the requested removal amount, noOfNFTsToRemove_ @@ -358,7 +361,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit RemoveCollateral(msg.sender, index_, noOfNFTsToRemove_, lpAmount_); // update pool interest rate state - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); _transferFromPoolToAddress(msg.sender, bucketTokenIds, noOfNFTsToRemove_); } @@ -368,7 +371,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /*******************************/ /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolSettlerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -386,7 +389,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { bucketDepth: maxDepth_ }); - SettleResult memory result = Auctions.settlePoolDebt( + SettleResult memory result = SettlerActions.settlePoolDebt( auctions, buckets, deposits, @@ -415,11 +418,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { poolState.debt -= Maths.wmul(result.t0DebtSettled, poolState.inflator); poolState.t0Debt -= result.t0DebtSettled; poolState.collateral -= result.collateralSettled; - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolTakerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -433,7 +436,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); - TakeResult memory result = Auctions.take( + TakeResult memory result = TakerActions.take( auctions, buckets, deposits, @@ -497,7 +500,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolTakerActions * @dev write state: * - decrement poolBalances.t0Debt accumulator * - decrement poolBalances.t0DebtInAuction accumulator @@ -511,7 +514,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); - BucketTakeResult memory result = Auctions.bucketTake( + BucketTakeResult memory result = TakerActions.bucketTake( auctions, buckets, deposits, diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index 3448dce01..96f0f80ec 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -5,13 +5,14 @@ pragma solidity 0.8.14; import { IPool, IERC20Token } from './interfaces/pool/IPool.sol'; import { + _auctionPrice, _claimableReserves, _borrowFeeRate, _depositFeeRate, _indexOf, _isCollateralized, - _lpsToCollateral, - _lpsToQuoteToken, + _lpToCollateral, + _lpToQuoteToken, _minDebtAmount, _priceAt, _reserveAuctionPrice, @@ -22,7 +23,6 @@ import { import { Buckets } from './libraries/internal/Buckets.sol'; import { Maths } from './libraries/internal/Maths.sol'; -import { Auctions } from './libraries/external/Auctions.sol'; import { PoolCommons } from './libraries/external/PoolCommons.sol'; /** @@ -64,7 +64,7 @@ contract PoolInfoUtils { uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); - price_ = Auctions._auctionPrice(kickMomp, neutralPrice_, kickTime_); + price_ = _auctionPrice(kickMomp, neutralPrice_, kickTime_); } } @@ -392,19 +392,19 @@ contract PoolInfoUtils { } /** - * @notice Calculate the amount of quote tokens in bucket for a given amount of LPs. - * @param lps_ The number of LPs to calculate amounts for. + * @notice Calculate the amount of quote tokens in bucket for a given amount of LP. + * @param lps_ The number of LP to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. - * @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given LPs, WAD units. + * @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given LP, WAD units. */ - function lpsToQuoteTokens( + function lpToQuoteTokens( address ajnaPool_, uint256 lps_, uint256 index_ ) external view returns (uint256 quoteAmount_) { IPool pool = IPool(ajnaPool_); (uint256 bucketLPs_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - quoteAmount_ = _lpsToQuoteToken( + quoteAmount_ = _lpToQuoteToken( bucketLPs_, bucketCollateral, bucketDeposit, @@ -415,19 +415,19 @@ contract PoolInfoUtils { } /** - * @notice Calculate the amount of collateral tokens in bucket for a given amount of LPs. - * @param lps_ The number of LPs to calculate amounts for. + * @notice Calculate the amount of collateral tokens in bucket for a given amount of LP. + * @param lps_ The number of LP to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. - * @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given LPs, WAD units. + * @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given LP, WAD units. */ - function lpsToCollateral( + function lpToCollateral( address ajnaPool_, uint256 lps_, uint256 index_ ) external view returns (uint256 collateralAmount_) { IPool pool = IPool(ajnaPool_); (uint256 bucketLPs_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - collateralAmount_ = _lpsToCollateral( + collateralAmount_ = _lpToCollateral( bucketCollateral, bucketLPs_, bucketDeposit, diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 30b8bedfa..19f07702f 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -22,7 +22,7 @@ import { ERC721PoolFactory } from './ERC721PoolFactory.sol'; import { PermitERC721 } from './base/PermitERC721.sol'; import { - _lpsToQuoteToken, + _lpToQuoteToken, _priceAt } from './libraries/helpers/PoolHelper.sol'; import { tokenSymbol } from './libraries/helpers/SafeTokenNamer.sol'; @@ -68,14 +68,14 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /*************************/ struct MoveLiquidityLocalVars { - uint256 bucketLPs; // [WAD] amount of LPs in from bucket + uint256 bucketLPs; // [WAD] amount of LP in from bucket uint256 bucketCollateral; // [WAD] amount of collateral in from bucket uint256 bankruptcyTime; // from bucket bankruptcy time uint256 bucketDeposit; // [WAD] from bucket deposit uint256 depositTime; // lender deposit time in from bucekt uint256 maxQuote; // [WAD] max amount that can be moved from bucket - uint256 lpbAmountFrom; // [WAD] the LPs redeemed from bucket - uint256 lpbAmountTo; // [WAD] the LPs awarded in to bucket + uint256 lpbAmountFrom; // [WAD] the LP redeemed from bucket + uint256 lpbAmountTo; // [WAD] the LP awarded in to bucket } /*****************/ @@ -145,7 +145,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R * @inheritdoc IPositionManagerOwnerActions * @dev External calls to Pool contract: * - lenderInfo(): get lender position in bucket - * - transferLPs(): transfer LPs ownership to PositionManager contracts + * - transferLP(): transfer LP ownership to PositionManager contracts * @dev write state: * - positionIndexes: add bucket index * - positions: update tokenId => bucket id position @@ -180,12 +180,12 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R if (position.depositTime != 0) { // check that bucket didn't go bankrupt after prior memorialization if (_bucketBankruptAfterDeposit(pool, index, position.depositTime)) { - // if bucket did go bankrupt, zero out the LPs tracked by position manager + // if bucket did go bankrupt, zero out the LP tracked by position manager position.lps = 0; } } - // update token position LPs + // update token position LP position.lps += lpBalance; // set token's position deposit time to the original lender's deposit time position.depositTime = depositTime; @@ -196,8 +196,8 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R unchecked { ++i; } } - // update pool lps accounting and transfer ownership of lps to PositionManager contract - pool.transferLPs(owner, address(this), params_.indexes); + // update pool LP accounting and transfer ownership of LP to PositionManager contract + pool.transferLP(owner, address(this), params_.indexes); emit MemorializePosition(owner, params_.tokenId, params_.indexes); } @@ -271,8 +271,8 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R // check that bucket hasn't gone bankrupt since memorialization if (vars.depositTime <= vars.bankruptcyTime) revert BucketBankrupt(); - // calculate the max amount of quote tokens that can be moved, given the tracked LPs - vars.maxQuote = _lpsToQuoteToken( + // calculate the max amount of quote tokens that can be moved, given the tracked LP + vars.maxQuote = _lpToQuoteToken( vars.bucketLPs, vars.bucketCollateral, vars.bucketDeposit, @@ -303,7 +303,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R Position storage toPosition = positions[params_.tokenId][params_.toIndex]; - // update position LPs state + // update position LP state fromPosition.lps -= vars.lpbAmountFrom; toPosition.lps += vars.lpbAmountTo; // update position deposit time to the from bucket deposit time @@ -323,7 +323,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R * @inheritdoc IPositionManagerOwnerActions * @dev External calls to Pool contract: * - increaseLPAllowance(): approve ownership for transfer - * - transferLPs(): transfer LPs ownership from PositionManager contract + * - transferLP(): transfer LP ownership from PositionManager contract * @dev write state: * - positionIndexes: remove from bucket index * - positions: delete bucket position @@ -363,7 +363,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R lpAmounts[i] = position.lps; - // remove LPs tracked by position manager at bucket index + // remove LP tracked by position manager at bucket index delete positions[params_.tokenId][index]; unchecked { ++i; } @@ -371,10 +371,10 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R address owner = ownerOf(params_.tokenId); - // approve owner to take over the LPs ownership (required for transferLPs pool call) - pool.increaseLPsAllowance(owner, params_.indexes, lpAmounts); + // approve owner to take over the LP ownership (required for transferLP pool call) + pool.increaseLPAllowance(owner, params_.indexes, lpAmounts); // update pool lps accounting and transfer ownership of lps from PositionManager contract - pool.transferLPs(address(this), owner, params_.indexes); + pool.transferLP(address(this), owner, params_.indexes); emit RedeemPosition(owner, params_.tokenId, params_.indexes); } @@ -434,7 +434,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /**********************/ /// @inheritdoc IPositionManagerDerivedState - function getLPs( + function getLP( uint256 tokenId_, uint256 index_ ) external override view returns (uint256) { diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 728580169..aca8dad8c 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -170,7 +170,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { // update to bucket state BucketState storage toBucket = stakeInfo.snapshot[toIndex]; - toBucket.lpsAtStakeTime = uint128(positionManager.getLPs(tokenId_, toIndex)); + toBucket.lpsAtStakeTime = uint128(positionManager.getLP(tokenId_, toIndex)); toBucket.rateAtStakeTime = uint128(IPool(ajnaPool).bucketExchangeRate(toIndex)); delete stakeInfo.snapshot[fromIndex]; @@ -227,7 +227,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { BucketState storage bucketState = stakeInfo.snapshot[bucketId]; // record the number of lps in bucket at the time of staking - bucketState.lpsAtStakeTime = uint128(positionManager.getLPs( + bucketState.lpsAtStakeTime = uint128(positionManager.getLP( tokenId_, bucketId )); @@ -470,11 +470,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } /** - * @notice Calculate the amount of interest that has accrued to a lender in a bucket based upon their LPs. + * @notice Calculate the amount of interest that has accrued to a lender in a bucket based upon their LP. * @param pool_ Address of the pool whose exchange rates are being checked. * @param nextEventEpoch_ The next event epoch to check the exchange rate for. * @param bucketIndex_ Index of the bucket to check the exchange rate for. - * @param bucketLPs Amount of LPs in bucket. + * @param bucketLPs Amount of LP in bucket. * @param exchangeRate_ Exchange rate in current epoch. * @return interestEarned_ The amount of interest accrued. */ diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 63b62f379..d07088c4e 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -12,10 +12,12 @@ import { IPool, IPoolImmutables, IPoolBorrowerActions, + IPoolLPActions, IPoolLenderActions, + IPoolKickerActions, + IPoolTakerActions, + IPoolSettlerActions, IPoolState, - IPoolLiquidationActions, - IPoolReserveAuctionActions, IPoolDerivedState, IERC20Token } from '../interfaces/pool/IPool.sol'; @@ -32,14 +34,14 @@ import { Bucket, BurnEvent, Liquidation -} from '../interfaces/pool/commons/IPoolState.sol'; +} from '../interfaces/pool/commons/IPoolState.sol'; import { KickResult, RemoveQuoteParams, MoveQuoteParams, - AddQuoteParams -} from '../interfaces/pool/commons/IPoolInternals.sol'; -import { StartReserveAuctionParams } from '../interfaces/pool/commons/IPoolReserveAuctionActions.sol'; + AddQuoteParams, + KickReserveAuctionParams +} from '../interfaces/pool/commons/IPoolInternals.sol'; import { _priceAt, @@ -56,9 +58,11 @@ import { Deposits } from '../libraries/internal/Deposits.sol'; import { Loans } from '../libraries/internal/Loans.sol'; import { Maths } from '../libraries/internal/Maths.sol'; -import { Auctions } from '../libraries/external/Auctions.sol'; import { BorrowerActions } from '../libraries/external/BorrowerActions.sol'; import { LenderActions } from '../libraries/external/LenderActions.sol'; +import { LPActions } from '../libraries/external/LPActions.sol'; +import { KickerActions } from '../libraries/external/KickerActions.sol'; +import { TakerActions } from '../libraries/external/TakerActions.sol'; import { PoolCommons } from '../libraries/external/PoolCommons.sol'; /** @@ -235,7 +239,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /// @inheritdoc IPoolLenderActions function updateInterest() external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); - _updateInterestState(poolState, _lup(poolState.debt)); + _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); } /***********************************/ @@ -261,7 +265,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /*****************************/ /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolKickerActions * @dev write state: * - increment poolBalances.t0DebtInAuction and poolBalances.t0Debt accumulators */ @@ -272,7 +276,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { PoolState memory poolState = _accruePoolInterest(); // kick auction - KickResult memory result = Auctions.kick( + KickResult memory result = KickerActions.kick( auctions, deposits, loans, @@ -302,7 +306,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolKickerActions * @dev write state: * - increment poolBalances.t0DebtInAuction and poolBalances.t0Debt accumulators */ @@ -313,7 +317,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { PoolState memory poolState = _accruePoolInterest(); // kick auctions - KickResult memory result = Auctions.kickWithDeposit( + KickResult memory result = KickerActions.kickWithDeposit( auctions, deposits, buckets, @@ -345,7 +349,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } /** - * @inheritdoc IPoolLiquidationActions + * @inheritdoc IPoolKickerActions * @dev write state: * - decrease kicker's claimable accumulator * - decrease auctions totalBondEscrowed accumulator @@ -377,21 +381,21 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /*********************************/ /** - * @inheritdoc IPoolReserveAuctionActions + * @inheritdoc IPoolKickerActions * @dev write state: * - increment latestBurnEpoch counter * - update reserveAuction.latestBurnEventEpoch and burn event timestamp state * @dev reverts on: * - 2 weeks not passed ReserveAuctionTooSoon() * @dev emit events: - * - ReserveAuction + * - KickReserveAuction */ - function startClaimableReserveAuction() external override nonReentrant { + function kickReserveAuction() external override nonReentrant { // start a new claimable reserve auction, passing in relevant parameters such as the current pool size, debt, balance, and inflator value - uint256 kickerAward = Auctions.startClaimableReserveAuction( + uint256 kickerAward = KickerActions.kickReserveAuction( auctions, reserveAuction, - StartReserveAuctionParams({ + KickReserveAuctionParams({ poolSize: Deposits.treeSum(deposits), t0PoolDebt: poolBalances.t0Debt, poolBalance: _getNormalizedPoolQuoteTokenBalance(), @@ -404,7 +408,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } /** - * @inheritdoc IPoolReserveAuctionActions + * @inheritdoc IPoolTakerActions * @dev write state: * - increment reserveAuction.totalAjnaBurned accumulator * - update burn event totalInterest and totalBurned accumulators @@ -413,7 +417,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { uint256 maxAmount_ ) external override nonReentrant returns (uint256 amount_) { uint256 ajnaRequired; - (amount_, ajnaRequired) = Auctions.takeReserves( + (amount_, ajnaRequired) = TakerActions.takeReserves( reserveAuction, maxAmount_ ); @@ -427,17 +431,17 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _transferQuoteToken(msg.sender, amount_); } - /******************************/ - /*** Transfer LPs Functions ***/ - /******************************/ + /*****************************/ + /*** Transfer LP Functions ***/ + /*****************************/ - /// @inheritdoc IPoolLenderActions - function increaseLPsAllowance( + /// @inheritdoc IPoolLPActions + function increaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external override nonReentrant { - LenderActions.increaseLPsAllowance( + LPActions.increaseLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_, @@ -445,13 +449,13 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); } - /// @inheritdoc IPoolLenderActions - function decreaseLPsAllowance( + /// @inheritdoc IPoolLPActions + function decreaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external override nonReentrant { - LenderActions.decreaseLPsAllowance( + LPActions.decreaseLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_, @@ -459,49 +463,49 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); } - /// @inheritdoc IPoolLenderActions - function revokeLPsAllowance( + /// @inheritdoc IPoolLPActions + function revokeLPAllowance( address spender_, uint256[] calldata indexes_ ) external override nonReentrant { - LenderActions.revokeLPsAllowance( + LPActions.revokeLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_ ); } - /// @inheritdoc IPoolLenderActions - function approveLPsTransferors( + /// @inheritdoc IPoolLPActions + function approveLPTransferors( address[] calldata transferors_ ) external override { - LenderActions.approveLPsTransferors( + LPActions.approveLPTransferors( approvedTransferors[msg.sender], transferors_ ); } /** - * @inheritdoc IPoolLenderActions + * @inheritdoc IPoolLPActions * @dev write state: * - approvedTransferors mapping */ - function revokeLPsTransferors( + function revokeLPTransferors( address[] calldata transferors_ ) external override { - LenderActions.revokeLPsTransferors( + LPActions.revokeLPTransferors( approvedTransferors[msg.sender], transferors_ ); } - /// @inheritdoc IPoolLenderActions - function transferLPs( + /// @inheritdoc IPoolLPActions + function transferLP( address owner_, address newOwner_, uint256[] calldata indexes_ ) external override nonReentrant { - LenderActions.transferLPs( + LPActions.transferLP( buckets, _lpAllowances, approvedTransferors, @@ -641,10 +645,6 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { return IERC20(_getArgAddress(QUOTE_ADDRESS)).balanceOf(address(this)) * _getArgUint256(QUOTE_SCALE); } - function _lup(uint256 debt_) internal view returns (uint256) { - return _priceAt(Deposits.findIndexOfSum(deposits, debt_)); - } - /*******************************/ /*** External View Functions ***/ /*******************************/ diff --git a/src/interfaces/pool/IPool.sol b/src/interfaces/pool/IPool.sol index 35b91fa08..63ad3b669 100644 --- a/src/interfaces/pool/IPool.sol +++ b/src/interfaces/pool/IPool.sol @@ -2,25 +2,30 @@ pragma solidity 0.8.14; -import { IPoolBorrowerActions } from './commons/IPoolBorrowerActions.sol'; -import { IPoolLenderActions } from './commons/IPoolLenderActions.sol'; -import { IPoolLiquidationActions } from './commons/IPoolLiquidationActions.sol'; -import { IPoolReserveAuctionActions } from './commons/IPoolReserveAuctionActions.sol'; -import { IPoolImmutables } from './commons/IPoolImmutables.sol'; -import { IPoolState } from './commons/IPoolState.sol'; -import { IPoolDerivedState } from './commons/IPoolDerivedState.sol'; -import { IPoolEvents } from './commons/IPoolEvents.sol'; -import { IPoolErrors } from './commons/IPoolErrors.sol'; -import { IERC3156FlashLender } from './IERC3156FlashLender.sol'; +import { IPoolBorrowerActions } from './commons/IPoolBorrowerActions.sol'; +import { IPoolLPActions } from './commons/IPoolLPActions.sol'; +import { IPoolLenderActions } from './commons/IPoolLenderActions.sol'; +import { IPoolKickerActions } from './commons/IPoolKickerActions.sol'; +import { IPoolTakerActions } from './commons/IPoolTakerActions.sol'; +import { IPoolSettlerActions } from './commons/IPoolSettlerActions.sol'; + +import { IPoolImmutables } from './commons/IPoolImmutables.sol'; +import { IPoolState } from './commons/IPoolState.sol'; +import { IPoolDerivedState } from './commons/IPoolDerivedState.sol'; +import { IPoolEvents } from './commons/IPoolEvents.sol'; +import { IPoolErrors } from './commons/IPoolErrors.sol'; +import { IERC3156FlashLender } from './IERC3156FlashLender.sol'; /** * @title Base Pool */ interface IPool is IPoolBorrowerActions, + IPoolLPActions, IPoolLenderActions, - IPoolLiquidationActions, - IPoolReserveAuctionActions, + IPoolKickerActions, + IPoolTakerActions, + IPoolSettlerActions, IPoolImmutables, IPoolState, IPoolDerivedState, diff --git a/src/interfaces/pool/commons/IPoolErrors.sol b/src/interfaces/pool/commons/IPoolErrors.sol index 00418787c..3a9351f3b 100644 --- a/src/interfaces/pool/commons/IPoolErrors.sol +++ b/src/interfaces/pool/commons/IPoolErrors.sol @@ -11,7 +11,7 @@ interface IPoolErrors { /**************************/ /** - * @notice LPs allowance is already set by the owner. + * @notice LP allowance is already set by the owner. */ error AllowanceAlreadySet(); @@ -98,9 +98,9 @@ interface IPoolErrors { /** * @notice Lender is attempting to move or remove more collateral they have claim to in the bucket. * @notice Lender is attempting to remove more collateral they have claim to in the bucket. - * @notice Lender must have enough LPs to claim the desired amount of quote from the bucket. + * @notice Lender must have enough LP to claim the desired amount of quote from the bucket. */ - error InsufficientLPs(); + error InsufficientLP(); /** * @notice Bucket must have more quote available in the bucket than the lender is attempting to claim. @@ -108,12 +108,12 @@ interface IPoolErrors { error InsufficientLiquidity(); /** - * @notice When increasing / decreasing LPs allowances indexes and amounts arrays parameters should have same length. + * @notice When increasing / decreasing LP allowances indexes and amounts arrays parameters should have same length. */ error InvalidAllowancesInput(); /** - * @notice When transferring LPs between indices, the new index must be a valid index. + * @notice When transferring LP between indices, the new index must be a valid index. */ error InvalidIndex(); @@ -144,7 +144,7 @@ interface IPoolErrors { error MoveToSameIndex(); /** - * @notice Owner of the LPs must have approved the new owner prior to transfer. + * @notice Owner of the LP must have approved the new owner prior to transfer. */ error NoAllowance(); @@ -205,12 +205,12 @@ interface IPoolErrors { error TransactionExpired(); /** - * @notice The address that transfer LPs is not approved by the LPs receiving address. + * @notice The address that transfer LP is not approved by the LP receiving address. */ error TransferorNotApproved(); /** - * @notice Owner of the LPs attemps to transfer LPs to same address. + * @notice Owner of the LP attemps to transfer LP to same address. */ error TransferToSameOwner(); diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index 7f3c0e000..adcaa6321 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -145,7 +145,7 @@ interface IPoolEvents { ); /** - * @notice Emitted when LPs are awarded to a taker or kicker in a bucket take. + * @notice Emitted when LP are awarded to a taker or kicker in a bucket take. * @param taker Actor who invoked the bucket take. * @param kicker Actor who started the auction. * @param lpAwardedTaker Amount of LP awarded to the taker. @@ -200,8 +200,8 @@ interface IPoolEvents { * @notice Emitted when NFT auction is completed. * @param borrower Address of borrower that exits auction. * @param collateral Borrower's remaining collateral when auction completed. - * @param lps Amount of LPs given to the borrower to compensate fractional collateral (if any). - * @param index Index of the bucket with LPs to compensate fractional collateral. + * @param lps Amount of LP given to the borrower to compensate fractional collateral (if any). + * @param index Index of the bucket with LP to compensate fractional collateral. */ event AuctionNFTSettle( address indexed borrower, @@ -211,7 +211,19 @@ interface IPoolEvents { ); /** - * @notice Emitted when a Claimaible Reserve Auction is started or taken. + * @notice Emitted when a Claimaible Reserve Auction is started. + * @return claimableReservesRemaining Amount of claimable reserves which has not yet been taken. + * @return auctionPrice Current price at which 1 quote token may be purchased, denominated in Ajna. + * @return currentBurnEpoch Current burn epoch. + */ + event KickReserveAuction( + uint256 claimableReservesRemaining, + uint256 auctionPrice, + uint256 currentBurnEpoch + ); + + /** + * @notice Emitted when a Claimaible Reserve Auction is taken. * @return claimableReservesRemaining Amount of claimable reserves which has not yet been taken. * @return auctionPrice Current price at which 1 quote token may be purchased, denominated in Ajna. * @return currentBurnEpoch Current burn epoch. @@ -222,18 +234,18 @@ interface IPoolEvents { uint256 currentBurnEpoch ); - /***************************/ - /*** LPs transfer events ***/ - /***************************/ + /**************************/ + /*** LP transfer events ***/ + /**************************/ /** - * @notice Emitted when owner increase the LPs allowance of a spender at specified indexes with specified amounts. - * @param owner LPs owner. - * @param spender Address approved to transfer LPs. - * @param indexes Bucket indexes of LPs approved. + * @notice Emitted when owner increase the LP allowance of a spender at specified indexes with specified amounts. + * @param owner LP owner. + * @param spender Address approved to transfer LP. + * @param indexes Bucket indexes of LP approved. * @param amounts LP amounts added (ordered by indexes). */ - event IncreaseLPsAllowance( + event IncreaseLPAllowance( address indexed owner, address indexed spender, uint256[] indexes, @@ -241,13 +253,13 @@ interface IPoolEvents { ); /** - * @notice Emitted when owner decrease the LPs allowance of a spender at specified indexes with specified amounts. - * @param owner LPs owner. - * @param spender Address approved to transfer LPs. - * @param indexes Bucket indexes of LPs approved. + * @notice Emitted when owner decrease the LP allowance of a spender at specified indexes with specified amounts. + * @param owner LP owner. + * @param spender Address approved to transfer LP. + * @param indexes Bucket indexes of LP approved. * @param amounts LP amounts removed (ordered by indexes). */ - event DecreaseLPsAllowance( + event DecreaseLPAllowance( address indexed owner, address indexed spender, uint256[] indexes, @@ -255,46 +267,46 @@ interface IPoolEvents { ); /** - * @notice Emitted when lender removes the allowance of a spender for their LPs. - * @param owner LPs owner. + * @notice Emitted when lender removes the allowance of a spender for their LP. + * @param owner LP owner. * @param spender Address that is having it's allowance revoked. * @param indexes List of bucket index to remove the allowance from. */ - event RevokeLPsAllowance( + event RevokeLPAllowance( address indexed owner, address indexed spender, uint256[] indexes ); /** - * @notice Emitted when lender whitelists addresses to accept LPs from. - * @param lender Recipient that approves new owner for LPs. - * @param transferors List of addresses that can transfer LPs to lender. + * @notice Emitted when lender whitelists addresses to accept LP from. + * @param lender Recipient that approves new owner for LP. + * @param transferors List of addresses that can transfer LP to lender. */ - event ApproveLPsTransferors( + event ApproveLPTransferors( address indexed lender, address[] transferors ); /** - * @notice Emitted when lender removes addresses from the LPs transferors whitelist. - * @param lender Recipient that approves new owner for LPs. - * @param transferors List of addresses that won't be able to transfer LPs to lender anymore. + * @notice Emitted when lender removes addresses from the LP transferors whitelist. + * @param lender Recipient that approves new owner for LP. + * @param transferors List of addresses that won't be able to transfer LP to lender anymore. */ - event RevokeLPsTransferors( + event RevokeLPTransferors( address indexed lender, address[] transferors ); /** - * @notice Emitted when a lender transfers their LPs to a different address. + * @notice Emitted when a lender transfers their LP to a different address. * @dev Used by PositionManager.memorializePositions(). * @param owner The original owner address of the position. * @param newOwner The new owner address of the position. - * @param indexes Array of price bucket indexes at which LPs were transferred. - * @param lps Amount of LPs transferred. + * @param indexes Array of price bucket indexes at which LP were transferred. + * @param lps Amount of LP transferred. */ - event TransferLPs( + event TransferLP( address owner, address newOwner, uint256[] indexes, @@ -306,7 +318,7 @@ interface IPoolEvents { /**************************/ /** - * @notice Emitted when LPs are forfeited as a result of the bucket losing all assets. + * @notice Emitted when LP are forfeited as a result of the bucket losing all assets. * @param index The index of the bucket. * @param lpForfeited Amount of LP forfeited by lenders. */ diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 0c7b41266..002ca9f2e 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -12,7 +12,7 @@ pragma solidity 0.8.14; struct BucketTakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken - uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take uint256 poolDebt; // [WAD] current pool debt @@ -52,7 +52,7 @@ struct SettleResult { struct TakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken - uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral @@ -68,6 +68,13 @@ struct TakeResult { uint256 collateralPostAction; // [WAD] The amount of borrower collateral after take } +struct KickReserveAuctionParams { + uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) + uint256 t0PoolDebt; // [WAD] current t0 pool debt + uint256 poolBalance; // [WAD] pool quote token balance + uint256 inflator; // [WAD] pool current inflator +} + /******************************************/ /*** Liquidity Management Param Structs ***/ /******************************************/ diff --git a/src/interfaces/pool/commons/IPoolKickerActions.sol b/src/interfaces/pool/commons/IPoolKickerActions.sol new file mode 100644 index 000000000..2b3e117a5 --- /dev/null +++ b/src/interfaces/pool/commons/IPoolKickerActions.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.14; + +/** + * @title Pool Kicker Actions + */ +interface IPoolKickerActions { + + /********************/ + /*** Liquidations ***/ + /********************/ + + /** + * @notice Called by actors to initiate a liquidation. + * @param borrower Identifies the loan to liquidate. + * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. + */ + function kick( + address borrower, + uint256 npLimitIndex + ) external; + + /** + * @notice Called by lenders to liquidate the top loan using their deposits. + * @param index The deposit index to use for kicking the top loan. + * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. + */ + function kickWithDeposit( + uint256 index, + uint256 npLimitIndex + ) external; + + /** + * @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions). + * @param recipient Address to receive claimed bonds amount. + * @param maxAmount The max amount to withdraw from auction bonds. Constrained by claimable amounts and liquidity + */ + function withdrawBonds( + address recipient, + uint256 maxAmount + ) external; + + /***********************/ + /*** Reserve Auction ***/ + /***********************/ + + /** + * @notice Called by actor to start a Claimable Reserve Auction (CRA). + */ + function kickReserveAuction() external; +} \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolLPActions.sol b/src/interfaces/pool/commons/IPoolLPActions.sol new file mode 100644 index 000000000..a50f5d8e1 --- /dev/null +++ b/src/interfaces/pool/commons/IPoolLPActions.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.14; + +/** + * @title Pool LP Actions + */ +interface IPoolLPActions { + + /** + * @notice Called by LP owners to approve transfer of an amount of LP to a new owner. + * @dev Intended for use by the PositionManager contract. + * @param spender The new owner of the LP. + * @param indexes Bucket indexes from where LP are transferred. + * @param amounts The amounts of LP approved to transfer. + */ + function increaseLPAllowance( + address spender, + uint256[] calldata indexes, + uint256[] calldata amounts + ) external; + + /** + * @notice Called by LP owners to decrease the amount of LP that can be spend by a new owner. + * @dev Intended for use by the PositionManager contract. + * @param spender The new owner of the LP. + * @param indexes Bucket indexes from where LP are transferred. + * @param amounts The amounts of LP disapproved to transfer. + */ + function decreaseLPAllowance( + address spender, + uint256[] calldata indexes, + uint256[] calldata amounts + ) external; + + /** + * @notice Called by LP owners to decrease the amount of LP that can be spend by a new owner. + * @param spender Address that is having it's allowance revoked. + * @param indexes List of bucket index to remove the allowance from. + */ + function revokeLPAllowance( + address spender, + uint256[] calldata indexes + ) external; + + /** + * @notice Called by LP owners to allow addresses that can transfer LP. + * @dev Intended for use by the PositionManager contract. + * @param transferors Addresses that are allowed to transfer LP to new owner. + */ + function approveLPTransferors( + address[] calldata transferors + ) external; + + /** + * @notice Called by LP owners to revoke addresses that can transfer LP. + * @dev Intended for use by the PositionManager contract. + * @param transferors Addresses that are revoked to transfer LP to new owner. + */ + function revokeLPTransferors( + address[] calldata transferors + ) external; + + /** + * @notice Called by LP owners to transfers their LP to a different address. approveLpOwnership needs to be run first + * @dev Used by PositionManager.memorializePositions(). + * @param owner The original owner address of the position. + * @param newOwner The new owner address of the position. + * @param indexes Array of price buckets index at which LP were moved. + */ + function transferLP( + address owner, + address newOwner, + uint256[] calldata indexes + ) external; +} diff --git a/src/interfaces/pool/commons/IPoolLenderActions.sol b/src/interfaces/pool/commons/IPoolLenderActions.sol index 62be6094f..33fd237f8 100644 --- a/src/interfaces/pool/commons/IPoolLenderActions.sol +++ b/src/interfaces/pool/commons/IPoolLenderActions.sol @@ -16,7 +16,7 @@ interface IPoolLenderActions { * @param amount The amount of quote token to be added by a lender. * @param index The index of the bucket to which the quote tokens will be added. * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LPs changed for the added quote tokens. + * @return lpbChange The amount of LP changed for the added quote tokens. */ function addQuoteToken( uint256 amount, @@ -30,8 +30,8 @@ interface IPoolLenderActions { * @param fromIndex The bucket index from which the quote tokens will be removed. * @param toIndex The bucket index to which the quote tokens will be added. * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbAmountFrom The amount of LPs moved out from bucket. - * @return lpbAmountTo The amount of LPs moved to destination bucket. + * @return lpbAmountFrom The amount of LP moved out from bucket. + * @return lpbAmountTo The amount of LP moved to destination bucket. * @return quoteTokenAmount The amount of quote token moved. */ function moveQuoteToken( @@ -58,7 +58,7 @@ interface IPoolLenderActions { * @param maxAmount The max amount of quote token to be removed by a lender. * @param index The bucket index from which quote tokens will be removed. * @return quoteTokenAmount The amount of quote token removed. - * @return lpAmount The amount of LPs used for removing quote tokens amount. + * @return lpAmount The amount of LP used for removing quote tokens amount. */ function removeQuoteToken( uint256 maxAmount, @@ -74,74 +74,4 @@ interface IPoolLenderActions { */ function updateInterest() external; - /******************************/ - /*** LPs transfer functions ***/ - /******************************/ - - /** - * @notice Called by lenders to approve transfer of an amount of LPs to a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LPs. - * @param indexes Bucket indexes from where LPs are transferred. - * @param amounts The amounts of LPs approved to transfer. - */ - function increaseLPsAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts - ) external; - - /** - * @notice Called by lenders to decrease the amount of LPs that can be spend by a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LPs. - * @param indexes Bucket indexes from where LPs are transferred. - * @param amounts The amounts of LPs disapproved to transfer. - */ - function decreaseLPsAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts - ) external; - - /** - * @notice Called by lenders to decrease the amount of LPs that can be spend by a new owner. - * @param spender Address that is having it's allowance revoked. - * @param indexes List of bucket index to remove the allowance from. - */ - function revokeLPsAllowance( - address spender, - uint256[] calldata indexes - ) external; - - /** - * @notice Called by lenders to allow addresses that can transfer LPs. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are allowed to transfer LPs to lender. - */ - function approveLPsTransferors( - address[] calldata transferors - ) external; - - /** - * @notice Called by lenders to revoke addresses that can transfer LPs. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are revoked to transfer LPs to lender. - */ - function revokeLPsTransferors( - address[] calldata transferors - ) external; - - /** - * @notice Called by lenders to transfers their LPs to a different address. approveLpOwnership needs to be run first - * @dev Used by PositionManager.memorializePositions(). - * @param owner The original owner address of the position. - * @param newOwner The new owner address of the position. - * @param indexes Array of price buckets index at which LPs were moved. - */ - function transferLPs( - address owner, - address newOwner, - uint256[] calldata indexes - ) external; } diff --git a/src/interfaces/pool/commons/IPoolLiquidationActions.sol b/src/interfaces/pool/commons/IPoolLiquidationActions.sol deleted file mode 100644 index f24c85a1c..000000000 --- a/src/interfaces/pool/commons/IPoolLiquidationActions.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.14; - -/** - * @title Pool Liquidation Actions - */ -interface IPoolLiquidationActions { - /** - * @notice Called by actors to use quote token to arb higher-priced deposit off the book. - * @param borrower Identifies the loan to liquidate. - * @param depositTake If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. - * @param index Index of a bucket, likely the HPB, in which collateral will be deposited. - */ - function bucketTake( - address borrower, - bool depositTake, - uint256 index - ) external; - - /** - * @notice Called by actors to settle an amount of debt in a completed liquidation. - * @param borrowerAddress Address of the auctioned borrower. - * @param maxDepth Measured from HPB, maximum number of buckets deep to settle debt. - * @dev maxDepth is used to prevent unbounded iteration clearing large liquidations. - */ - function settle( - address borrowerAddress, - uint256 maxDepth - ) external; - - /** - * @notice Called by actors to initiate a liquidation. - * @param borrower Identifies the loan to liquidate. - * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. - */ - function kick( - address borrower, - uint256 npLimitIndex - ) external; - - /** - * @notice Called by lenders to liquidate the top loan using their deposits. - * @param index The deposit index to use for kicking the top loan. - * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. - */ - function kickWithDeposit( - uint256 index, - uint256 npLimitIndex - ) external; - - /** - * @notice Called by actors to purchase collateral from the auction in exchange for quote token. - * @param borrower Address of the borower take is being called upon. - * @param maxAmount Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). - * @param callee Identifies where collateral should be sent and where quote token should be obtained. - * @param data If provided, take will assume the callee implements IERC*Taker. Take will send collateral to - * callee before passing this data to IERC*Taker.atomicSwapCallback. If not provided, - * the callback function will not be invoked. - */ - function take( - address borrower, - uint256 maxAmount, - address callee, - bytes calldata data - ) external; - - /** - * @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions). - * @param recipient Address to receive claimed bonds amount. - * @param maxAmount The max amount to withdraw from auction bonds. Constrained by claimable amounts and liquidity - */ - function withdrawBonds( - address recipient, - uint256 maxAmount - ) external; -} \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol b/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol deleted file mode 100644 index 6ceed46dc..000000000 --- a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.14; - -/** - * @title Pool Reserve Auction Actions - */ -interface IPoolReserveAuctionActions { - /** - * @notice Called by actor to start a Claimable Reserve Auction (CRA). - */ - function startClaimableReserveAuction() external; - - /** - * @notice Purchases claimable reserves during a CRA using Ajna token. - * @param maxAmount Maximum amount of quote token to purchase at the current auction price. - * @return amount Actual amount of reserves taken. - */ - function takeReserves( - uint256 maxAmount - ) external returns (uint256 amount); -} - -/*********************/ -/*** Param Structs ***/ -/*********************/ - -struct StartReserveAuctionParams { - uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) - uint256 t0PoolDebt; // [WAD] current t0 pool debt - uint256 poolBalance; // [WAD] pool quote token balance - uint256 inflator; // [WAD] pool current inflator -} \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolSettlerActions.sol b/src/interfaces/pool/commons/IPoolSettlerActions.sol new file mode 100644 index 000000000..180ecad7a --- /dev/null +++ b/src/interfaces/pool/commons/IPoolSettlerActions.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.14; + +/** + * @title Pool Settler Actions + */ +interface IPoolSettlerActions { + + /** + * @notice Called by actors to settle an amount of debt in a completed liquidation. + * @param borrowerAddress Address of the auctioned borrower. + * @param maxDepth Measured from HPB, maximum number of buckets deep to settle debt. + * @dev maxDepth is used to prevent unbounded iteration clearing large liquidations. + */ + function settle( + address borrowerAddress, + uint256 maxDepth + ) external; + +} \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index b46c53829..04ebdabfb 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -14,7 +14,7 @@ interface IPoolState { * @return bondFactor The factor used for calculating bond size. * @return bondSize The bond amount in quote token terms. * @return kickTime Time the liquidation was initiated. - * @return kickPrice Highest Price Bucket at time of liquidation. + * @return kickMomp Price where the average loan utilizes deposit, at the time when the loan is liquidated (kicked). * @return neutralPrice Neutral Price of auction. * @return head Address of the head auction. * @return next Address of the next auction in queue. @@ -29,7 +29,7 @@ interface IPoolState { uint256 bondFactor, uint256 bondSize, uint256 kickTime, - uint256 kickPrice, + uint256 kickMomp, uint256 neutralPrice, address head, address next, @@ -67,7 +67,7 @@ interface IPoolState { * @notice Mapping of buckets indexes to {Bucket} structs. * @dev NOTE: Cannot use appended underscore syntax for return params since struct is used. * @param index Bucket index. - * @return lpAccumulator Amount of LPs accumulated in current bucket. + * @return lpAccumulator Amount of LP accumulated in current bucket. * @return availableCollateral Amount of collateral available in current bucket. * @return bankruptcyTime Timestamp when bucket become insolvent, 0 if healthy. * @return bucketDeposit Amount of quote tokens in bucket. @@ -163,7 +163,7 @@ interface IPoolState { * @notice Mapping of buckets indexes and owner addresses to {Lender} structs. * @param index Bucket index. * @param lp Address of the liquidity provider. - * @return lpBalance Amount of LPs owner has in current bucket. + * @return lpBalance Amount of LP owner has in current bucket. * @return lastQuoteDeposit Time the user last deposited quote token. */ function lenderInfo( @@ -181,8 +181,8 @@ interface IPoolState { * @notice Return the LPB allowance a LP owner provided to a spender. * @param index Bucket index. * @param spender Address of the LPB spender. - * @param owner The initial owner of the LPs. - * @return allowance_ Amount of LPs spender can utilize. + * @param owner The initial owner of the LP. + * @return allowance_ Amount of LP spender can utilize. */ function lpAllowance( uint256 index, @@ -265,9 +265,9 @@ interface IPoolState { function totalT0DebtInAuction() external view returns (uint256); /** - * @notice Mapping of addresses that can transfer LPs to a given lender. - * @param lender Lender that receives LPs. - * @param transferor Transferor that transfers LPs. + * @notice Mapping of addresses that can transfer LP to a given lender. + * @param lender Lender that receives LP. + * @param transferor Transferor that transfers LP. * @return True if the transferor is approved by lender. */ function approvedTransferors( diff --git a/src/interfaces/pool/commons/IPoolTakerActions.sol b/src/interfaces/pool/commons/IPoolTakerActions.sol new file mode 100644 index 000000000..627cacabf --- /dev/null +++ b/src/interfaces/pool/commons/IPoolTakerActions.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.14; + +/** + * @title Pool Taker Actions + */ +interface IPoolTakerActions { + + /** + * @notice Called by actors to use quote token to arb higher-priced deposit off the book. + * @param borrower Identifies the loan to liquidate. + * @param depositTake If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. + * @param index Index of a bucket, likely the HPB, in which collateral will be deposited. + */ + function bucketTake( + address borrower, + bool depositTake, + uint256 index + ) external; + + /** + * @notice Called by actors to purchase collateral from the auction in exchange for quote token. + * @param borrower Address of the borower take is being called upon. + * @param maxAmount Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). + * @param callee Identifies where collateral should be sent and where quote token should be obtained. + * @param data If provided, take will assume the callee implements IERC*Taker. Take will send collateral to + * callee before passing this data to IERC*Taker.atomicSwapCallback. If not provided, + * the callback function will not be invoked. + */ + function take( + address borrower, + uint256 maxAmount, + address callee, + bytes calldata data + ) external; + + /***********************/ + /*** Reserve Auction ***/ + /***********************/ + + /** + * @notice Purchases claimable reserves during a CRA using Ajna token. + * @param maxAmount Maximum amount of quote token to purchase at the current auction price. + * @return amount Actual amount of reserves taken. + */ + function takeReserves( + uint256 maxAmount + ) external returns (uint256 amount); + +} \ No newline at end of file diff --git a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol index 7ad2a2cd2..3d43ea4e0 100644 --- a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol +++ b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol @@ -12,7 +12,7 @@ interface IERC20PoolLenderActions { * @param amount Amount of collateral to deposit. * @param index The bucket index to which collateral will be deposited. * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LPs changed for the added collateral. + * @return lpbChange The amount of LP changed for the added collateral. */ function addCollateral( uint256 amount, diff --git a/src/interfaces/pool/erc721/IERC721PoolEvents.sol b/src/interfaces/pool/erc721/IERC721PoolEvents.sol index ac32b1eec..123e86a3c 100644 --- a/src/interfaces/pool/erc721/IERC721PoolEvents.sol +++ b/src/interfaces/pool/erc721/IERC721PoolEvents.sol @@ -25,7 +25,7 @@ interface IERC721PoolEvents { * @notice Emitted when actor adds claimable collateral to a bucket. * @param actor Recipient that added collateral. * @param collateralMerged Amount of collateral merged. - * @param toIndexLps If non-zero, amount of LPs in toIndex when collateral is merged into bucket. If 0, no collateral is merged. + * @param toIndexLps If non-zero, amount of LP in toIndex when collateral is merged into bucket. If 0, no collateral is merged. */ event MergeOrRemoveCollateralNFT( address indexed actor, diff --git a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol index 20886ef61..c9f5119aa 100644 --- a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol +++ b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol @@ -12,7 +12,7 @@ interface IERC721PoolLenderActions { * @param tokenIds Array of collateral to deposit. * @param index The bucket index to which collateral will be deposited. * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LPs changed for the added collateral. + * @return lpbChange The amount of LP changed for the added collateral. */ function addCollateral( uint256[] calldata tokenIds, @@ -26,7 +26,7 @@ interface IERC721PoolLenderActions { * @param toIndex_ The bucket index to which merge collateral into. * @param noOfNFTsToRemove_ Intergral number of NFTs to remove if collateral amount is met noOfNFTsToRemove_, else merge at bucket index, toIndex_. * @return collateralMerged_ Amount of collateral merged into toIndex. - * @return bucketLPs_ If non-zero, amount of LPs in toIndex when collateral is merged into bucket. If 0, no collateral is merged. + * @return bucketLPs_ If non-zero, amount of LP in toIndex when collateral is merged into bucket. If 0, no collateral is merged. */ function mergeOrRemoveCollateral( uint256[] calldata removeAmountAtIndex_, diff --git a/src/interfaces/position/IPositionManagerDerivedState.sol b/src/interfaces/position/IPositionManagerDerivedState.sol index b21a23cf3..7cc95761a 100644 --- a/src/interfaces/position/IPositionManagerDerivedState.sol +++ b/src/interfaces/position/IPositionManagerDerivedState.sol @@ -8,13 +8,13 @@ pragma solidity 0.8.14; interface IPositionManagerDerivedState { /** - * @notice Returns the LPs accrued to a given tokenId, bucket pairing. + * @notice Returns the LP accrued to a given tokenId, bucket pairing. * @dev Nested mappings aren't returned normally as part of the default getter for a mapping. * @param tokenId Unique ID of token. * @param index Index of bucket to check LP balance of. * @return lps Balance of lps in the bucket for this position. */ - function getLPs( + function getLP( uint256 tokenId, uint256 index ) external view returns (uint256 lps); diff --git a/src/interfaces/position/IPositionManagerOwnerActions.sol b/src/interfaces/position/IPositionManagerOwnerActions.sol index 60418951c..7138f811b 100644 --- a/src/interfaces/position/IPositionManagerOwnerActions.sol +++ b/src/interfaces/position/IPositionManagerOwnerActions.sol @@ -20,8 +20,8 @@ interface IPositionManagerOwnerActions { * @notice Called to memorialize existing positions with a given NFT. * @dev The array of buckets is expected to be constructed off chain by scanning events for that lender. * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. - * @dev An additional call is made to the pool to transfer the LPs from their previous owner, to the Position Manager. - * @dev `Pool.increaseLPsAllowance` must be called prior to calling this method in order to allow Position manager contract to transfer LPs to be memorialized. + * @dev An additional call is made to the pool to transfer the LP from their previous owner, to the Position Manager. + * @dev `Pool.increaseLPAllowance` must be called prior to calling this method in order to allow Position manager contract to transfer LP to be memorialized. * @param params Calldata struct supplying inputs required to conduct the memorialization. */ function memorializePositions( @@ -50,8 +50,8 @@ interface IPositionManagerOwnerActions { * @notice Called to reedem existing positions with a given NFT. * @dev The array of buckets is expected to be constructed off chain by scanning events for that lender. * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. - * @dev An additional call is made to the pool to transfer the LPs Position Manager to owner. - * @dev `Pool.approveLPsTransferors` must be called prior to calling this method in order to allow Position manager contract to transfer redeemed LPs. + * @dev An additional call is made to the pool to transfer the LP Position Manager to owner. + * @dev `Pool.approveLPTransferors` must be called prior to calling this method in order to allow Position manager contract to transfer redeemed LP. * @param params Calldata struct supplying inputs required to conduct the redeem. */ function reedemPositions( diff --git a/src/interfaces/position/IPositionManagerState.sol b/src/interfaces/position/IPositionManagerState.sol index 23979cebf..364e00bbb 100644 --- a/src/interfaces/position/IPositionManagerState.sol +++ b/src/interfaces/position/IPositionManagerState.sol @@ -18,6 +18,6 @@ interface IPositionManagerState { } struct Position { - uint256 lps; // [WAD] position LPs + uint256 lps; // [WAD] position LP uint256 depositTime; // deposit time for position } diff --git a/src/interfaces/rewards/IRewardsManagerState.sol b/src/interfaces/rewards/IRewardsManagerState.sol index 0b9d4b8b8..061b8cd64 100644 --- a/src/interfaces/rewards/IRewardsManagerState.sol +++ b/src/interfaces/rewards/IRewardsManagerState.sol @@ -48,7 +48,7 @@ interface IRewardsManagerState { ) external view returns (address, address, uint256); /** - * @notice Retrieve information about recorded LPs and rate values for a given bucket and a given stake, at stake time. + * @notice Retrieve information about recorded LP and rate values for a given bucket and a given stake, at stake time. * @param tokenId ID of the NFT staked in the rewards contract to retrieve information about. * @param bucketId ID of the bucket to retrieve recorded information at stake time. * @return [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking. diff --git a/src/libraries/external/Auctions.sol b/src/libraries/external/Auctions.sol deleted file mode 100644 index 6c42b5f97..000000000 --- a/src/libraries/external/Auctions.sol +++ /dev/null @@ -1,1579 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity 0.8.14; - -import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; - -import { PoolType } from '../../interfaces/pool/IPool.sol'; - -import { - AuctionsState, - Borrower, - Bucket, - BurnEvent, - DepositsState, - Kicker, - Lender, - Liquidation, - LoansState, - PoolState, - ReserveAuctionState -} from '../../interfaces/pool/commons/IPoolState.sol'; -import { - BucketTakeResult, - KickResult, - SettleParams, - SettleResult, - TakeResult -} from '../../interfaces/pool/commons/IPoolInternals.sol'; -import { StartReserveAuctionParams } from '../../interfaces/pool/commons/IPoolReserveAuctionActions.sol'; - -import { - _claimableReserves, - _indexOf, - _isCollateralized, - _priceAt, - _reserveAuctionPrice, - _roundToScale, - MAX_FENWICK_INDEX, - MAX_PRICE, - MIN_PRICE -} from '../helpers/PoolHelper.sol'; -import { - _revertOnMinDebt, - _revertIfPriceDroppedBelowLimit -} from '../helpers/RevertsHelper.sol'; - -import { Buckets } from '../internal/Buckets.sol'; -import { Deposits } from '../internal/Deposits.sol'; -import { Loans } from '../internal/Loans.sol'; -import { Maths } from '../internal/Maths.sol'; - -/** - @title Auctions library - @notice External library containing actions involving auctions within pool: - - Kickers: kick undercollateralized loans; settle auctions; claim bond rewards - - Bidders: take auctioned collateral - - Reserve purchasers: start auctions; take reserves - */ -library Auctions { - - /*******************************/ - /*** Function Params Structs ***/ - /*******************************/ - - struct BucketTakeParams { - address borrower; // borrower address to take from - bool depositTake; // deposit or arb take, used by bucket take - uint256 index; // bucket index, used by bucket take - uint256 inflator; // [WAD] current pool inflator - uint256 collateralScale; // precision of collateral token based on decimals - } - struct TakeParams { - address borrower; // borrower address to take from - uint256 takeCollateral; // [WAD] desired amount to take - uint256 inflator; // [WAD] current pool inflator - uint256 poolType; // pool type (ERC20 or NFT) - uint256 collateralScale; // precision of collateral token based on decimals - } - - /*************************/ - /*** Local Var Structs ***/ - /*************************/ - - struct KickLocalVars { - uint256 borrowerDebt; // [WAD] the accrued debt of kicked borrower - uint256 borrowerCollateral; // [WAD] amount of kicked borrower collateral - uint256 neutralPrice; // [WAD] neutral price recorded in kick action - uint256 noOfLoans; // number of loans and auctions in pool (used to calculate MOMP) - uint256 momp; // [WAD] MOMP of kicked auction - uint256 bondFactor; // [WAD] bond factor of kicked auction - uint256 bondSize; // [WAD] bond size of kicked auction - uint256 t0KickPenalty; // [WAD] t0 debt added as kick penalty - uint256 kickPenalty; // [WAD] current debt added as kick penalty - } - struct KickWithDepositLocalVars { - uint256 amountToDebitFromDeposit; // [WAD] the amount of quote tokens used to kick and debited from lender deposit - uint256 bucketCollateral; // [WAD] amount of collateral in bucket - uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket - uint256 bucketLPs; // [WAD] LPs of the bucket - uint256 bucketPrice; // [WAD] bucket price - uint256 bucketRate; // [WAD] bucket exchange rate - uint256 bucketScale; // [WAD] bucket scales - uint256 bucketUnscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket - uint256 lenderLPs; // [WAD] LPs of lender in bucket - uint256 redeemedLPs; // [WAD] LPs used by kick action - } - struct SettleLocalVars { - uint256 collateralUsed; // [WAD] collateral used to settle debt - uint256 debt; // [WAD] debt to settle - uint256 depositToRemove; // [WAD] deposit used by settle auction - uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket - uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle - uint256 hpbLPs; // [WAD] amount of LPs in HPB bucket - uint256 index; // index of settling bucket - uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral - uint256 price; // [WAD] price of settling bucket - uint256 scaledDeposit; // [WAD] scaled amount of quote tokens in bucket - uint256 scale; // [WAD] scale of settling bucket - uint256 unscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket - } - struct TakeLocalVars { - uint256 auctionPrice; // [WAD] The price of auction. - uint256 bondChange; // [WAD] The change made on the bond size (beeing reward or penalty). - uint256 borrowerDebt; // [WAD] The accrued debt of auctioned borrower. - int256 bpf; // The bond penalty factor. - uint256 bucketPrice; // [WAD] The bucket price. - uint256 bucketScale; // [WAD] The bucket scale. - uint256 collateralAmount; // [WAD] The amount of collateral taken. - uint256 excessQuoteToken; // [WAD] Difference of quote token that borrower receives after take (for fractional NFT only) - uint256 factor; // The take factor, calculated based on bond penalty factor. - bool isRewarded; // True if kicker is rewarded (auction price lower than neutral price), false if penalized (auction price greater than neutral price). - address kicker; // Address of auction kicker. - uint256 quoteTokenAmount; // [WAD] Scaled quantity in Fenwick tree and before 1-bpf factor, paid for collateral - uint256 t0RepayAmount; // [WAD] The amount of debt (quote tokens) that is recovered / repayed by take t0 terms. - uint256 t0BorrowerDebt; // [WAD] Borrower's t0 debt. - uint256 t0DebtPenalty; // [WAD] Borrower's t0 penalty - 7% from current debt if intial take, 0 otherwise. - uint256 unscaledDeposit; // [WAD] Unscaled bucket quantity - uint256 unscaledQuoteTokenAmount; // [WAD] The unscaled token amount that taker should pay for collateral taken. - } - - /**************/ - /*** Events ***/ - /**************/ - - // See `IPoolEvents` for descriptions - event AuctionSettle(address indexed borrower, uint256 collateral); - event AuctionNFTSettle(address indexed borrower, uint256 collateral, uint256 lps, uint256 index); - event BucketTake(address indexed borrower, uint256 index, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); - event BucketTakeLPAwarded(address indexed taker, address indexed kicker, uint256 lpAwardedTaker, uint256 lpAwardedKicker); - event Kick(address indexed borrower, uint256 debt, uint256 collateral, uint256 bond); - event Take(address indexed borrower, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); - event RemoveQuoteToken(address indexed lender, uint256 indexed price, uint256 amount, uint256 lpRedeemed, uint256 lup); - event ReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); - event Settle(address indexed borrower, uint256 settledDebt); - - /**************/ - /*** Errors ***/ - /**************/ - - // See `IPoolErrors` for descriptions - event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); - error AuctionActive(); - error AuctionNotClearable(); - error AuctionPriceGtBucketPrice(); - error BorrowerOk(); - error CollateralRoundingNeededButNotPossible(); - error InsufficientLiquidity(); - error InsufficientCollateral(); - error InvalidAmount(); - error NoAuction(); - error NoReserves(); - error NoReservesAuction(); - error PriceBelowLUP(); - error ReserveAuctionTooSoon(); - error TakeNotPastCooldown(); - - /***************************/ - /*** External Functions ***/ - /***************************/ - - /** - * @notice Settles the debt of the given loan / borrower. - * @dev write state: - * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): - * - update values array state - * - Buckets.addCollateral: - * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLPs: - * - increment lender.lps accumulator and lender.depositTime state - * - update borrower state - * @dev reverts on: - * - loan is not in auction NoAuction() - * - 72 hours didn't pass and auction still has collateral AuctionNotClearable() - * @dev emit events: - * - Settle - * - BucketBankruptcy - * @param params_ Settle params - * @return result_ The result of settle action. - */ - function settlePoolDebt( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - LoansState storage loans_, - ReserveAuctionState storage reserveAuction_, - PoolState calldata poolState_, - SettleParams memory params_ - ) external returns (SettleResult memory result_) { - uint256 kickTime = auctions_.liquidations[params_.borrower].kickTime; - if (kickTime == 0) revert NoAuction(); - - Borrower memory borrower = loans_.borrowers[params_.borrower]; - if ((block.timestamp - kickTime < 72 hours) && (borrower.collateral != 0)) revert AuctionNotClearable(); - - result_.debtPreAction = borrower.t0Debt; - result_.collateralPreAction = borrower.collateral; - result_.t0DebtSettled = borrower.t0Debt; - result_.collateralSettled = borrower.collateral; - - // auction has debt to cover with remaining collateral - while (params_.bucketDepth != 0 && borrower.t0Debt != 0 && borrower.collateral != 0) { - SettleLocalVars memory vars; - - (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); - vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); - vars.unscaledDeposit = vars.hpbUnscaledDeposit; - vars.price = _priceAt(vars.index); - - if (vars.unscaledDeposit != 0) { - vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled - vars.maxSettleableDebt = Maths.floorWmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral - vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); - - // enough deposit in bucket and collateral avail to settle entire debt - if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { - // remove only what's needed to settle the debt - vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); - vars.collateralUsed = Maths.wdiv(vars.debt, vars.price); - - // settle the entire debt - borrower.t0Debt = 0; - } - // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount - else if (vars.maxSettleableDebt >= vars.scaledDeposit) { - vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); - - // subtract from debt the corresponding t0 amount of deposit - borrower.t0Debt -= Maths.floorWdiv(vars.scaledDeposit, poolState_.inflator); - } - // settle constrained by collateral available - else { - vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); - vars.collateralUsed = borrower.collateral; - - borrower.t0Debt -= Maths.floorWdiv(vars.maxSettleableDebt, poolState_.inflator); - } - - // remove settled collateral from loan - borrower.collateral -= vars.collateralUsed; - - Bucket storage hpb = buckets_[vars.index]; - vars.hpbLPs = hpb.lps; - vars.hpbCollateral = hpb.collateral + vars.collateralUsed; - - // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) - vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); - vars.hpbUnscaledDeposit -= vars.unscaledDeposit; - - // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) - Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); - - // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs - if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { - emit BucketBankruptcy(vars.index, vars.hpbLPs); - hpb.lps = 0; - hpb.bankruptcyTime = block.timestamp; - } else { - // add settled collateral into bucket - hpb.collateral = vars.hpbCollateral; - } - - } else { - // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 - Buckets.addCollateral( - buckets_[vars.index], - params_.borrower, - 0, // zero deposit in bucket - borrower.collateral, - vars.price - ); - borrower.collateral = 0; // entire collateral added into bucket - } - - --params_.bucketDepth; - } - - // if there's still debt and no collateral - if (borrower.t0Debt != 0 && borrower.collateral == 0) { - - uint256 assets = Maths.wmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; - uint256 liabilities = Deposits.treeSum(deposits_) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; - uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; - - // settle debt from reserves -- round reserves down however - borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(reserves, poolState_.inflator)); - - // if there's still debt after settling from reserves then start to forgive amount from next HPB - // loop through remaining buckets if there's still debt to settle - while (params_.bucketDepth != 0 && borrower.t0Debt != 0) { - SettleLocalVars memory vars; - - (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); - vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); - vars.depositToRemove = Maths.wmul(vars.scale, vars.unscaledDeposit); - vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - - // enough deposit in bucket to settle entire debt - if (vars.depositToRemove >= vars.debt) { - Deposits.unscaledRemove(deposits_, vars.index, Maths.wdiv(vars.debt, vars.scale)); - borrower.t0Debt = 0; // no remaining debt to settle - - // not enough deposit to settle entire debt, we settle only deposit amount - } else { - borrower.t0Debt -= Maths.floorWdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit - - Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket - Bucket storage hpbBucket = buckets_[vars.index]; - - if (hpbBucket.collateral == 0) { // existing LPs for the bucket shall become unclaimable. - emit BucketBankruptcy(vars.index, hpbBucket.lps); - hpbBucket.lps = 0; - hpbBucket.bankruptcyTime = block.timestamp; - } - } - - --params_.bucketDepth; - } - } - - result_.t0DebtSettled -= borrower.t0Debt; - - emit Settle(params_.borrower, result_.t0DebtSettled); - - if (borrower.t0Debt == 0) { - // settle auction - (borrower.collateral, ) = _settleAuction( - auctions_, - buckets_, - deposits_, - params_.borrower, - borrower.collateral, - poolState_.poolType - ); - } - - result_.debtPostAction = borrower.t0Debt; - result_.collateralRemaining = borrower.collateral; - result_.collateralSettled -= result_.collateralRemaining; - - // update borrower state - loans_.borrowers[params_.borrower] = borrower; - } - - /** - * @notice Called to start borrower liquidation and to update the auctions queue. - * @param poolState_ Current state of the pool. - * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @return kickResult_ The result of the kick action. - */ - function kick( - AuctionsState storage auctions_, - DepositsState storage deposits_, - LoansState storage loans_, - PoolState calldata poolState_, - address borrowerAddress_, - uint256 limitIndex_ - ) external returns ( - KickResult memory - ) { - return _kick( - auctions_, - deposits_, - loans_, - poolState_, - borrowerAddress_, - limitIndex_, - 0 - ); - } - - /** - * @notice Called by lenders to kick loans using their deposits. - * @dev write state: - * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): - * - update values array state - * - decrement lender.lps accumulator - * - decrement bucket.lps accumulator - * @dev emit events: - * - RemoveQuoteToken - * @param poolState_ Current state of the pool. - * @param index_ The deposit index from where lender removes liquidity. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @return kickResult_ The result of the kick action. - */ - function kickWithDeposit( - AuctionsState storage auctions_, - DepositsState storage deposits_, - mapping(uint256 => Bucket) storage buckets_, - LoansState storage loans_, - PoolState calldata poolState_, - uint256 index_, - uint256 limitIndex_ - ) external returns ( - KickResult memory kickResult_ - ) { - Bucket storage bucket = buckets_[index_]; - Lender storage lender = bucket.lenders[msg.sender]; - - KickWithDepositLocalVars memory vars; - - if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLPs = lender.lps; - - vars.bucketLPs = bucket.lps; - vars.bucketCollateral = bucket.collateral; - vars.bucketPrice = _priceAt(index_); - vars.bucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, index_); - vars.bucketScale = Deposits.scale(deposits_, index_); - vars.bucketDeposit = Maths.wmul(vars.bucketUnscaledDeposit, vars.bucketScale); - - // calculate max amount that can be removed (constrained by lender LPs in bucket, bucket deposit and the amount lender wants to remove) - vars.bucketRate = Buckets.getExchangeRate( - vars.bucketCollateral, - vars.bucketLPs, - vars.bucketDeposit, - vars.bucketPrice - ); - - vars.amountToDebitFromDeposit = Maths.wmul(vars.lenderLPs, vars.bucketRate); // calculate amount to remove based on lender LPs in bucket - - if (vars.amountToDebitFromDeposit > vars.bucketDeposit) vars.amountToDebitFromDeposit = vars.bucketDeposit; // cap the amount to remove at bucket deposit - - // revert if no amount that can be removed - if (vars.amountToDebitFromDeposit == 0) revert InsufficientLiquidity(); - - // kick top borrower - kickResult_ = _kick( - auctions_, - deposits_, - loans_, - poolState_, - Loans.getMax(loans_).borrower, - limitIndex_, - vars.amountToDebitFromDeposit - ); - - // amount to remove from deposit covers entire bond amount - if (vars.amountToDebitFromDeposit > kickResult_.amountToCoverBond) { - vars.amountToDebitFromDeposit = kickResult_.amountToCoverBond; // cap amount to remove from deposit at amount to cover bond - - kickResult_.lup = _lup(deposits_, poolState_.debt + vars.amountToDebitFromDeposit); // recalculate the LUP with the amount to cover bond - kickResult_.amountToCoverBond = 0; // entire bond is covered from deposit, no additional amount to be send by lender - } else { - kickResult_.amountToCoverBond -= vars.amountToDebitFromDeposit; // lender should send additional amount to cover bond - } - - // revert if the bucket price used to kick and remove is below new LUP - if (vars.bucketPrice < kickResult_.lup) revert PriceBelowLUP(); - - // remove amount from deposits - if (vars.amountToDebitFromDeposit == vars.bucketDeposit && vars.bucketCollateral == 0) { - // In this case we are redeeming the entire bucket exactly, and need to ensure bucket LPs are set to 0 - vars.redeemedLPs = vars.bucketLPs; - - Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); - - } else { - vars.redeemedLPs = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); - - Deposits.unscaledRemove( - deposits_, - index_, - Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketScale) - ); - } - - // remove bucket LPs coresponding to the amount removed from deposits - lender.lps -= vars.redeemedLPs; - bucket.lps -= vars.redeemedLPs; - - emit RemoveQuoteToken(msg.sender, index_, vars.amountToDebitFromDeposit, vars.redeemedLPs, kickResult_.lup); - } - - /** - * @notice Performs bucket take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). - * @dev reverts on: - * - insufficient collateral InsufficientCollateral() - * @param borrowerAddress_ Borrower address to take. - * @param depositTake_ If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. - * @param index_ Index of a bucket, likely the HPB, in which collateral will be deposited. - * @return result_ BucketTakeResult struct containing details of take. - */ - function bucketTake( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - LoansState storage loans_, - PoolState memory poolState_, - address borrowerAddress_, - bool depositTake_, - uint256 index_, - uint256 collateralScale_ - ) external returns (BucketTakeResult memory result_) { - Borrower memory borrower = loans_.borrowers[borrowerAddress_]; - - if (borrower.collateral == 0) revert InsufficientCollateral(); // revert if borrower's collateral is 0 - - result_.debtPreAction = borrower.t0Debt; - result_.collateralPreAction = borrower.collateral; - - // bucket take auction - TakeLocalVars memory vars = _takeBucket( - auctions_, - buckets_, - deposits_, - borrower, - BucketTakeParams({ - borrower: borrowerAddress_, - inflator: poolState_.inflator, - depositTake: depositTake_, - index: index_, - collateralScale: collateralScale_ - }) - ); - - // update borrower after take - borrower.collateral -= vars.collateralAmount; - borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; - // update pool params after take - poolState_.t0Debt += vars.t0DebtPenalty; - poolState_.t0Debt -= vars.t0RepayAmount; - poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); - - // update loan after take - ( - result_.newLup, - result_.settledAuction, - result_.remainingCollateral, - result_.compensatedCollateral - ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); - - // complete take result struct - result_.debtPostAction = borrower.t0Debt; - result_.collateralPostAction = borrower.collateral; - result_.t0PoolDebt = poolState_.t0Debt; - result_.poolDebt = poolState_.debt; - result_.collateralAmount = vars.collateralAmount; - result_.t0DebtPenalty = vars.t0DebtPenalty; - // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount - result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; - } - - /** - * @notice Performs take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). - * @dev reverts on: - * - insufficient collateral InsufficientCollateral() - * @param borrowerAddress_ Borrower address to take. - * @param collateral_ Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). - * @return result_ TakeResult struct containing details of take. - */ - function take( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - LoansState storage loans_, - PoolState memory poolState_, - address borrowerAddress_, - uint256 collateral_, - uint256 collateralScale_ - ) external returns (TakeResult memory result_) { - // revert if no amount to take - if (collateral_ == 0) revert InvalidAmount(); - - Borrower memory borrower = loans_.borrowers[borrowerAddress_]; - - if ( - (poolState_.poolType == uint8(PoolType.ERC721) && borrower.collateral < 1e18) || // revert in case of NFT take when there isn't a full token to be taken - (poolState_.poolType == uint8(PoolType.ERC20) && borrower.collateral == 0) // revert in case of ERC20 take when no collateral to be taken - ) { - revert InsufficientCollateral(); - } - - result_.debtPreAction = borrower.t0Debt; - result_.collateralPreAction = borrower.collateral; - - // take auction - TakeLocalVars memory vars = _take( - auctions_, - borrower, - TakeParams({ - borrower: borrowerAddress_, - takeCollateral: collateral_, - inflator: poolState_.inflator, - poolType: poolState_.poolType, - collateralScale: collateralScale_ - }) - ); - - // update borrower after take - borrower.collateral -= vars.collateralAmount; - borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; - // update pool params after take - poolState_.t0Debt += vars.t0DebtPenalty; - poolState_.t0Debt -= vars.t0RepayAmount; - poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); - - // update loan after take - ( - result_.newLup, - result_.settledAuction, - result_.remainingCollateral, - result_.compensatedCollateral - ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); - - // complete take result struct - result_.debtPostAction = borrower.t0Debt; - result_.collateralPostAction = borrower.collateral; - result_.t0PoolDebt = poolState_.t0Debt; - result_.poolDebt = poolState_.debt; - result_.collateralAmount = vars.collateralAmount; - result_.t0DebtPenalty = vars.t0DebtPenalty; - result_.quoteTokenAmount = vars.quoteTokenAmount; - result_.excessQuoteToken = vars.excessQuoteToken; - // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount - result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; - } - - /** - * @notice See `IPoolReserveAuctionActions` for descriptions. - * @dev write state: - * - update reserveAuction.unclaimed accumulator - * - update reserveAuction.kicked timestamp state - * @dev reverts on: - * - no reserves to claim NoReserves() - * @dev emit events: - * - ReserveAuction - */ - function startClaimableReserveAuction( - AuctionsState storage auctions_, - ReserveAuctionState storage reserveAuction_, - StartReserveAuctionParams calldata params_ - ) external returns (uint256 kickerAward_) { - // retrieve timestamp of latest burn event and last burn timestamp - uint256 latestBurnEpoch = reserveAuction_.latestBurnEventEpoch; - uint256 lastBurnTimestamp = reserveAuction_.burnEvents[latestBurnEpoch].timestamp; - - // check that at least two weeks have passed since the last reserve auction completed, and that the auction was not kicked within the past 72 hours - if (block.timestamp < lastBurnTimestamp + 2 weeks || block.timestamp - reserveAuction_.kicked <= 72 hours) { - revert ReserveAuctionTooSoon(); - } - - uint256 curUnclaimedAuctionReserve = reserveAuction_.unclaimed; - - uint256 claimable = _claimableReserves( - Maths.wmul(params_.t0PoolDebt, params_.inflator), - params_.poolSize, - auctions_.totalBondEscrowed, - curUnclaimedAuctionReserve, - params_.poolBalance - ); - - kickerAward_ = Maths.wmul(0.01 * 1e18, claimable); - - curUnclaimedAuctionReserve += claimable - kickerAward_; - - if (curUnclaimedAuctionReserve == 0) revert NoReserves(); - - reserveAuction_.unclaimed = curUnclaimedAuctionReserve; - reserveAuction_.kicked = block.timestamp; - - // increment latest burn event epoch and update burn event timestamp - latestBurnEpoch += 1; - - reserveAuction_.latestBurnEventEpoch = latestBurnEpoch; - reserveAuction_.burnEvents[latestBurnEpoch].timestamp = block.timestamp; - - emit ReserveAuction( - curUnclaimedAuctionReserve, - _reserveAuctionPrice(block.timestamp), - latestBurnEpoch - ); - } - - /** - * @notice See `IPoolReserveAuctionActions` for descriptions. - * @dev write state: - * - decrement reserveAuction.unclaimed accumulator - * @dev reverts on: - * - not kicked or 72 hours didn't pass NoReservesAuction() - * @dev emit events: - * - ReserveAuction - */ - function takeReserves( - ReserveAuctionState storage reserveAuction_, - uint256 maxAmount_ - ) external returns (uint256 amount_, uint256 ajnaRequired_) { - // revert if no amount to be taken - if (maxAmount_ == 0) revert InvalidAmount(); - - uint256 kicked = reserveAuction_.kicked; - - if (kicked != 0 && block.timestamp - kicked <= 72 hours) { - uint256 unclaimed = reserveAuction_.unclaimed; - uint256 price = _reserveAuctionPrice(kicked); - - amount_ = Maths.min(unclaimed, maxAmount_); - ajnaRequired_ = Maths.wmul(amount_, price); - - unclaimed -= amount_; - - reserveAuction_.unclaimed = unclaimed; - - uint256 totalBurned = reserveAuction_.totalAjnaBurned + ajnaRequired_; - - // accumulate additional ajna burned - reserveAuction_.totalAjnaBurned = totalBurned; - - uint256 burnEventEpoch = reserveAuction_.latestBurnEventEpoch; - - // record burn event information to enable querying by staking rewards - BurnEvent storage burnEvent = reserveAuction_.burnEvents[burnEventEpoch]; - burnEvent.totalInterest = reserveAuction_.totalInterestEarned; - burnEvent.totalBurned = totalBurned; - - emit ReserveAuction(unclaimed, price, burnEventEpoch); - } else { - revert NoReservesAuction(); - } - } - - /***************************/ - /*** Internal Functions ***/ - /***************************/ - - /** - * @notice Performs auction settle based on pool type, emits settle event and removes auction from auctions queue. - * @dev emit events: - * - AuctionNFTSettle or AuctionSettle - * @param borrowerAddress_ Address of the borrower that exits auction. - * @param borrowerCollateral_ Borrower collateral amount before auction exit (in NFT could be fragmented as result of partial takes). - * @param poolType_ Type of the pool (can be ERC20 or NFT). - * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for ERC20 pool, rounded collateral for NFT pool). - * @return compensatedCollateral_ Amount of collateral compensated (NFT settle only), to be deducted from pool pledged collateral accumulator. 0 for ERC20 pools. - */ - function _settleAuction( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - address borrowerAddress_, - uint256 borrowerCollateral_, - uint256 poolType_ - ) internal returns (uint256 remainingCollateral_, uint256 compensatedCollateral_) { - - if (poolType_ == uint8(PoolType.ERC721)) { - uint256 lps; - uint256 bucketIndex; - - remainingCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // floor collateral of borrower - - // if there's fraction of NFTs remaining then reward difference to borrower as LPs in auction price bucket - if (remainingCollateral_ != borrowerCollateral_) { - - // calculate the amount of collateral that should be compensated with LPs - compensatedCollateral_ = borrowerCollateral_ - remainingCollateral_; - - uint256 auctionPrice = _auctionPrice( - auctions_.liquidations[borrowerAddress_].kickMomp, - auctions_.liquidations[borrowerAddress_].neutralPrice, - auctions_.liquidations[borrowerAddress_].kickTime - ); - - // determine the bucket index to compensate fractional collateral - bucketIndex = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; - - // deposit collateral in bucket and reward LPs to compensate fractional collateral - lps = Buckets.addCollateral( - buckets_[bucketIndex], - borrowerAddress_, - Deposits.valueAt(deposits_, bucketIndex), - compensatedCollateral_, - _priceAt(bucketIndex) - ); - } - - emit AuctionNFTSettle(borrowerAddress_, remainingCollateral_, lps, bucketIndex); - - } else { - remainingCollateral_ = borrowerCollateral_; - - emit AuctionSettle(borrowerAddress_, remainingCollateral_); - } - - _removeAuction(auctions_, borrowerAddress_); - } - - /** - * @notice Called to start borrower liquidation and to update the auctions queue. - * @dev write state: - * - _recordAuction: - * - borrower -> liquidation mapping update - * - increment auctions count accumulator - * - increment auctions.totalBondEscrowed accumulator - * - updates auction queue state - * - _updateKicker: - * - update locked and claimable kicker accumulators - * - Loans.remove: - * - delete borrower from indices => borrower address mapping - * - remove loan from loans array - * @dev emit events: - * - Kick - * @param poolState_ Current state of the pool. - * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @param additionalDebt_ Additional debt to be used when calculating proposed LUP. - * @return kickResult_ The result of the kick action. - */ - function _kick( - AuctionsState storage auctions_, - DepositsState storage deposits_, - LoansState storage loans_, - PoolState calldata poolState_, - address borrowerAddress_, - uint256 limitIndex_, - uint256 additionalDebt_ - ) internal returns ( - KickResult memory kickResult_ - ) { - Borrower storage borrower = loans_.borrowers[borrowerAddress_]; - - kickResult_.debtPreAction = borrower.t0Debt; - kickResult_.collateralPreAction = borrower.collateral; - kickResult_.t0KickedDebt = kickResult_.debtPreAction ; - // add amount to remove to pool debt in order to calculate proposed LUP - kickResult_.lup = _lup(deposits_, poolState_.debt + additionalDebt_); - - KickLocalVars memory vars; - vars.borrowerDebt = Maths.wmul(kickResult_.t0KickedDebt, poolState_.inflator); - vars.borrowerCollateral = kickResult_.collateralPreAction; - - // revert if kick on a collateralized borrower - if (_isCollateralized(vars.borrowerDebt, vars.borrowerCollateral, kickResult_.lup, poolState_.poolType)) { - revert BorrowerOk(); - } - - // calculate auction params - vars.neutralPrice = Maths.wmul(borrower.t0Np, poolState_.inflator); - // check if NP is not less than price at the limit index provided by the kicker - done to prevent frontrunning kick auction call with a large amount of loan - // which will make it harder for kicker to earn a reward and more likely that the kicker is penalized - _revertIfPriceDroppedBelowLimit(vars.neutralPrice, limitIndex_); - - vars.noOfLoans = Loans.noOfLoans(loans_) + auctions_.noOfAuctions; - - vars.momp = _priceAt( - Deposits.findIndexOfSum( - deposits_, - Maths.wdiv(poolState_.debt, vars.noOfLoans * 1e18) - ) - ); - - (vars.bondFactor, vars.bondSize) = _bondParams( - vars.borrowerDebt, - vars.borrowerCollateral, - vars.momp - ); - - // record liquidation info - _recordAuction( - auctions_, - borrowerAddress_, - vars.bondSize, - vars.bondFactor, - vars.momp, - vars.neutralPrice - ); - - // update kicker balances and get the difference needed to cover bond (after using any kick claimable funds if any) - kickResult_.amountToCoverBond = _updateKicker(auctions_, vars.bondSize); - - // remove kicked loan from heap - Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); - - // when loan is kicked, penalty of three months of interest is added - vars.t0KickPenalty = Maths.wdiv(Maths.wmul(kickResult_.t0KickedDebt, poolState_.rate), 4 * 1e18); - vars.kickPenalty = Maths.wmul(vars.t0KickPenalty, poolState_.inflator); - - kickResult_.t0PoolDebt = poolState_.t0Debt + vars.t0KickPenalty; - kickResult_.t0KickedDebt += vars.t0KickPenalty; - - // update borrower debt with kicked debt penalty - borrower.t0Debt = kickResult_.t0KickedDebt; - - emit Kick( - borrowerAddress_, - vars.borrowerDebt + vars.kickPenalty, - vars.borrowerCollateral, - vars.bondSize - ); - } - - /** - * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. - * @dev emit events: - * - Take - * @param borrower_ Struct containing auctioned borrower details. - * @param params_ Struct containing take action params details. - * @return vars_ Struct containing auction take vars. - */ - function _take( - AuctionsState storage auctions_, - Borrower memory borrower_, - TakeParams memory params_ - ) internal returns (TakeLocalVars memory vars_) { - Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - - vars_ = _prepareTake( - liquidation, - borrower_.t0Debt, - borrower_.collateral, - params_.inflator - ); - - // These are placeholder max values passed to calculateTakeFlows because there is no explicit bound on the - // quote token amount in take calls (as opposed to bucketTake) - vars_.unscaledDeposit = type(uint256).max; - vars_.bucketScale = Maths.WAD; - - uint256 takeableCollateral = borrower_.collateral; - // for NFT take make sure the take flow and bond change calculation happens for the rounded collateral that can be taken - if (params_.poolType == uint8(PoolType.ERC721)) { - takeableCollateral = (takeableCollateral / 1e18) * 1e18; - } - - // In the case of take, the taker binds the collateral qty but not the quote token qty - // ugly to get take work like a bucket take -- this is the max amount of quote token from the take that could go to - // reduce the debt of the borrower -- analagous to the amount of deposit in the bucket for a bucket take - vars_ = _calculateTakeFlowsAndBondChange( - Maths.min(takeableCollateral, params_.takeCollateral), - params_.inflator, - params_.collateralScale, - vars_ - ); - - _rewardTake(auctions_, liquidation, vars_); - - emit Take( - params_.borrower, - vars_.quoteTokenAmount, - vars_.collateralAmount, - vars_.bondChange, - vars_.isRewarded - ); - - if (params_.poolType == uint8(PoolType.ERC721)) { - // slither-disable-next-line divide-before-multiply - uint256 collateralTaken = (vars_.collateralAmount / 1e18) * 1e18; // solidity rounds down, so if 2.5 it will be 2.5 / 1 = 2 - - if (collateralTaken != vars_.collateralAmount) { // collateral taken not a round number - if (Maths.min(borrower_.collateral, params_.takeCollateral) >= collateralTaken + 1e18) { - collateralTaken += 1e18; // round up collateral to take - // taker should send additional quote tokens to cover difference between collateral needed to be taken and rounded collateral, at auction price - // borrower will get quote tokens for the difference between rounded collateral and collateral taken to cover debt - vars_.excessQuoteToken = Maths.wmul(collateralTaken - vars_.collateralAmount, vars_.auctionPrice); - vars_.collateralAmount = collateralTaken; - } else { - // shouldn't get here, but just in case revert - revert CollateralRoundingNeededButNotPossible(); - } - } - } - } - - /** - * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). - * @dev emit events: - * - BucketTake - * @param borrower_ Struct containing auctioned borrower details. - * @param params_ Struct containing take action details. - * @return vars_ Struct containing auction take vars. - */ - function _takeBucket( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - Borrower memory borrower_, - BucketTakeParams memory params_ - ) internal returns (TakeLocalVars memory vars_) { - Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - - vars_= _prepareTake( - liquidation, - borrower_.t0Debt, - borrower_.collateral, - params_.inflator - ); - - vars_.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); - - // revert if no quote tokens in arbed bucket - if (vars_.unscaledDeposit == 0) revert InsufficientLiquidity(); - - vars_.bucketPrice = _priceAt(params_.index); - - // cannot arb with a price lower than the auction price - if (vars_.auctionPrice > vars_.bucketPrice) revert AuctionPriceGtBucketPrice(); - - // if deposit take then price to use when calculating take is bucket price - if (params_.depositTake) vars_.auctionPrice = vars_.bucketPrice; - - vars_.bucketScale = Deposits.scale(deposits_, params_.index); - - vars_ = _calculateTakeFlowsAndBondChange( - borrower_.collateral, - params_.inflator, - params_.collateralScale, - vars_ - ); - - // revert if bucket deposit cannot cover at least one unit of collateral - if (vars_.collateralAmount == 0) revert InsufficientLiquidity(); - - _rewardBucketTake( - auctions_, - deposits_, - buckets_, - liquidation, - params_.index, - params_.depositTake, - vars_ - ); - - emit BucketTake( - params_.borrower, - params_.index, - vars_.quoteTokenAmount, - vars_.collateralAmount, - vars_.bondChange, - vars_.isRewarded - ); - } - - /** - * @notice Performs update of an auctioned loan that was taken (using bucket or regular take). - * @notice If borrower becomes recollateralized then auction is settled. Update loan's state. - * @dev reverts on: - * - borrower debt less than pool min debt AmountLTMinDebt() - * @param borrower_ Struct containing pool details. - * @param borrower_ The borrower details owning loan that is taken. - * @param borrowerAddress_ The address of the borrower. - * @return newLup_ The new LUP of pool (after debt is repaid). - * @return settledAuction_ True if auction is settled by the take action. (NFT take: rebalance borrower collateral in pool if true) - * @return remainingCollateral_ Borrower collateral remaining after take action. (NFT take: collateral to be rebalanced in case of NFT settlement) - * @return compensatedCollateral_ Amount of collateral compensated, to be deducted from pool pledged collateral accumulator. - */ - function _takeLoan( - AuctionsState storage auctions_, - mapping(uint256 => Bucket) storage buckets_, - DepositsState storage deposits_, - LoansState storage loans_, - PoolState memory poolState_, - Borrower memory borrower_, - address borrowerAddress_ - ) internal returns ( - uint256 newLup_, - bool settledAuction_, - uint256 remainingCollateral_, - uint256 compensatedCollateral_ - ) { - - uint256 borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); - - // check that taking from loan doesn't leave borrower debt under min debt amount - _revertOnMinDebt( - loans_, - poolState_.debt, - borrowerDebt, - poolState_.quoteDustLimit - ); - - // calculate new lup with repaid debt from take - newLup_ = _lup(deposits_, poolState_.debt); - - remainingCollateral_ = borrower_.collateral; - - if (_isCollateralized(borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { - settledAuction_ = true; - - // settle auction and update borrower's collateral with value after settlement - (remainingCollateral_, compensatedCollateral_) = _settleAuction( - auctions_, - buckets_, - deposits_, - borrowerAddress_, - borrower_.collateral, - poolState_.poolType - ); - - borrower_.collateral = remainingCollateral_; - } - - // update loan state, stamp borrower t0Np only when exiting from auction - Loans.update( - loans_, - auctions_, - deposits_, - borrower_, - borrowerAddress_, - poolState_.debt, - poolState_.rate, - newLup_, - !settledAuction_, - settledAuction_ // stamp borrower t0Np if exiting from auction - ); - } - - /** - * @notice Calculates bond parameters of an auction. - * @param borrowerDebt_ Borrower's debt before entering in liquidation. - * @param collateral_ Borrower's collateral before entering in liquidation. - * @param momp_ Current pool momp. - */ - function _bondParams( - uint256 borrowerDebt_, - uint256 collateral_, - uint256 momp_ - ) internal pure returns (uint256 bondFactor_, uint256 bondSize_) { - uint256 thresholdPrice = borrowerDebt_ * Maths.WAD / collateral_; - - // bondFactor = min(30%, max(1%, (MOMP - thresholdPrice) / MOMP)) - if (thresholdPrice >= momp_) { - bondFactor_ = 0.01 * 1e18; - } else { - bondFactor_ = Maths.min( - 0.3 * 1e18, - Maths.max( - 0.01 * 1e18, - 1e18 - Maths.wdiv(thresholdPrice, momp_) - ) - ); - } - - bondSize_ = Maths.wmul(bondFactor_, borrowerDebt_); - } - - /** - * @notice Updates kicker balances. - * @dev write state: - * - update locked and claimable kicker accumulators - * @param bondSize_ Bond size to cover newly kicked auction. - * @return bondDifference_ The amount that kicker should send to pool to cover auction bond. - */ - function _updateKicker( - AuctionsState storage auctions_, - uint256 bondSize_ - ) internal returns (uint256 bondDifference_){ - Kicker storage kicker = auctions_.kickers[msg.sender]; - - kicker.locked += bondSize_; - - uint256 kickerClaimable = kicker.claimable; - - if (kickerClaimable >= bondSize_) { - kicker.claimable -= bondSize_; - - // decrement total bond escrowed by bond size - auctions_.totalBondEscrowed -= bondSize_; - } else { - bondDifference_ = bondSize_ - kickerClaimable; - kicker.claimable = 0; - - // decrement total bond escrowed by kicker claimable - auctions_.totalBondEscrowed -= kickerClaimable; - } - } - - /** - * @notice Computes the flows of collateral, quote token between the borrower, lender and kicker. - * @param totalCollateral_ Total collateral in loan. - * @param inflator_ Current pool inflator. - * @param vars TakeParams for the take/buckettake - */ - function _calculateTakeFlowsAndBondChange( - uint256 totalCollateral_, - uint256 inflator_, - uint256 collateralScale_, - TakeLocalVars memory vars - ) internal pure returns ( - TakeLocalVars memory - ) { - // price is the current auction price, which is the price paid by the LENDER for collateral - // from the borrower point of view, the price is actually (1-bpf) * price, as the rewards to the - // bond holder are effectively paid for by the borrower. - uint256 borrowerPayoffFactor = (vars.isRewarded) ? Maths.WAD - uint256(vars.bpf) : Maths.WAD; - uint256 borrowerPrice = (vars.isRewarded) ? Maths.wmul(borrowerPayoffFactor, vars.auctionPrice) : vars.auctionPrice; - - // If there is no unscaled quote token bound, then we pass in max, but that cannot be scaled without an overflow. So we check in the line below. - vars.quoteTokenAmount = (vars.unscaledDeposit != type(uint256).max) ? Maths.wmul(vars.unscaledDeposit, vars.bucketScale) : type(uint256).max; - - uint256 borrowerCollateralValue = Maths.wmul(totalCollateral_, borrowerPrice); - - if (vars.quoteTokenAmount <= vars.borrowerDebt && vars.quoteTokenAmount <= borrowerCollateralValue) { - // quote token used to purchase is constraining factor - vars.collateralAmount = _roundToScale(Maths.wdiv(vars.quoteTokenAmount, borrowerPrice), collateralScale_); - vars.t0RepayAmount = Maths.wdiv(vars.quoteTokenAmount, inflator_); - vars.unscaledQuoteTokenAmount = vars.unscaledDeposit; - - vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); - - } else if (vars.borrowerDebt <= borrowerCollateralValue) { - // borrower debt is constraining factor - vars.collateralAmount = _roundToScale(Maths.wdiv(vars.borrowerDebt, borrowerPrice), collateralScale_); - vars.t0RepayAmount = vars.t0BorrowerDebt; - vars.unscaledQuoteTokenAmount = Maths.wdiv(vars.borrowerDebt, vars.bucketScale); - - vars.quoteTokenAmount = (vars.isRewarded) ? Maths.wdiv(vars.borrowerDebt, borrowerPayoffFactor) : vars.borrowerDebt; - - } else { - // collateral available is constraint - vars.collateralAmount = totalCollateral_; - vars.t0RepayAmount = Maths.wdiv(borrowerCollateralValue, inflator_); - vars.unscaledQuoteTokenAmount = Maths.wdiv(borrowerCollateralValue, vars.bucketScale); - - vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); - } - - if (vars.isRewarded) { - // take is below neutralPrice, Kicker is rewarded - vars.bondChange = Maths.wmul(vars.quoteTokenAmount, uint256(vars.bpf)); - } else { - // take is above neutralPrice, Kicker is penalized - vars.bondChange = Maths.wmul(vars.quoteTokenAmount, uint256(-vars.bpf)); - } - - return vars; - } - - /** - * @notice Saves a new liquidation that was kicked. - * @dev write state: - * - borrower -> liquidation mapping update - * - increment auctions count accumulator - * - updates auction queue state - * @param borrowerAddress_ Address of the borrower that is kicked. - * @param bondSize_ Bond size to cover newly kicked auction. - * @param bondFactor_ Bond factor of the newly kicked auction. - * @param momp_ Current pool MOMP. - * @param neutralPrice_ Current pool Neutral Price. - */ - function _recordAuction( - AuctionsState storage auctions_, - address borrowerAddress_, - uint256 bondSize_, - uint256 bondFactor_, - uint256 momp_, - uint256 neutralPrice_ - ) internal { - Liquidation storage liquidation = auctions_.liquidations[borrowerAddress_]; - if (liquidation.kickTime != 0) revert AuctionActive(); - - // record liquidation info - liquidation.kicker = msg.sender; - liquidation.kickTime = uint96(block.timestamp); - liquidation.kickMomp = uint96(momp_); - liquidation.bondSize = uint160(bondSize_); - liquidation.bondFactor = uint96(bondFactor_); - liquidation.neutralPrice = uint96(neutralPrice_); - - // increment number of active auctions - ++auctions_.noOfAuctions; - - // update totalBondEscrowed accumulator - auctions_.totalBondEscrowed += bondSize_; - - // update auctions queue - if (auctions_.head != address(0)) { - // other auctions in queue, liquidation doesn't exist or overwriting. - auctions_.liquidations[auctions_.tail].next = borrowerAddress_; - liquidation.prev = auctions_.tail; - } else { - // first auction in queue - auctions_.head = borrowerAddress_; - } - // update liquidation with the new ordering - auctions_.tail = borrowerAddress_; - } - - /** - * @notice Removes auction and repairs the queue order. - * @notice Updates kicker's claimable balance with bond size awarded and subtracts bond size awarded from liquidationBondEscrowed. - * @dev write state: - * - decrement kicker locked accumulator, increment kicker claimable accumumlator - * - decrement auctions count accumulator - * - update auction queue state - * @param borrower_ Auctioned borrower address. - */ - function _removeAuction( - AuctionsState storage auctions_, - address borrower_ - ) internal { - Liquidation memory liquidation = auctions_.liquidations[borrower_]; - // update kicker balances - Kicker storage kicker = auctions_.kickers[liquidation.kicker]; - - kicker.locked -= liquidation.bondSize; - kicker.claimable += liquidation.bondSize; - - // decrement number of active auctions - -- auctions_.noOfAuctions; - - // update auctions queue - if (auctions_.head == borrower_ && auctions_.tail == borrower_) { - // liquidation is the head and tail - auctions_.head = address(0); - auctions_.tail = address(0); - } - else if(auctions_.head == borrower_) { - // liquidation is the head - auctions_.liquidations[liquidation.next].prev = address(0); - auctions_.head = liquidation.next; - } - else if(auctions_.tail == borrower_) { - // liquidation is the tail - auctions_.liquidations[liquidation.prev].next = address(0); - auctions_.tail = liquidation.prev; - } - else { - // liquidation is in the middle - auctions_.liquidations[liquidation.prev].next = liquidation.next; - auctions_.liquidations[liquidation.next].prev = liquidation.prev; - } - // delete liquidation - delete auctions_.liquidations[borrower_]; - } - - /** - * @notice Rewards actors of a regular take action. - * @dev write state: - * - update liquidation bond size accumulator - * - update kicker's locked balance accumulator - * - update auctions.totalBondEscrowed accumulator - * @param vars Struct containing take action result details. - */ - function _rewardTake( - AuctionsState storage auctions_, - Liquidation storage liquidation_, - TakeLocalVars memory vars - ) internal { - if (vars.isRewarded) { - // take is below neutralPrice, Kicker is rewarded - liquidation_.bondSize += uint160(vars.bondChange); - auctions_.kickers[vars.kicker].locked += vars.bondChange; - auctions_.totalBondEscrowed += vars.bondChange; - } else { - // take is above neutralPrice, Kicker is penalized - vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); - - liquidation_.bondSize -= uint160(vars.bondChange); - auctions_.kickers[vars.kicker].locked -= vars.bondChange; - auctions_.totalBondEscrowed -= vars.bondChange; - } - } - - /** - * @notice Rewards actors of a bucket take action. - * @dev write state: - * - Buckets.addLenderLPs: - * - increment taker lender.lps accumulator and lender.depositTime state - * - increment kicker lender.lps accumulator and lender.depositTime state - * - update liquidation bond size accumulator - * - update kicker's locked balance accumulator - * - update auctions.totalBondEscrowed accumulator - * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): - * - update values array state - * - increment bucket.collateral and bucket.lps accumulator - * @dev emit events: - * - BucketTakeLPAwarded - * @param vars Struct containing take action result details. - */ - function _rewardBucketTake( - AuctionsState storage auctions_, - DepositsState storage deposits_, - mapping(uint256 => Bucket) storage buckets_, - Liquidation storage liquidation_, - uint256 bucketIndex_, - bool depositTake_, - TakeLocalVars memory vars - ) internal { - Bucket storage bucket = buckets_[bucketIndex_]; - - uint256 scaledDeposit = Maths.wmul(vars.unscaledDeposit, vars.bucketScale); - - uint256 exchangeRate = Buckets.getExchangeRate( - bucket.collateral, - bucket.lps, - scaledDeposit, - vars.bucketPrice - ); - - uint256 bankruptcyTime = bucket.bankruptcyTime; - uint256 totalLPsReward; - - // if arb take - taker is awarded collateral * (bucket price - auction price) worth (in quote token terms) units of LPB in the bucket - if (!depositTake_) { - uint256 takerReward = Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice); - - totalLPsReward = Maths.wdiv(takerReward, exchangeRate); - - Buckets.addLenderLPs(bucket, bankruptcyTime, msg.sender, totalLPsReward); - } - - uint256 kickerLPsReward; - - // the bondholder/kicker is awarded bond change worth of LPB in the bucket - if (vars.isRewarded) { - kickerLPsReward = Maths.wdiv(vars.bondChange, exchangeRate); - totalLPsReward += kickerLPsReward; - - Buckets.addLenderLPs(bucket, bankruptcyTime, vars.kicker, kickerLPsReward); - } else { - // take is above neutralPrice, Kicker is penalized - vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); - - liquidation_.bondSize -= uint160(vars.bondChange); - - auctions_.kickers[vars.kicker].locked -= vars.bondChange; - auctions_.totalBondEscrowed -= vars.bondChange; - } - - Deposits.unscaledRemove(deposits_, bucketIndex_, vars.unscaledQuoteTokenAmount); // remove quote tokens from bucket’s deposit - - // total rewarded LPs are added to the bucket LP balance - bucket.lps += totalLPsReward; - - // collateral is added to the bucket’s claimable collateral - bucket.collateral += vars.collateralAmount; - - emit BucketTakeLPAwarded( - msg.sender, - vars.kicker, - totalLPsReward - kickerLPsReward, - kickerLPsReward - ); - } - - /** - * @notice Calculates auction price. - * @param kickMomp_ MOMP recorded at the time of kick. - * @param neutralPrice_ Neutral Price of the auction. - * @param kickTime_ Time when auction was kicked. - * @return price_ Calculated auction price. - */ - function _auctionPrice( - uint256 kickMomp_, - uint256 neutralPrice_, - uint256 kickTime_ - ) internal view returns (uint256 price_) { - uint256 elapsedHours = Maths.wdiv((block.timestamp - kickTime_) * 1e18, 1 hours * 1e18); - - elapsedHours -= Maths.min(elapsedHours, 1e18); // price locked during cure period - - int256 timeAdjustment = PRBMathSD59x18.mul(-1 * 1e18, int256(elapsedHours)); - uint256 referencePrice = Maths.max(kickMomp_, neutralPrice_); - - price_ = 32 * Maths.wmul(referencePrice, uint256(PRBMathSD59x18.exp2(timeAdjustment))); - } - - /** - * @notice Calculates bond penalty factor. - * @dev Called in kick and take. - * @param debt_ Borrower debt. - * @param collateral_ Borrower collateral. - * @param neutralPrice_ NP of auction. - * @param bondFactor_ Factor used to determine bondSize. - * @param auctionPrice_ Auction price at the time of call. - * @return bpf_ Factor used in determining bond Reward (positive) or penalty (negative). - */ - function _bpf( - uint256 debt_, - uint256 collateral_, - uint256 neutralPrice_, - uint256 bondFactor_, - uint256 auctionPrice_ - ) internal pure returns (int256) { - int256 thresholdPrice = int256(Maths.wdiv(debt_, collateral_)); - - int256 sign; - if (thresholdPrice < int256(neutralPrice_)) { - // BPF = BondFactor * min(1, max(-1, (neutralPrice - price) / (neutralPrice - thresholdPrice))) - sign = Maths.minInt( - 1e18, - Maths.maxInt( - -1 * 1e18, - PRBMathSD59x18.div( - int256(neutralPrice_) - int256(auctionPrice_), - int256(neutralPrice_) - thresholdPrice - ) - ) - ); - } else { - int256 val = int256(neutralPrice_) - int256(auctionPrice_); - if (val < 0 ) sign = -1e18; - else if (val != 0) sign = 1e18; - } - - return PRBMathSD59x18.mul(int256(bondFactor_), sign); - } - - /** - * @notice Utility function to validate take and calculate take's parameters. - * @dev write state: - * - update liquidation.alreadyTaken state - * @dev reverts on: - * - loan is not in auction NoAuction() - * - in 1 hour cool down period TakeNotPastCooldown() - * @param liquidation_ Liquidation struct holding auction details. - * @param t0Debt_ Borrower t0 debt. - * @param collateral_ Borrower collateral. - * @param inflator_ The pool's inflator, used to calculate borrower debt. - * @return vars The prepared vars for take action. - */ - function _prepareTake( - Liquidation storage liquidation_, - uint256 t0Debt_, - uint256 collateral_, - uint256 inflator_ - ) internal returns (TakeLocalVars memory vars) { - - uint256 kickTime = liquidation_.kickTime; - if (kickTime == 0) revert NoAuction(); - if (block.timestamp - kickTime <= 1 hours) revert TakeNotPastCooldown(); - - vars.t0BorrowerDebt = t0Debt_; - - // if first take borrower debt is increased by 7% penalty - if (!liquidation_.alreadyTaken) { - vars.t0DebtPenalty = Maths.wmul(t0Debt_, 0.07 * 1e18); - vars.t0BorrowerDebt += vars.t0DebtPenalty; - - liquidation_.alreadyTaken = true; - } - - vars.borrowerDebt = Maths.wmul(vars.t0BorrowerDebt, inflator_); - - uint256 neutralPrice = liquidation_.neutralPrice; - - vars.auctionPrice = _auctionPrice(liquidation_.kickMomp, neutralPrice, kickTime); - vars.bpf = _bpf( - vars.borrowerDebt, - collateral_, - neutralPrice, - liquidation_.bondFactor, - vars.auctionPrice - ); - vars.factor = uint256(1e18 - Maths.maxInt(0, vars.bpf)); - vars.kicker = liquidation_.kicker; - vars.isRewarded = (vars.bpf >= 0); - } - - /**********************/ - /*** View Functions ***/ - /**********************/ - - function _lup( - DepositsState storage deposits_, - uint256 debt_ - ) internal view returns (uint256) { - return _priceAt(Deposits.findIndexOfSum(deposits_, debt_)); - } - -} diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index 3512143dd..9c5c584ee 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -30,7 +30,7 @@ import { Deposits } from '../internal/Deposits.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; -import { Auctions } from './Auctions.sol'; +import { SettlerActions } from './SettlerActions.sol'; /** @title BorrowerActions library @@ -46,7 +46,7 @@ library BorrowerActions { struct DrawDebtLocalVars { bool borrow; // true if borrow action uint256 borrowerDebt; // [WAD] borrower's accrued debt - uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs (NFTs only) + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP (NFTs only) uint256 t0BorrowAmount; // [WAD] t0 amount to borrow uint256 t0DebtChange; // [WAD] additional t0 debt resulted from draw debt action bool inAuction; // true if loan is auctioned @@ -55,7 +55,7 @@ library BorrowerActions { } struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt - uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LPs (NFTs only) + uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP (NFTs only) bool inAuction; // true if loan still in auction after repay, false otherwise bool pull; // true if pull action bool repay; // true if repay action @@ -91,7 +91,7 @@ library BorrowerActions { /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions * @dev write state: - * - Auctions._settleAuction: + * - SettlerActions._settleAuction: * - _removeAuction: * - decrement kicker locked accumulator, increment kicker claimable accumumlator * - decrement auctions count accumulator @@ -109,7 +109,7 @@ library BorrowerActions { * - limit price reached LimitIndexExceeded() * - borrower cannot draw more debt BorrowerUnderCollateralized() * @dev emit events: - * - Auctions._settleAuction: + * - SettlerActions._settleAuction: * - AuctionNFTSettle or AuctionSettle */ function drawDebt( @@ -149,7 +149,7 @@ library BorrowerActions { borrower.collateral += collateralToPledge_; result_.remainingCollateral += collateralToPledge_; - result_.newLup = _lup(deposits_, result_.poolDebt); + result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // if loan is auctioned and becomes collateralized by newly pledged collateral then settle auction if ( @@ -168,7 +168,7 @@ library BorrowerActions { ( result_.remainingCollateral, vars.compensatedCollateral - ) = Auctions._settleAuction( + ) = SettlerActions._settleAuction( auctions_, buckets_, deposits_, @@ -213,7 +213,7 @@ library BorrowerActions { // add debt change to pool's debt result_.t0PoolDebt += vars.t0DebtChange; result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); - result_.newLup = _lup(deposits_, result_.poolDebt); + result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // revert if borrow drives LUP price under the specified price limit _revertIfPriceDroppedBelowLimit(result_.newLup, limitIndex_); @@ -250,7 +250,7 @@ library BorrowerActions { /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions * @dev write state: - * - Auctions._settleAuction: + * - SettlerActions._settleAuction: * - _removeAuction: * - decrement kicker locked accumulator, increment kicker claimable accumumlator * - decrement auctions count accumulator @@ -269,7 +269,7 @@ library BorrowerActions { * - not enough collateral to pull InsufficientCollateral() * - limit price reached LimitIndexExceeded() * @dev emit events: - * - Auctions._settleAuction: + * - SettlerActions._settleAuction: * - AuctionNFTSettle or AuctionSettle */ function repayDebt( @@ -330,7 +330,7 @@ library BorrowerActions { poolState_.quoteDustLimit ); - result_.newLup = _lup(deposits_, result_.poolDebt); + result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // if loan is auctioned and becomes collateralized by repaying debt then settle auction if (vars.inAuction) { @@ -347,7 +347,7 @@ library BorrowerActions { ( result_.remainingCollateral, vars.compensatedCollateral - ) = Auctions._settleAuction( + ) = SettlerActions._settleAuction( auctions_, buckets_, deposits_, @@ -376,7 +376,7 @@ library BorrowerActions { if (vars.inAuction) revert AuctionActive(); // calculate LUP only if it wasn't calculated in repay action - if (!vars.repay) result_.newLup = _lup(deposits_, result_.poolDebt); + if (!vars.repay) result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); _revertIfPriceDroppedBelowLimit(result_.newLup, limitIndex_); @@ -441,7 +441,7 @@ library BorrowerActions { Borrower memory borrower = loans_.borrowers[msg.sender]; - newLup_ = _lup(deposits_, poolState_.debt); + newLup_ = Deposits.getLup(deposits_, poolState_.debt); // revert if loan is not fully collateralized at current LUP if ( @@ -487,11 +487,4 @@ library BorrowerActions { return auctions_.liquidations[borrower_].kickTime != 0; } - function _lup( - DepositsState storage deposits_, - uint256 debt_ - ) internal view returns (uint256) { - return _priceAt(Deposits.findIndexOfSum(deposits_, debt_)); - } - } diff --git a/src/libraries/external/KickerActions.sol b/src/libraries/external/KickerActions.sol new file mode 100644 index 000000000..9a3708e8a --- /dev/null +++ b/src/libraries/external/KickerActions.sol @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.14; + +import { PoolType } from '../../interfaces/pool/IPool.sol'; + +import { + AuctionsState, + Borrower, + Bucket, + DepositsState, + Kicker, + Lender, + Liquidation, + LoansState, + PoolState, + ReserveAuctionState +} from '../../interfaces/pool/commons/IPoolState.sol'; +import { + KickResult, + KickReserveAuctionParams +} from '../../interfaces/pool/commons/IPoolInternals.sol'; + +import { + _auctionPrice, + _bondParams, + _bpf, + _claimableReserves, + _isCollateralized, + _priceAt, + _reserveAuctionPrice +} from '../helpers/PoolHelper.sol'; +import { + _revertIfPriceDroppedBelowLimit +} from '../helpers/RevertsHelper.sol'; + +import { Buckets } from '../internal/Buckets.sol'; +import { Deposits } from '../internal/Deposits.sol'; +import { Loans } from '../internal/Loans.sol'; +import { Maths } from '../internal/Maths.sol'; + +/** + @title Auctions kicker actions library + @notice External library containing kicker actions involving auctions within pool: + - kick undercollateralized loans; start reserve auctions + */ +library KickerActions { + + /*************************/ + /*** Local Var Structs ***/ + /*************************/ + + struct KickLocalVars { + uint256 borrowerDebt; // [WAD] the accrued debt of kicked borrower + uint256 borrowerCollateral; // [WAD] amount of kicked borrower collateral + uint256 neutralPrice; // [WAD] neutral price recorded in kick action + uint256 noOfLoans; // number of loans and auctions in pool (used to calculate MOMP) + uint256 momp; // [WAD] MOMP of kicked auction + uint256 bondFactor; // [WAD] bond factor of kicked auction + uint256 bondSize; // [WAD] bond size of kicked auction + uint256 t0KickPenalty; // [WAD] t0 debt added as kick penalty + uint256 kickPenalty; // [WAD] current debt added as kick penalty + } + struct KickWithDepositLocalVars { + uint256 amountToDebitFromDeposit; // [WAD] the amount of quote tokens used to kick and debited from lender deposit + uint256 bucketCollateral; // [WAD] amount of collateral in bucket + uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket + uint256 bucketLPs; // [WAD] LP of the bucket + uint256 bucketPrice; // [WAD] bucket price + uint256 bucketRate; // [WAD] bucket exchange rate + uint256 bucketScale; // [WAD] bucket scales + uint256 bucketUnscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket + uint256 lenderLPs; // [WAD] LP of lender in bucket + uint256 redeemedLPs; // [WAD] LP used by kick action + } + + /**************/ + /*** Events ***/ + /**************/ + + // See `IPoolEvents` for descriptions + event Kick(address indexed borrower, uint256 debt, uint256 collateral, uint256 bond); + event RemoveQuoteToken(address indexed lender, uint256 indexed price, uint256 amount, uint256 lpRedeemed, uint256 lup); + event KickReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); + + /**************/ + /*** Errors ***/ + /**************/ + + // See `IPoolErrors` for descriptions + error AuctionActive(); + error BorrowerOk(); + error InsufficientLiquidity(); + error NoReserves(); + error PriceBelowLUP(); + error ReserveAuctionTooSoon(); + + /***************************/ + /*** External Functions ***/ + /***************************/ + + /** + * @notice Called to start borrower liquidation and to update the auctions queue. + * @param poolState_ Current state of the pool. + * @param borrowerAddress_ Address of the borrower to kick. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. + * @return kickResult_ The result of the kick action. + */ + function kick( + AuctionsState storage auctions_, + DepositsState storage deposits_, + LoansState storage loans_, + PoolState calldata poolState_, + address borrowerAddress_, + uint256 limitIndex_ + ) external returns ( + KickResult memory + ) { + return _kick( + auctions_, + deposits_, + loans_, + poolState_, + borrowerAddress_, + limitIndex_, + 0 + ); + } + + /** + * @notice Called by lenders to kick loans using their deposits. + * @dev write state: + * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): + * - update values array state + * - decrement lender.lps accumulator + * - decrement bucket.lps accumulator + * @dev emit events: + * - RemoveQuoteToken + * @param poolState_ Current state of the pool. + * @param index_ The deposit index from where lender removes liquidity. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. + * @return kickResult_ The result of the kick action. + */ + function kickWithDeposit( + AuctionsState storage auctions_, + DepositsState storage deposits_, + mapping(uint256 => Bucket) storage buckets_, + LoansState storage loans_, + PoolState calldata poolState_, + uint256 index_, + uint256 limitIndex_ + ) external returns ( + KickResult memory kickResult_ + ) { + Bucket storage bucket = buckets_[index_]; + Lender storage lender = bucket.lenders[msg.sender]; + + KickWithDepositLocalVars memory vars; + + if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLPs = lender.lps; + + vars.bucketLPs = bucket.lps; + vars.bucketCollateral = bucket.collateral; + vars.bucketPrice = _priceAt(index_); + vars.bucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, index_); + vars.bucketScale = Deposits.scale(deposits_, index_); + vars.bucketDeposit = Maths.wmul(vars.bucketUnscaledDeposit, vars.bucketScale); + + // calculate max amount that can be removed (constrained by lender LP in bucket, bucket deposit and the amount lender wants to remove) + vars.bucketRate = Buckets.getExchangeRate( + vars.bucketCollateral, + vars.bucketLPs, + vars.bucketDeposit, + vars.bucketPrice + ); + + vars.amountToDebitFromDeposit = Maths.wmul(vars.lenderLPs, vars.bucketRate); // calculate amount to remove based on lender LP in bucket + + if (vars.amountToDebitFromDeposit > vars.bucketDeposit) vars.amountToDebitFromDeposit = vars.bucketDeposit; // cap the amount to remove at bucket deposit + + // revert if no amount that can be removed + if (vars.amountToDebitFromDeposit == 0) revert InsufficientLiquidity(); + + // kick top borrower + kickResult_ = _kick( + auctions_, + deposits_, + loans_, + poolState_, + Loans.getMax(loans_).borrower, + limitIndex_, + vars.amountToDebitFromDeposit + ); + + // amount to remove from deposit covers entire bond amount + if (vars.amountToDebitFromDeposit > kickResult_.amountToCoverBond) { + vars.amountToDebitFromDeposit = kickResult_.amountToCoverBond; // cap amount to remove from deposit at amount to cover bond + + kickResult_.lup = Deposits.getLup(deposits_, poolState_.debt + vars.amountToDebitFromDeposit); // recalculate the LUP with the amount to cover bond + kickResult_.amountToCoverBond = 0; // entire bond is covered from deposit, no additional amount to be send by lender + } else { + kickResult_.amountToCoverBond -= vars.amountToDebitFromDeposit; // lender should send additional amount to cover bond + } + + // revert if the bucket price used to kick and remove is below new LUP + if (vars.bucketPrice < kickResult_.lup) revert PriceBelowLUP(); + + // remove amount from deposits + if (vars.amountToDebitFromDeposit == vars.bucketDeposit && vars.bucketCollateral == 0) { + // In this case we are redeeming the entire bucket exactly, and need to ensure bucket LPs are set to 0 + vars.redeemedLPs = vars.bucketLPs; + + Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); + + } else { + vars.redeemedLPs = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); + + Deposits.unscaledRemove( + deposits_, + index_, + Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketScale) + ); + } + + // remove bucket LP coresponding to the amount removed from deposits + lender.lps -= vars.redeemedLPs; + bucket.lps -= vars.redeemedLPs; + + emit RemoveQuoteToken( + msg.sender, + index_, + vars.amountToDebitFromDeposit, + vars.redeemedLPs, + kickResult_.lup + ); + } + + /*************************/ + /*** Reserve Auction ***/ + /*************************/ + + /** + * @notice See `IPoolReserveAuctionActions` for descriptions. + * @dev write state: + * - update reserveAuction.unclaimed accumulator + * - update reserveAuction.kicked timestamp state + * @dev reverts on: + * - no reserves to claim NoReserves() + * @dev emit events: + * - KickReserveAuction + */ + function kickReserveAuction( + AuctionsState storage auctions_, + ReserveAuctionState storage reserveAuction_, + KickReserveAuctionParams calldata params_ + ) external returns (uint256 kickerAward_) { + // retrieve timestamp of latest burn event and last burn timestamp + uint256 latestBurnEpoch = reserveAuction_.latestBurnEventEpoch; + uint256 lastBurnTimestamp = reserveAuction_.burnEvents[latestBurnEpoch].timestamp; + + // check that at least two weeks have passed since the last reserve auction completed, and that the auction was not kicked within the past 72 hours + if (block.timestamp < lastBurnTimestamp + 2 weeks || block.timestamp - reserveAuction_.kicked <= 72 hours) { + revert ReserveAuctionTooSoon(); + } + + uint256 curUnclaimedAuctionReserve = reserveAuction_.unclaimed; + + uint256 claimable = _claimableReserves( + Maths.wmul(params_.t0PoolDebt, params_.inflator), + params_.poolSize, + auctions_.totalBondEscrowed, + curUnclaimedAuctionReserve, + params_.poolBalance + ); + + kickerAward_ = Maths.wmul(0.01 * 1e18, claimable); + + curUnclaimedAuctionReserve += claimable - kickerAward_; + + if (curUnclaimedAuctionReserve == 0) revert NoReserves(); + + reserveAuction_.unclaimed = curUnclaimedAuctionReserve; + reserveAuction_.kicked = block.timestamp; + + // increment latest burn event epoch and update burn event timestamp + latestBurnEpoch += 1; + + reserveAuction_.latestBurnEventEpoch = latestBurnEpoch; + reserveAuction_.burnEvents[latestBurnEpoch].timestamp = block.timestamp; + + emit KickReserveAuction( + curUnclaimedAuctionReserve, + _reserveAuctionPrice(block.timestamp), + latestBurnEpoch + ); + } + + /***************************/ + /*** Internal Functions ***/ + /***************************/ + + /** + * @notice Called to start borrower liquidation and to update the auctions queue. + * @dev write state: + * - _recordAuction: + * - borrower -> liquidation mapping update + * - increment auctions count accumulator + * - increment auctions.totalBondEscrowed accumulator + * - updates auction queue state + * - _updateKicker: + * - update locked and claimable kicker accumulators + * - Loans.remove: + * - delete borrower from indices => borrower address mapping + * - remove loan from loans array + * @dev emit events: + * - Kick + * @param poolState_ Current state of the pool. + * @param borrowerAddress_ Address of the borrower to kick. + * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. + * @param additionalDebt_ Additional debt to be used when calculating proposed LUP. + * @return kickResult_ The result of the kick action. + */ + function _kick( + AuctionsState storage auctions_, + DepositsState storage deposits_, + LoansState storage loans_, + PoolState calldata poolState_, + address borrowerAddress_, + uint256 limitIndex_, + uint256 additionalDebt_ + ) internal returns ( + KickResult memory kickResult_ + ) { + Borrower storage borrower = loans_.borrowers[borrowerAddress_]; + + kickResult_.debtPreAction = borrower.t0Debt; + kickResult_.collateralPreAction = borrower.collateral; + kickResult_.t0KickedDebt = kickResult_.debtPreAction ; + // add amount to remove to pool debt in order to calculate proposed LUP + kickResult_.lup = Deposits.getLup(deposits_, poolState_.debt + additionalDebt_); + + KickLocalVars memory vars; + vars.borrowerDebt = Maths.wmul(kickResult_.t0KickedDebt, poolState_.inflator); + vars.borrowerCollateral = kickResult_.collateralPreAction; + + // revert if kick on a collateralized borrower + if (_isCollateralized(vars.borrowerDebt, vars.borrowerCollateral, kickResult_.lup, poolState_.poolType)) { + revert BorrowerOk(); + } + + // calculate auction params + vars.neutralPrice = Maths.wmul(borrower.t0Np, poolState_.inflator); + // check if NP is not less than price at the limit index provided by the kicker - done to prevent frontrunning kick auction call with a large amount of loan + // which will make it harder for kicker to earn a reward and more likely that the kicker is penalized + _revertIfPriceDroppedBelowLimit(vars.neutralPrice, limitIndex_); + + vars.noOfLoans = Loans.noOfLoans(loans_) + auctions_.noOfAuctions; + + vars.momp = _priceAt( + Deposits.findIndexOfSum( + deposits_, + Maths.wdiv(poolState_.debt, vars.noOfLoans * 1e18) + ) + ); + + (vars.bondFactor, vars.bondSize) = _bondParams( + vars.borrowerDebt, + vars.borrowerCollateral, + vars.momp + ); + + // record liquidation info + _recordAuction( + auctions_, + borrowerAddress_, + vars.bondSize, + vars.bondFactor, + vars.momp, + vars.neutralPrice + ); + + // update kicker balances and get the difference needed to cover bond (after using any kick claimable funds if any) + kickResult_.amountToCoverBond = _updateKicker(auctions_, vars.bondSize); + + // remove kicked loan from heap + Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); + + // when loan is kicked, penalty of three months of interest is added + vars.t0KickPenalty = Maths.wdiv(Maths.wmul(kickResult_.t0KickedDebt, poolState_.rate), 4 * 1e18); + vars.kickPenalty = Maths.wmul(vars.t0KickPenalty, poolState_.inflator); + + kickResult_.t0PoolDebt = poolState_.t0Debt + vars.t0KickPenalty; + kickResult_.t0KickedDebt += vars.t0KickPenalty; + + // update borrower debt with kicked debt penalty + borrower.t0Debt = kickResult_.t0KickedDebt; + + emit Kick( + borrowerAddress_, + vars.borrowerDebt + vars.kickPenalty, + vars.borrowerCollateral, + vars.bondSize + ); + } + + /** + * @notice Updates kicker balances. + * @dev write state: + * - update locked and claimable kicker accumulators + * @param bondSize_ Bond size to cover newly kicked auction. + * @return bondDifference_ The amount that kicker should send to pool to cover auction bond. + */ + function _updateKicker( + AuctionsState storage auctions_, + uint256 bondSize_ + ) internal returns (uint256 bondDifference_){ + Kicker storage kicker = auctions_.kickers[msg.sender]; + + kicker.locked += bondSize_; + + uint256 kickerClaimable = kicker.claimable; + + if (kickerClaimable >= bondSize_) { + kicker.claimable -= bondSize_; + + // decrement total bond escrowed by bond size + auctions_.totalBondEscrowed -= bondSize_; + } else { + bondDifference_ = bondSize_ - kickerClaimable; + kicker.claimable = 0; + + // decrement total bond escrowed by kicker claimable + auctions_.totalBondEscrowed -= kickerClaimable; + } + } + + /** + * @notice Saves a new liquidation that was kicked. + * @dev write state: + * - borrower -> liquidation mapping update + * - increment auctions count accumulator + * - updates auction queue state + * @param borrowerAddress_ Address of the borrower that is kicked. + * @param bondSize_ Bond size to cover newly kicked auction. + * @param bondFactor_ Bond factor of the newly kicked auction. + * @param momp_ Current pool MOMP. + * @param neutralPrice_ Current pool Neutral Price. + */ + function _recordAuction( + AuctionsState storage auctions_, + address borrowerAddress_, + uint256 bondSize_, + uint256 bondFactor_, + uint256 momp_, + uint256 neutralPrice_ + ) internal { + Liquidation storage liquidation = auctions_.liquidations[borrowerAddress_]; + if (liquidation.kickTime != 0) revert AuctionActive(); + + // record liquidation info + liquidation.kicker = msg.sender; + liquidation.kickTime = uint96(block.timestamp); + liquidation.kickMomp = uint96(momp_); + liquidation.bondSize = uint160(bondSize_); + liquidation.bondFactor = uint96(bondFactor_); + liquidation.neutralPrice = uint96(neutralPrice_); + + // increment number of active auctions + ++auctions_.noOfAuctions; + + // update totalBondEscrowed accumulator + auctions_.totalBondEscrowed += bondSize_; + + // update auctions queue + if (auctions_.head != address(0)) { + // other auctions in queue, liquidation doesn't exist or overwriting. + auctions_.liquidations[auctions_.tail].next = borrowerAddress_; + liquidation.prev = auctions_.tail; + } else { + // first auction in queue + auctions_.head = borrowerAddress_; + } + // update liquidation with the new ordering + auctions_.tail = borrowerAddress_; + } + +} diff --git a/src/libraries/external/LPActions.sol b/src/libraries/external/LPActions.sol new file mode 100644 index 000000000..951ece024 --- /dev/null +++ b/src/libraries/external/LPActions.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.14; + +import { Bucket, Lender } from '../../interfaces/pool/commons/IPoolState.sol'; + +import { MAX_FENWICK_INDEX } from '../helpers/PoolHelper.sol'; + +import { Maths } from '../internal/Maths.sol'; + +/** + @title LPActions library + @notice External library containing logic for LP owners to: + - increase/decrease/revoke LP allowance; approve/revoke LP transferors; transfer LP + */ +library LPActions { + + /**************/ + /*** Events ***/ + /**************/ + + // See `IPoolEvents` for descriptions + event ApproveLPTransferors(address indexed lender, address[] transferors); + event RevokeLPTransferors(address indexed lender, address[] transferors); + event IncreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); + event DecreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); + event RevokeLPAllowance(address indexed owner, address indexed spender, uint256[] indexes); + event TransferLP(address owner, address newOwner, uint256[] indexes, uint256 lps); + + /**************/ + /*** Errors ***/ + /**************/ + + // See `IPoolErrors` for descriptions + error BucketBankruptcyBlock(); + error InvalidAllowancesInput(); + error InvalidIndex(); + error NoAllowance(); + error TransferorNotApproved(); + error TransferToSameOwner(); + + /***************************/ + /*** External Functions ***/ + /***************************/ + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - increment LP allowances + * @dev reverts on: + * - invalid indexes and amounts input InvalidAllowancesInput() + * @dev emit events: + * - IncreaseLPAllowance + */ + function increaseLPAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external { + uint256 indexesLength = indexes_.length; + + if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); + + uint256 index; + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] += amounts_[i]; + + unchecked { ++i; } + } + + emit IncreaseLPAllowance( + msg.sender, + spender_, + indexes_, + amounts_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - decrement LP allowances + * @dev reverts on: + * - invalid indexes and amounts input InvalidAllowancesInput() + * @dev emit events: + * - DecreaseLPAllowance + */ + function decreaseLPAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ + ) external { + uint256 indexesLength = indexes_.length; + + if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); + + uint256 index; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] -= amounts_[i]; + + unchecked { ++i; } + } + + emit DecreaseLPAllowance( + msg.sender, + spender_, + indexes_, + amounts_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - decrement LP allowances + * @dev emit events: + * - RevokeLPAllowance + */ + function revokeLPAllowance( + mapping(uint256 => uint256) storage allowances_, + address spender_, + uint256[] calldata indexes_ + ) external { + uint256 indexesLength = indexes_.length; + uint256 index; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + allowances_[index] = 0; + + unchecked { ++i; } + } + + emit RevokeLPAllowance( + msg.sender, + spender_, + indexes_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - approvedTransferors mapping + * @dev emit events: + * - ApproveLPTransferors + */ + function approveLPTransferors( + mapping(address => bool) storage allowances_, + address[] calldata transferors_ + ) external { + uint256 transferorsLength = transferors_.length; + for (uint256 i = 0; i < transferorsLength; ) { + allowances_[transferors_[i]] = true; + + unchecked { ++i; } + } + + emit ApproveLPTransferors( + msg.sender, + transferors_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - approvedTransferors mapping + * @dev emit events: + * - RevokeLPTransferors + */ + function revokeLPTransferors( + mapping(address => bool) storage allowances_, + address[] calldata transferors_ + ) external { + uint256 transferorsLength = transferors_.length; + for (uint256 i = 0; i < transferorsLength; ) { + delete allowances_[transferors_[i]]; + + unchecked { ++i; } + } + + emit RevokeLPTransferors( + msg.sender, + transferors_ + ); + } + + /** + * @notice See `IPoolLenderActions` for descriptions + * @dev write state: + * - delete allowance mapping + * - increment new lender.lps accumulator and lender.depositTime state + * - delete old lender from bucket -> lender mapping + * @dev reverts on: + * - invalid index InvalidIndex() + * - no allowance NoAllowance() + * @dev emit events: + * - TransferLP + */ + function transferLP( + mapping(uint256 => Bucket) storage buckets_, + mapping(address => mapping(address => mapping(uint256 => uint256))) storage allowances_, + mapping(address => mapping(address => bool)) storage approvedTransferors_, + address ownerAddress_, + address newOwnerAddress_, + uint256[] calldata indexes_ + ) external { + // revert if msg.sender is not the new owner and is not approved as a transferor by the new owner + if (newOwnerAddress_ != msg.sender && !approvedTransferors_[newOwnerAddress_][msg.sender]) revert TransferorNotApproved(); + + // revert if new owner address is the same as old owner address + if (ownerAddress_ == newOwnerAddress_) revert TransferToSameOwner(); + + uint256 indexesLength = indexes_.length; + uint256 index; + uint256 lpsTransferred; + + for (uint256 i = 0; i < indexesLength; ) { + index = indexes_[i]; + + // revert if invalid index + if (index > MAX_FENWICK_INDEX) revert InvalidIndex(); + + Bucket storage bucket = buckets_[index]; + Lender storage owner = bucket.lenders[ownerAddress_]; + + uint256 bankruptcyTime = bucket.bankruptcyTime; + uint256 ownerDepositTime = owner.depositTime; + uint256 ownerLpBalance = bankruptcyTime < ownerDepositTime ? owner.lps : 0; + + uint256 allowedAmount = allowances_[ownerAddress_][newOwnerAddress_][index]; + if (allowedAmount == 0) revert NoAllowance(); + + // transfer allowed amount or entire LP balance + allowedAmount = Maths.min(allowedAmount, ownerLpBalance); + + // move owner lps (if any) to the new owner + if (allowedAmount != 0) { + Lender storage newOwner = bucket.lenders[newOwnerAddress_]; + + uint256 newOwnerDepositTime = newOwner.depositTime; + + if (newOwnerDepositTime > bankruptcyTime) { + // deposit happened in a healthy bucket, add amount of LP to new owner + newOwner.lps += allowedAmount; + } else { + // bucket bankruptcy happened after deposit, reset balance and add amount of LP to new owner + newOwner.lps = allowedAmount; + } + + owner.lps -= allowedAmount; // remove amount of LP from old owner + lpsTransferred += allowedAmount; // add amount of LP to total LP transferred + + // set the deposit time as the max of transferred deposit and current deposit time + newOwner.depositTime = Maths.max(ownerDepositTime, newOwnerDepositTime); + } + + // reset allowances of transferred LP + delete allowances_[ownerAddress_][newOwnerAddress_][index]; + + unchecked { ++i; } + } + + emit TransferLP( + ownerAddress_, + newOwnerAddress_, + indexes_, + lpsTransferred + ); + } +} diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index 4e5db7cf1..06c0780f9 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -22,8 +22,8 @@ import { Maths } from '../internal/Maths.sol'; /** @title LenderActions library - @notice External library containing logic for pool actors: - - Lenders: add, remove and move quote tokens; transfer LPs + @notice External library containing logic for lender actors: + - Lenders: add, remove and move quote tokens; - Traders: add, remove and move quote tokens; add and remove collateral */ library LenderActions { @@ -35,10 +35,10 @@ library LenderActions { struct MoveQuoteLocalVars { uint256 fromBucketPrice; // [WAD] Price of the bucket to move amount from. uint256 fromBucketCollateral; // [WAD] Total amount of collateral in from bucket. - uint256 fromBucketLPs; // [WAD] Total amount of LPs in from bucket. - uint256 fromBucketLenderLPs; // [WAD] Amount of LPs owned by lender in from bucket. + uint256 fromBucketLPs; // [WAD] Total amount of LP in from bucket. + uint256 fromBucketLenderLPs; // [WAD] Amount of LP owned by lender in from bucket. uint256 fromBucketDepositTime; // Time of lender deposit in the bucket to move amount from. - uint256 fromBucketRemainingLPs; // Amount of LPs remaining in from bucket after move. + uint256 fromBucketRemainingLPs; // Amount of LP remaining in from bucket after move. uint256 fromBucketRemainingDeposit; // Amount of scaled deposit remaining in from bucket after move. uint256 toBucketPrice; // [WAD] Price of the bucket to move amount to. uint256 toBucketBankruptcyTime; // Time the bucket to move amount to was marked as insolvent. @@ -69,13 +69,6 @@ library LenderActions { event MoveQuoteToken(address indexed lender, uint256 indexed from, uint256 indexed to, uint256 amount, uint256 lpRedeemedFrom, uint256 lpAwardedTo, uint256 lup); event RemoveQuoteToken(address indexed lender, uint256 indexed index, uint256 amount, uint256 lpRedeemed, uint256 lup); - event ApproveLPsTransferors(address indexed lender, address[] transferors); - event RevokeLPsTransferors(address indexed lender, address[] transferors); - event IncreaseLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); - event DecreaseLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); - event RevokeLPsAllowance(address indexed owner, address indexed spender, uint256[] indexes); - event TransferLPs(address owner, address newOwner, uint256[] indexes, uint256 lps); - /**************/ /*** Errors ***/ /**************/ @@ -84,18 +77,14 @@ library LenderActions { error BucketBankruptcyBlock(); error CannotMergeToHigherPrice(); error DustAmountNotExceeded(); - error NoAllowance(); - error InvalidAllowancesInput(); error InvalidIndex(); error InvalidAmount(); error LUPBelowHTP(); error NoClaim(); - error InsufficientLPs(); + error InsufficientLP(); error InsufficientLiquidity(); error InsufficientCollateral(); error MoveToSameIndex(); - error TransferorNotApproved(); - error TransferToSameOwner(); /***************************/ /*** External Functions ***/ @@ -106,7 +95,7 @@ library LenderActions { * @dev write state: * - Buckets.addCollateral: * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLPs: + * - addLenderLP: * - increment lender.lps accumulator and lender.depositTime state * @dev reverts on: * - invalid bucket index InvalidIndex() @@ -178,7 +167,7 @@ library LenderActions { addedAmount = Maths.wmul(addedAmount, Maths.WAD - _depositFeeRate(poolState_.rate)); } - bucketLPs_ = Buckets.quoteTokensToLPs( + bucketLPs_ = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, bucketDeposit, @@ -188,10 +177,10 @@ library LenderActions { Deposits.unscaledAdd(deposits_, params_.index, Maths.wdiv(addedAmount, bucketScale)); - // update lender LPs - Buckets.addLenderLPs(bucket, bankruptcyTime, msg.sender, bucketLPs_); + // update lender LP + Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, bucketLPs_); - // update bucket LPs + // update bucket LP bucket.lps += bucketLPs_; // only need to recalculate LUP if the deposit was above it @@ -200,7 +189,13 @@ library LenderActions { } lup_ = _priceAt(lupIndex); - emit AddQuoteToken(msg.sender, params_.index, addedAmount, bucketLPs_, lup_); + emit AddQuoteToken( + msg.sender, + params_.index, + addedAmount, + bucketLPs_, + lup_ + ); } /** @@ -271,7 +266,7 @@ library LenderActions { }) ); - lup_ = _lup(deposits_, poolState_.debt); + lup_ = Deposits.getLup(deposits_, poolState_.debt); // apply unutilized deposit fee if quote token is moved from above the LUP to below the LUP if (vars.fromBucketPrice >= lup_ && vars.toBucketPrice < lup_) { movedAmount_ = Maths.wmul(movedAmount_, Maths.WAD - _depositFeeRate(poolState_.rate)); @@ -281,7 +276,7 @@ library LenderActions { vars.toBucketScale = Deposits.scale(deposits_, params_.toIndex); vars.toBucketDeposit = Maths.wmul(vars.toBucketUnscaledDeposit, vars.toBucketScale); - toBucketLPs_ = Buckets.quoteTokensToLPs( + toBucketLPs_ = Buckets.quoteTokensToLP( toBucket.collateral, toBucket.lps, vars.toBucketDeposit, @@ -296,21 +291,26 @@ library LenderActions { // check loan book's htp against new lup, revert if move drives LUP below HTP if (params_.fromIndex < params_.toIndex && vars.htp > lup_) revert LUPBelowHTP(); - // update lender and bucket LPs balance in from bucket + // update lender and bucket LP balance in from bucket vars.fromBucketRemainingLPs = vars.fromBucketLPs - fromBucketRedeemedLPs_; + // check if from bucket healthy after move quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP if (vars.fromBucketCollateral == 0 && vars.fromBucketRemainingDeposit == 0 && vars.fromBucketRemainingLPs != 0) { - emit BucketBankruptcy(params_.fromIndex, vars.fromBucketRemainingLPs); fromBucket.lps = 0; fromBucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + params_.fromIndex, + vars.fromBucketRemainingLPs + ); } else { - // update lender and bucket LPs balance + // update lender and bucket LP balance fromBucketLender.lps -= fromBucketRedeemedLPs_; fromBucket.lps = vars.fromBucketRemainingLPs; } - // update lender and bucket LPs balance in target bucket + // update lender and bucket LP balance in target bucket Lender storage toBucketLender = toBucket.lenders[msg.sender]; vars.toBucketDepositTime = toBucketLender.depositTime; @@ -327,7 +327,7 @@ library LenderActions { // set deposit time to the greater of the lender's from bucket and the target bucket toBucketLender.depositTime = Maths.max(vars.fromBucketDepositTime, vars.toBucketDepositTime); - // update bucket LPs balance + // update bucket LP balance toBucket.lps += toBucketLPs_; emit MoveQuoteToken( @@ -350,7 +350,7 @@ library LenderActions { * - decrement lender.lps accumulator * - decrement bucket.lps accumulator * @dev reverts on: - * - no LPs NoClaim() + * - no LP NoClaim() * - LUP lower than HTP LUPBelowHTP() * @dev emit events: * - RemoveQuoteToken @@ -389,7 +389,7 @@ library LenderActions { removeParams ); - lup_ = _lup(deposits_, poolState_.debt); + lup_ = Deposits.getLup(deposits_, poolState_.debt); uint256 htp = Maths.wmul(params_.thresholdPrice, poolState_.inflator); @@ -404,18 +404,29 @@ library LenderActions { uint256 lpsRemaining = removeParams.bucketLPs - redeemedLPs_; + // check if bucket healthy after remove quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpsRemaining != 0) { - emit BucketBankruptcy(params_.index, lpsRemaining); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + params_.index, + lpsRemaining + ); } else { - // update lender and bucket LPs balances + // update lender and bucket LP balances lender.lps -= redeemedLPs_; bucket.lps = lpsRemaining; } - emit RemoveQuoteToken(msg.sender, params_.index, removedAmount_, redeemedLPs_, lup_); + emit RemoveQuoteToken( + msg.sender, + params_.index, + removedAmount_, + redeemedLPs_, + lup_ + ); } /** @@ -425,7 +436,7 @@ library LenderActions { * - decrement bucket.collateral and bucket.lps accumulator * @dev reverts on: * - not enough collateral InsufficientCollateral() - * - insufficient LPs InsufficientLPs() + * - insufficient LP InsufficientLP() * @dev emit events: * - BucketBankruptcy */ @@ -448,7 +459,7 @@ library LenderActions { uint256 bucketLPs = bucket.lps; uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); - lpAmount_ = Buckets.collateralToLPs( + lpAmount_ = Buckets.collateralToLP( bucketCollateral, bucketLPs, bucketDeposit, @@ -460,9 +471,9 @@ library LenderActions { uint256 lenderLpBalance; if (bucket.bankruptcyTime < lender.depositTime) lenderLpBalance = lender.lps; - if (lenderLpBalance == 0 || lpAmount_ > lenderLpBalance) revert InsufficientLPs(); + if (lenderLpBalance == 0 || lpAmount_ > lenderLpBalance) revert InsufficientLP(); - // update bucket LPs and collateral balance + // update bucket LP and collateral balance bucketLPs -= lpAmount_; // If clearing out the bucket collateral, ensure it's zeroed out @@ -473,12 +484,17 @@ library LenderActions { bucketCollateral -= Maths.min(bucketCollateral, amount_); bucket.collateral = bucketCollateral; + // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { - emit BucketBankruptcy(index_, bucketLPs); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + index_, + bucketLPs + ); } else { - // update lender LPs balance + // update lender LP balance lender.lps -= lpAmount_; bucket.lps = bucketLPs; @@ -495,7 +511,7 @@ library LenderActions { * - not enough collateral InsufficientCollateral() * - no claim NoClaim() * @return Amount of collateral that was removed. - * @return Amount of LPs redeemed for removed collateral amount. + * @return Amount of LP redeemed for removed collateral amount. */ function removeMaxCollateral( mapping(uint256 => Bucket) storage buckets_, @@ -519,7 +535,7 @@ library LenderActions { * @dev write state: * - Buckets.addCollateral: * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLPs: + * - addLenderLP: * - increment lender.lps accumulator and lender.depositTime state * @dev reverts on: * - invalid merge index CannotMergeToHigherPrice() @@ -572,240 +588,6 @@ library LenderActions { } } - /******************************/ - /*** Transfer LPs Functions ***/ - /******************************/ - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - increment LPs allowances - * @dev reverts on: - * - invalid indexes and amounts input InvalidAllowancesInput() - * @dev emit events: - * - IncreaseLPsAllowance - */ - function increaseLPsAllowance( - mapping(uint256 => uint256) storage allowances_, - address spender_, - uint256[] calldata indexes_, - uint256[] calldata amounts_ - ) external { - uint256 indexesLength = indexes_.length; - - if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); - - uint256 index; - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances_[index] += amounts_[i]; - - unchecked { ++i; } - } - - emit IncreaseLPsAllowance( - msg.sender, - spender_, - indexes_, - amounts_ - ); - } - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - decrement LPs allowances - * @dev reverts on: - * - invalid indexes and amounts input InvalidAllowancesInput() - * @dev emit events: - * - DecreaseLPsAllowance - */ - function decreaseLPsAllowance( - mapping(uint256 => uint256) storage allowances_, - address spender_, - uint256[] calldata indexes_, - uint256[] calldata amounts_ - ) external { - uint256 indexesLength = indexes_.length; - - if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); - - uint256 index; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances_[index] -= amounts_[i]; - - unchecked { ++i; } - } - - emit DecreaseLPsAllowance( - msg.sender, - spender_, - indexes_, - amounts_ - ); - } - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - decrement LPs allowances - * @dev emit events: - * - RevokeLPsAllowance - */ - function revokeLPsAllowance( - mapping(uint256 => uint256) storage allowances_, - address spender_, - uint256[] calldata indexes_ - ) external { - uint256 indexesLength = indexes_.length; - uint256 index; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - allowances_[index] = 0; - - unchecked { ++i; } - } - - emit RevokeLPsAllowance( - msg.sender, - spender_, - indexes_ - ); - } - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - approvedTransferors mapping - * @dev emit events: - * - ApproveLPsTransferors - */ - function approveLPsTransferors( - mapping(address => bool) storage allowances_, - address[] calldata transferors_ - ) external { - uint256 transferorsLength = transferors_.length; - for (uint256 i = 0; i < transferorsLength; ) { - allowances_[transferors_[i]] = true; - - unchecked { ++i; } - } - - emit ApproveLPsTransferors( - msg.sender, - transferors_ - ); - } - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - approvedTransferors mapping - * @dev emit events: - * - RevokeLPsTransferors - */ - function revokeLPsTransferors( - mapping(address => bool) storage allowances_, - address[] calldata transferors_ - ) external { - uint256 transferorsLength = transferors_.length; - for (uint256 i = 0; i < transferorsLength; ) { - delete allowances_[transferors_[i]]; - - unchecked { ++i; } - } - - emit RevokeLPsTransferors( - msg.sender, - transferors_ - ); - } - - /** - * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - delete allowance mapping - * - increment new lender.lps accumulator and lender.depositTime state - * - delete old lender from bucket -> lender mapping - * @dev reverts on: - * - invalid index InvalidIndex() - * - no allowance NoAllowance() - * @dev emit events: - * - TransferLPs - */ - function transferLPs( - mapping(uint256 => Bucket) storage buckets_, - mapping(address => mapping(address => mapping(uint256 => uint256))) storage allowances_, - mapping(address => mapping(address => bool)) storage approvedTransferors_, - address ownerAddress_, - address newOwnerAddress_, - uint256[] calldata indexes_ - ) external { - // revert if msg.sender is not the new owner and is not approved as a transferor by the new owner - if (newOwnerAddress_ != msg.sender && !approvedTransferors_[newOwnerAddress_][msg.sender]) revert TransferorNotApproved(); - - // revert if new owner address is the same as old owner address - if (ownerAddress_ == newOwnerAddress_) revert TransferToSameOwner(); - - uint256 indexesLength = indexes_.length; - uint256 index; - uint256 lpsTransferred; - - for (uint256 i = 0; i < indexesLength; ) { - index = indexes_[i]; - - // revert if invalid index - if (index > MAX_FENWICK_INDEX) revert InvalidIndex(); - - Bucket storage bucket = buckets_[index]; - Lender storage owner = bucket.lenders[ownerAddress_]; - - uint256 bankruptcyTime = bucket.bankruptcyTime; - uint256 ownerDepositTime = owner.depositTime; - uint256 ownerLpBalance = bankruptcyTime < ownerDepositTime ? owner.lps : 0; - - uint256 allowedAmount = allowances_[ownerAddress_][newOwnerAddress_][index]; - if (allowedAmount == 0) revert NoAllowance(); - - // transfer allowed amount or entire LP balance - allowedAmount = Maths.min(allowedAmount, ownerLpBalance); - - // move owner lps (if any) to the new owner - if (allowedAmount != 0) { - Lender storage newOwner = bucket.lenders[newOwnerAddress_]; - - uint256 newOwnerDepositTime = newOwner.depositTime; - - if (newOwnerDepositTime > bankruptcyTime) { - // deposit happened in a healthy bucket, add amount of LPs to new owner - newOwner.lps += allowedAmount; - } else { - // bucket bankruptcy happened after deposit, reset balance and add amount of LPs to new owner - newOwner.lps = allowedAmount; - } - - owner.lps -= allowedAmount; // remove amount of LPs from old owner - lpsTransferred += allowedAmount; // add amount of LPs to total LPs transferred - - // set the deposit time as the max of transferred deposit and current deposit time - newOwner.depositTime = Maths.max(ownerDepositTime, newOwnerDepositTime); - } - - // reset allowances of transferred LPs - delete allowances_[ownerAddress_][newOwnerAddress_][index]; - - unchecked { ++i; } - } - - emit TransferLPs(ownerAddress_, newOwnerAddress_, indexes_, lpsTransferred); - } - /**************************/ /*** Internal Functions ***/ /**************************/ @@ -849,7 +631,7 @@ library LenderActions { collateralAmount_ = Maths.min(maxAmount_, bucketCollateral); // determine how much LP would be required to remove the requested amount - uint256 requiredLPs = Buckets.collateralToLPs( + uint256 requiredLPs = Buckets.collateralToLP( bucketCollateral, bucketLPs, bucketDeposit, @@ -865,7 +647,7 @@ library LenderActions { lpAmount_ = lenderLpBalance; collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLpBalance, collateralAmount_), requiredLPs); - if (collateralAmount_ == 0) revert InsufficientLPs(); + if (collateralAmount_ == 0) revert InsufficientLP(); } // update bucket LPs and collateral balance @@ -879,26 +661,30 @@ library LenderActions { bucketCollateral -= Maths.min(bucketCollateral, collateralAmount_); bucket.collateral = bucketCollateral; + // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { - emit BucketBankruptcy(index_, bucketLPs); bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + index_, + bucketLPs + ); } else { - // update lender LPs balance + // update lender LP balance lender.lps -= lpAmount_; bucket.lps = bucketLPs; } } - /** - * @notice Removes the amount of quote tokens calculated for the given amount of LPs. + * @notice Removes the amount of quote tokens calculated for the given amount of LP. * @dev write state: * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): * - update values array state * @return removedAmount_ Amount of scaled deposit removed. - * @return redeemedLPs_ Amount of bucket LPs corresponding for calculated scaled deposit amount. + * @return redeemedLPs_ Amount of bucket LP corresponding for calculated scaled deposit amount. * @return unscaledRemaining_ Amount of unscaled deposit remaining. */ function _removeMaxDeposit( @@ -954,15 +740,4 @@ library LenderActions { Deposits.unscaledRemove(deposits_, params_.index, unscaledRemovedAmount); // update FenwickTree } - - /**********************/ - /*** View Functions ***/ - /**********************/ - - function _lup( - DepositsState storage deposits_, - uint256 debt_ - ) internal view returns (uint256) { - return _priceAt(Deposits.findIndexOfSum(deposits_, debt_)); - } } diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index d096fb1ea..95ea4e4e7 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -165,7 +165,10 @@ library PoolCommons { interestParams_.interestRate = uint208(0.1 * 1e18); interestParams_.interestRateUpdate = uint48(block.timestamp); - emit ResetInterestRate(poolState_.rate, 0.1 * 1e18); + emit ResetInterestRate( + poolState_.rate, + 0.1 * 1e18 + ); } // otherwise calculate and update interest rate if it has been more than 12 hours since the last update else if (block.timestamp - interestParams_.interestRateUpdate > 12 hours) { @@ -181,7 +184,10 @@ library PoolCommons { interestParams_.interestRate = uint208(vars.newInterestRate); interestParams_.interestRateUpdate = uint48(block.timestamp); - emit UpdateInterestRate(poolState_.rate, vars.newInterestRate); + emit UpdateInterestRate( + poolState_.rate, + vars.newInterestRate + ); } } diff --git a/src/libraries/external/PositionNFTSVG.sol b/src/libraries/external/PositionNFTSVG.sol index 971453ee6..f0a5e6270 100644 --- a/src/libraries/external/PositionNFTSVG.sol +++ b/src/libraries/external/PositionNFTSVG.sol @@ -23,7 +23,7 @@ library PositionNFTSVG { uint256 tokenId; // the ID of positions NFT token address pool; // the address of pool tracked in positions NFT token address owner; // the owner of positions NFT token - uint256[] indexes; // the array of price buckets index with LPs to be tracked by the NFT + uint256[] indexes; // the array of price buckets index with LP to be tracked by the NFT } /*******************************/ diff --git a/src/libraries/external/SettlerActions.sol b/src/libraries/external/SettlerActions.sol new file mode 100644 index 000000000..3e0a9852c --- /dev/null +++ b/src/libraries/external/SettlerActions.sol @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.14; + +import { PoolType } from '../../interfaces/pool/IPool.sol'; + +import { + AuctionsState, + Borrower, + Bucket, + DepositsState, + Kicker, + Liquidation, + LoansState, + PoolState, + ReserveAuctionState +} from '../../interfaces/pool/commons/IPoolState.sol'; +import { + SettleParams, + SettleResult +} from '../../interfaces/pool/commons/IPoolInternals.sol'; + +import { + _auctionPrice, + _indexOf, + _priceAt, + MAX_FENWICK_INDEX, + MIN_PRICE +} from '../helpers/PoolHelper.sol'; + +import { Buckets } from '../internal/Buckets.sol'; +import { Deposits } from '../internal/Deposits.sol'; +import { Loans } from '../internal/Loans.sol'; +import { Maths } from '../internal/Maths.sol'; + +/** + @title Auction settler library + @notice External library containing actions involving auctions within pool: + - settle auctions + */ +library SettlerActions { + + /*************************/ + /*** Local Var Structs ***/ + /*************************/ + + struct SettleLocalVars { + uint256 collateralUsed; // [WAD] collateral used to settle debt + uint256 debt; // [WAD] debt to settle + uint256 depositToRemove; // [WAD] deposit used by settle auction + uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket + uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle + uint256 hpbLPs; // [WAD] amount of LP in HPB bucket + uint256 index; // index of settling bucket + uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral + uint256 price; // [WAD] price of settling bucket + uint256 scaledDeposit; // [WAD] scaled amount of quote tokens in bucket + uint256 scale; // [WAD] scale of settling bucket + uint256 unscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket + } + + /**************/ + /*** Events ***/ + /**************/ + + // See `IPoolEvents` for descriptions + event AuctionSettle(address indexed borrower, uint256 collateral); + event AuctionNFTSettle(address indexed borrower, uint256 collateral, uint256 lps, uint256 index); + event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); + event Settle(address indexed borrower, uint256 settledDebt); + + /**************/ + /*** Errors ***/ + /**************/ + + // See `IPoolErrors` for descriptions + error AuctionNotClearable(); + error NoAuction(); + + /***************************/ + /*** External Functions ***/ + /***************************/ + + /** + * @notice Settles the debt of the given loan / borrower. + * @dev write state: + * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): + * - update values array state + * - Buckets.addCollateral: + * - increment bucket.collateral and bucket.lps accumulator + * - addLenderLP: + * - increment lender.lps accumulator and lender.depositTime state + * - update borrower state + * @dev reverts on: + * - loan is not in auction NoAuction() + * - 72 hours didn't pass and auction still has collateral AuctionNotClearable() + * @dev emit events: + * - Settle + * - BucketBankruptcy + * @param params_ Settle params + * @return result_ The result of settle action. + */ + function settlePoolDebt( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + LoansState storage loans_, + ReserveAuctionState storage reserveAuction_, + PoolState calldata poolState_, + SettleParams memory params_ + ) external returns (SettleResult memory result_) { + uint256 kickTime = auctions_.liquidations[params_.borrower].kickTime; + if (kickTime == 0) revert NoAuction(); + + Borrower memory borrower = loans_.borrowers[params_.borrower]; + if ((block.timestamp - kickTime < 72 hours) && (borrower.collateral != 0)) revert AuctionNotClearable(); + + result_.debtPreAction = borrower.t0Debt; + result_.collateralPreAction = borrower.collateral; + result_.t0DebtSettled = borrower.t0Debt; + result_.collateralSettled = borrower.collateral; + + // auction has debt to cover with remaining collateral + while (params_.bucketDepth != 0 && borrower.t0Debt != 0 && borrower.collateral != 0) { + SettleLocalVars memory vars; + + (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); + vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); + vars.unscaledDeposit = vars.hpbUnscaledDeposit; + vars.price = _priceAt(vars.index); + + if (vars.unscaledDeposit != 0) { + vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled + vars.maxSettleableDebt = Maths.floorWmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral + vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); + + // enough deposit in bucket and collateral avail to settle entire debt + if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { + // remove only what's needed to settle the debt + vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); + vars.collateralUsed = Maths.wdiv(vars.debt, vars.price); + + // settle the entire debt + borrower.t0Debt = 0; + } + // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount + else if (vars.maxSettleableDebt >= vars.scaledDeposit) { + vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); + + // subtract from debt the corresponding t0 amount of deposit + borrower.t0Debt -= Maths.floorWdiv(vars.scaledDeposit, poolState_.inflator); + } + // settle constrained by collateral available + else { + vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); + vars.collateralUsed = borrower.collateral; + + borrower.t0Debt -= Maths.floorWdiv(vars.maxSettleableDebt, poolState_.inflator); + } + + // remove settled collateral from loan + borrower.collateral -= vars.collateralUsed; + + Bucket storage hpb = buckets_[vars.index]; + vars.hpbLPs = hpb.lps; + vars.hpbCollateral = hpb.collateral + vars.collateralUsed; + + // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) + vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); + vars.hpbUnscaledDeposit -= vars.unscaledDeposit; + + // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) + Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); + + // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs + if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { + hpb.lps = 0; + hpb.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + vars.index, + vars.hpbLPs + ); + } else { + // add settled collateral into bucket + hpb.collateral = vars.hpbCollateral; + } + + } else { + // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 + Buckets.addCollateral( + buckets_[vars.index], + params_.borrower, + 0, // zero deposit in bucket + borrower.collateral, + vars.price + ); + borrower.collateral = 0; // entire collateral added into bucket + } + + --params_.bucketDepth; + } + + // if there's still debt and no collateral + if (borrower.t0Debt != 0 && borrower.collateral == 0) { + + uint256 assets = Maths.wmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; + uint256 liabilities = Deposits.treeSum(deposits_) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; + uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; + + // settle debt from reserves -- round reserves down however + borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(reserves, poolState_.inflator)); + + // if there's still debt after settling from reserves then start to forgive amount from next HPB + // loop through remaining buckets if there's still debt to settle + while (params_.bucketDepth != 0 && borrower.t0Debt != 0) { + SettleLocalVars memory vars; + + (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); + vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); + vars.depositToRemove = Maths.wmul(vars.scale, vars.unscaledDeposit); + vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); + + // enough deposit in bucket to settle entire debt + if (vars.depositToRemove >= vars.debt) { + Deposits.unscaledRemove(deposits_, vars.index, Maths.wdiv(vars.debt, vars.scale)); + borrower.t0Debt = 0; // no remaining debt to settle + + // not enough deposit to settle entire debt, we settle only deposit amount + } else { + borrower.t0Debt -= Maths.floorWdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit + + Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket + Bucket storage hpbBucket = buckets_[vars.index]; + + if (hpbBucket.collateral == 0) { // existing LP for the bucket shall become unclaimable. + hpbBucket.lps = 0; + hpbBucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + vars.index, + hpbBucket.lps + ); + } + } + + --params_.bucketDepth; + } + } + + result_.t0DebtSettled -= borrower.t0Debt; + + emit Settle( + params_.borrower, + result_.t0DebtSettled + ); + + if (borrower.t0Debt == 0) { + // settle auction + (borrower.collateral, ) = _settleAuction( + auctions_, + buckets_, + deposits_, + params_.borrower, + borrower.collateral, + poolState_.poolType + ); + } + + result_.debtPostAction = borrower.t0Debt; + result_.collateralRemaining = borrower.collateral; + result_.collateralSettled -= result_.collateralRemaining; + + // update borrower state + loans_.borrowers[params_.borrower] = borrower; + } + + /***************************/ + /*** Internal Functions ***/ + /***************************/ + + /** + * @notice Performs auction settle based on pool type, emits settle event and removes auction from auctions queue. + * @dev emit events: + * - AuctionNFTSettle or AuctionSettle + * @param borrowerAddress_ Address of the borrower that exits auction. + * @param borrowerCollateral_ Borrower collateral amount before auction exit (in NFT could be fragmented as result of partial takes). + * @param poolType_ Type of the pool (can be ERC20 or NFT). + * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for ERC20 pool, rounded collateral for NFT pool). + * @return compensatedCollateral_ Amount of collateral compensated (NFT settle only), to be deducted from pool pledged collateral accumulator. 0 for ERC20 pools. + */ + function _settleAuction( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + address borrowerAddress_, + uint256 borrowerCollateral_, + uint256 poolType_ + ) internal returns (uint256 remainingCollateral_, uint256 compensatedCollateral_) { + + if (poolType_ == uint8(PoolType.ERC721)) { + uint256 lps; + uint256 bucketIndex; + + remainingCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // floor collateral of borrower + + // if there's fraction of NFTs remaining then reward difference to borrower as LP in auction price bucket + if (remainingCollateral_ != borrowerCollateral_) { + + // calculate the amount of collateral that should be compensated with LP + compensatedCollateral_ = borrowerCollateral_ - remainingCollateral_; + + uint256 auctionPrice = _auctionPrice( + auctions_.liquidations[borrowerAddress_].kickMomp, + auctions_.liquidations[borrowerAddress_].neutralPrice, + auctions_.liquidations[borrowerAddress_].kickTime + ); + + // determine the bucket index to compensate fractional collateral + bucketIndex = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; + + // deposit collateral in bucket and reward LP to compensate fractional collateral + lps = Buckets.addCollateral( + buckets_[bucketIndex], + borrowerAddress_, + Deposits.valueAt(deposits_, bucketIndex), + compensatedCollateral_, + _priceAt(bucketIndex) + ); + } + + emit AuctionNFTSettle( + borrowerAddress_, + remainingCollateral_, + lps, + bucketIndex + ); + + } else { + remainingCollateral_ = borrowerCollateral_; + + emit AuctionSettle( + borrowerAddress_, + remainingCollateral_ + ); + } + + _removeAuction(auctions_, borrowerAddress_); + } + + /** + * @notice Removes auction and repairs the queue order. + * @notice Updates kicker's claimable balance with bond size awarded and subtracts bond size awarded from liquidationBondEscrowed. + * @dev write state: + * - decrement kicker locked accumulator, increment kicker claimable accumumlator + * - decrement auctions count accumulator + * - update auction queue state + * @param borrower_ Auctioned borrower address. + */ + function _removeAuction( + AuctionsState storage auctions_, + address borrower_ + ) internal { + Liquidation memory liquidation = auctions_.liquidations[borrower_]; + // update kicker balances + Kicker storage kicker = auctions_.kickers[liquidation.kicker]; + + kicker.locked -= liquidation.bondSize; + kicker.claimable += liquidation.bondSize; + + // decrement number of active auctions + -- auctions_.noOfAuctions; + + // update auctions queue + if (auctions_.head == borrower_ && auctions_.tail == borrower_) { + // liquidation is the head and tail + auctions_.head = address(0); + auctions_.tail = address(0); + } + else if(auctions_.head == borrower_) { + // liquidation is the head + auctions_.liquidations[liquidation.next].prev = address(0); + auctions_.head = liquidation.next; + } + else if(auctions_.tail == borrower_) { + // liquidation is the tail + auctions_.liquidations[liquidation.prev].next = address(0); + auctions_.tail = liquidation.prev; + } + else { + // liquidation is in the middle + auctions_.liquidations[liquidation.prev].next = liquidation.next; + auctions_.liquidations[liquidation.next].prev = liquidation.prev; + } + // delete liquidation + delete auctions_.liquidations[borrower_]; + } + +} diff --git a/src/libraries/external/TakerActions.sol b/src/libraries/external/TakerActions.sol new file mode 100644 index 000000000..e0cbf6662 --- /dev/null +++ b/src/libraries/external/TakerActions.sol @@ -0,0 +1,762 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.14; + +import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; + +import { PoolType } from '../../interfaces/pool/IPool.sol'; + +import { + AuctionsState, + Borrower, + Bucket, + BurnEvent, + DepositsState, + Liquidation, + LoansState, + PoolState, + ReserveAuctionState +} from '../../interfaces/pool/commons/IPoolState.sol'; +import { + BucketTakeResult, + TakeResult +} from '../../interfaces/pool/commons/IPoolInternals.sol'; + +import { + _auctionPrice, + _bpf, + _isCollateralized, + _priceAt, + _reserveAuctionPrice, + _roundToScale +} from '../helpers/PoolHelper.sol'; +import { _revertOnMinDebt } from '../helpers/RevertsHelper.sol'; + +import { SettlerActions } from './SettlerActions.sol'; + +import { Buckets } from '../internal/Buckets.sol'; +import { Deposits } from '../internal/Deposits.sol'; +import { Loans } from '../internal/Loans.sol'; +import { Maths } from '../internal/Maths.sol'; + +/** + @title Auction Taker Actions library + @notice External library containing actions involving taking auctions within pool: + - take auctioned collateral; take reserves + */ +library TakerActions { + + /*******************************/ + /*** Function Params Structs ***/ + /*******************************/ + + struct BucketTakeParams { + address borrower; // borrower address to take from + bool depositTake; // deposit or arb take, used by bucket take + uint256 index; // bucket index, used by bucket take + uint256 inflator; // [WAD] current pool inflator + uint256 collateralScale; // precision of collateral token based on decimals + } + struct TakeParams { + address borrower; // borrower address to take from + uint256 takeCollateral; // [WAD] desired amount to take + uint256 inflator; // [WAD] current pool inflator + uint256 poolType; // pool type (ERC20 or NFT) + uint256 collateralScale; // precision of collateral token based on decimals + } + + /*************************/ + /*** Local Var Structs ***/ + /*************************/ + + struct TakeLocalVars { + uint256 auctionPrice; // [WAD] The price of auction. + uint256 bondChange; // [WAD] The change made on the bond size (beeing reward or penalty). + uint256 borrowerDebt; // [WAD] The accrued debt of auctioned borrower. + int256 bpf; // The bond penalty factor. + uint256 bucketPrice; // [WAD] The bucket price. + uint256 bucketScale; // [WAD] The bucket scale. + uint256 collateralAmount; // [WAD] The amount of collateral taken. + uint256 excessQuoteToken; // [WAD] Difference of quote token that borrower receives after take (for fractional NFT only) + uint256 factor; // The take factor, calculated based on bond penalty factor. + bool isRewarded; // True if kicker is rewarded (auction price lower than neutral price), false if penalized (auction price greater than neutral price). + address kicker; // Address of auction kicker. + uint256 quoteTokenAmount; // [WAD] Scaled quantity in Fenwick tree and before 1-bpf factor, paid for collateral + uint256 t0RepayAmount; // [WAD] The amount of debt (quote tokens) that is recovered / repayed by take t0 terms. + uint256 t0BorrowerDebt; // [WAD] Borrower's t0 debt. + uint256 t0DebtPenalty; // [WAD] Borrower's t0 penalty - 7% from current debt if intial take, 0 otherwise. + uint256 unscaledDeposit; // [WAD] Unscaled bucket quantity + uint256 unscaledQuoteTokenAmount; // [WAD] The unscaled token amount that taker should pay for collateral taken. + } + + /**************/ + /*** Events ***/ + /**************/ + + // See `IPoolEvents` for descriptions + event BucketTake(address indexed borrower, uint256 index, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); + event BucketTakeLPAwarded(address indexed taker, address indexed kicker, uint256 lpAwardedTaker, uint256 lpAwardedKicker); + event Take(address indexed borrower, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); + event ReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); + + /**************/ + /*** Errors ***/ + /**************/ + + // See `IPoolErrors` for descriptions + error AuctionPriceGtBucketPrice(); + error CollateralRoundingNeededButNotPossible(); + error InsufficientLiquidity(); + error InsufficientCollateral(); + error InvalidAmount(); + error NoAuction(); + error NoReserves(); + error NoReservesAuction(); + error ReserveAuctionTooSoon(); + error TakeNotPastCooldown(); + + /***************************/ + /*** External Functions ***/ + /***************************/ + + /** + * @notice Performs bucket take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). + * @dev reverts on: + * - insufficient collateral InsufficientCollateral() + * @param borrowerAddress_ Borrower address to take. + * @param depositTake_ If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. + * @param index_ Index of a bucket, likely the HPB, in which collateral will be deposited. + * @return result_ BucketTakeResult struct containing details of take. + */ + function bucketTake( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + LoansState storage loans_, + PoolState memory poolState_, + address borrowerAddress_, + bool depositTake_, + uint256 index_, + uint256 collateralScale_ + ) external returns (BucketTakeResult memory result_) { + Borrower memory borrower = loans_.borrowers[borrowerAddress_]; + + if (borrower.collateral == 0) revert InsufficientCollateral(); // revert if borrower's collateral is 0 + + result_.debtPreAction = borrower.t0Debt; + result_.collateralPreAction = borrower.collateral; + + // bucket take auction + TakeLocalVars memory vars = _takeBucket( + auctions_, + buckets_, + deposits_, + borrower, + BucketTakeParams({ + borrower: borrowerAddress_, + inflator: poolState_.inflator, + depositTake: depositTake_, + index: index_, + collateralScale: collateralScale_ + }) + ); + + // update borrower after take + borrower.collateral -= vars.collateralAmount; + borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; + // update pool params after take + poolState_.t0Debt += vars.t0DebtPenalty; + poolState_.t0Debt -= vars.t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + + // update loan after take + ( + result_.newLup, + result_.settledAuction, + result_.remainingCollateral, + result_.compensatedCollateral + ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); + + // complete take result struct + result_.debtPostAction = borrower.t0Debt; + result_.collateralPostAction = borrower.collateral; + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; + result_.collateralAmount = vars.collateralAmount; + result_.t0DebtPenalty = vars.t0DebtPenalty; + // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount + result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; + } + + /** + * @notice Performs take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). + * @dev reverts on: + * - insufficient collateral InsufficientCollateral() + * @param borrowerAddress_ Borrower address to take. + * @param collateral_ Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). + * @return result_ TakeResult struct containing details of take. + */ + function take( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + LoansState storage loans_, + PoolState memory poolState_, + address borrowerAddress_, + uint256 collateral_, + uint256 collateralScale_ + ) external returns (TakeResult memory result_) { + // revert if no amount to take + if (collateral_ == 0) revert InvalidAmount(); + + Borrower memory borrower = loans_.borrowers[borrowerAddress_]; + + if ( + (poolState_.poolType == uint8(PoolType.ERC721) && borrower.collateral < 1e18) || // revert in case of NFT take when there isn't a full token to be taken + (poolState_.poolType == uint8(PoolType.ERC20) && borrower.collateral == 0) // revert in case of ERC20 take when no collateral to be taken + ) { + revert InsufficientCollateral(); + } + + result_.debtPreAction = borrower.t0Debt; + result_.collateralPreAction = borrower.collateral; + + // take auction + TakeLocalVars memory vars = _take( + auctions_, + borrower, + TakeParams({ + borrower: borrowerAddress_, + takeCollateral: collateral_, + inflator: poolState_.inflator, + poolType: poolState_.poolType, + collateralScale: collateralScale_ + }) + ); + + // update borrower after take + borrower.collateral -= vars.collateralAmount; + borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; + // update pool params after take + poolState_.t0Debt += vars.t0DebtPenalty; + poolState_.t0Debt -= vars.t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + + // update loan after take + ( + result_.newLup, + result_.settledAuction, + result_.remainingCollateral, + result_.compensatedCollateral + ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); + + // complete take result struct + result_.debtPostAction = borrower.t0Debt; + result_.collateralPostAction = borrower.collateral; + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; + result_.collateralAmount = vars.collateralAmount; + result_.t0DebtPenalty = vars.t0DebtPenalty; + result_.quoteTokenAmount = vars.quoteTokenAmount; + result_.excessQuoteToken = vars.excessQuoteToken; + // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount + result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; + } + + /*************************/ + /*** Reserve Auction ***/ + /*************************/ + + /** + * @notice See `IPoolReserveAuctionActions` for descriptions. + * @dev write state: + * - decrement reserveAuction.unclaimed accumulator + * @dev reverts on: + * - not kicked or 72 hours didn't pass NoReservesAuction() + * @dev emit events: + * - ReserveAuction + */ + function takeReserves( + ReserveAuctionState storage reserveAuction_, + uint256 maxAmount_ + ) external returns (uint256 amount_, uint256 ajnaRequired_) { + // revert if no amount to be taken + if (maxAmount_ == 0) revert InvalidAmount(); + + uint256 kicked = reserveAuction_.kicked; + + if (kicked != 0 && block.timestamp - kicked <= 72 hours) { + uint256 unclaimed = reserveAuction_.unclaimed; + uint256 price = _reserveAuctionPrice(kicked); + + amount_ = Maths.min(unclaimed, maxAmount_); + ajnaRequired_ = Maths.wmul(amount_, price); + + unclaimed -= amount_; + + reserveAuction_.unclaimed = unclaimed; + + uint256 totalBurned = reserveAuction_.totalAjnaBurned + ajnaRequired_; + + // accumulate additional ajna burned + reserveAuction_.totalAjnaBurned = totalBurned; + + uint256 burnEventEpoch = reserveAuction_.latestBurnEventEpoch; + + // record burn event information to enable querying by staking rewards + BurnEvent storage burnEvent = reserveAuction_.burnEvents[burnEventEpoch]; + burnEvent.totalInterest = reserveAuction_.totalInterestEarned; + burnEvent.totalBurned = totalBurned; + + emit ReserveAuction( + unclaimed, + price, + burnEventEpoch + ); + } else { + revert NoReservesAuction(); + } + } + + /**************************/ + /*** Internal Functions ***/ + /**************************/ + + /** + * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. + * @dev emit events: + * - Take + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action params details. + * @return vars_ Struct containing auction take vars. + */ + function _take( + AuctionsState storage auctions_, + Borrower memory borrower_, + TakeParams memory params_ + ) internal returns (TakeLocalVars memory vars_) { + Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; + + vars_ = _prepareTake( + liquidation, + borrower_.t0Debt, + borrower_.collateral, + params_.inflator + ); + + // These are placeholder max values passed to calculateTakeFlows because there is no explicit bound on the + // quote token amount in take calls (as opposed to bucketTake) + vars_.unscaledDeposit = type(uint256).max; + vars_.bucketScale = Maths.WAD; + + uint256 takeableCollateral = borrower_.collateral; + // for NFT take make sure the take flow and bond change calculation happens for the rounded collateral that can be taken + if (params_.poolType == uint8(PoolType.ERC721)) { + takeableCollateral = (takeableCollateral / 1e18) * 1e18; + } + + // In the case of take, the taker binds the collateral qty but not the quote token qty + // ugly to get take work like a bucket take -- this is the max amount of quote token from the take that could go to + // reduce the debt of the borrower -- analagous to the amount of deposit in the bucket for a bucket take + vars_ = _calculateTakeFlowsAndBondChange( + Maths.min(takeableCollateral, params_.takeCollateral), + params_.inflator, + params_.collateralScale, + vars_ + ); + + _rewardTake(auctions_, liquidation, vars_); + + emit Take( + params_.borrower, + vars_.quoteTokenAmount, + vars_.collateralAmount, + vars_.bondChange, + vars_.isRewarded + ); + + if (params_.poolType == uint8(PoolType.ERC721)) { + // slither-disable-next-line divide-before-multiply + uint256 collateralTaken = (vars_.collateralAmount / 1e18) * 1e18; // solidity rounds down, so if 2.5 it will be 2.5 / 1 = 2 + + if (collateralTaken != vars_.collateralAmount) { // collateral taken not a round number + if (Maths.min(borrower_.collateral, params_.takeCollateral) >= collateralTaken + 1e18) { + collateralTaken += 1e18; // round up collateral to take + // taker should send additional quote tokens to cover difference between collateral needed to be taken and rounded collateral, at auction price + // borrower will get quote tokens for the difference between rounded collateral and collateral taken to cover debt + vars_.excessQuoteToken = Maths.wmul(collateralTaken - vars_.collateralAmount, vars_.auctionPrice); + vars_.collateralAmount = collateralTaken; + } else { + // shouldn't get here, but just in case revert + revert CollateralRoundingNeededButNotPossible(); + } + } + } + } + + /** + * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). + * @dev emit events: + * - BucketTake + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action details. + * @return vars_ Struct containing auction take vars. + */ + function _takeBucket( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + Borrower memory borrower_, + BucketTakeParams memory params_ + ) internal returns (TakeLocalVars memory vars_) { + Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; + + vars_= _prepareTake( + liquidation, + borrower_.t0Debt, + borrower_.collateral, + params_.inflator + ); + + vars_.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); + + // revert if no quote tokens in arbed bucket + if (vars_.unscaledDeposit == 0) revert InsufficientLiquidity(); + + vars_.bucketPrice = _priceAt(params_.index); + + // cannot arb with a price lower than the auction price + if (vars_.auctionPrice > vars_.bucketPrice) revert AuctionPriceGtBucketPrice(); + + // if deposit take then price to use when calculating take is bucket price + if (params_.depositTake) vars_.auctionPrice = vars_.bucketPrice; + + vars_.bucketScale = Deposits.scale(deposits_, params_.index); + + vars_ = _calculateTakeFlowsAndBondChange( + borrower_.collateral, + params_.inflator, + params_.collateralScale, + vars_ + ); + + // revert if bucket deposit cannot cover at least one unit of collateral + if (vars_.collateralAmount == 0) revert InsufficientLiquidity(); + + _rewardBucketTake( + auctions_, + deposits_, + buckets_, + liquidation, + params_.index, + params_.depositTake, + vars_ + ); + + emit BucketTake( + params_.borrower, + params_.index, + vars_.quoteTokenAmount, + vars_.collateralAmount, + vars_.bondChange, + vars_.isRewarded + ); + } + + /** + * @notice Performs update of an auctioned loan that was taken (using bucket or regular take). + * @notice If borrower becomes recollateralized then auction is settled. Update loan's state. + * @dev reverts on: + * - borrower debt less than pool min debt AmountLTMinDebt() + * @param borrower_ Struct containing pool details. + * @param borrower_ The borrower details owning loan that is taken. + * @param borrowerAddress_ The address of the borrower. + * @return newLup_ The new LUP of pool (after debt is repaid). + * @return settledAuction_ True if auction is settled by the take action. (NFT take: rebalance borrower collateral in pool if true) + * @return remainingCollateral_ Borrower collateral remaining after take action. (NFT take: collateral to be rebalanced in case of NFT settlement) + * @return compensatedCollateral_ Amount of collateral compensated, to be deducted from pool pledged collateral accumulator. + */ + function _takeLoan( + AuctionsState storage auctions_, + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + LoansState storage loans_, + PoolState memory poolState_, + Borrower memory borrower_, + address borrowerAddress_ + ) internal returns ( + uint256 newLup_, + bool settledAuction_, + uint256 remainingCollateral_, + uint256 compensatedCollateral_ + ) { + + uint256 borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); + + // check that taking from loan doesn't leave borrower debt under min debt amount + _revertOnMinDebt( + loans_, + poolState_.debt, + borrowerDebt, + poolState_.quoteDustLimit + ); + + // calculate new lup with repaid debt from take + newLup_ = Deposits.getLup(deposits_, poolState_.debt); + + remainingCollateral_ = borrower_.collateral; + + if (_isCollateralized(borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { + settledAuction_ = true; + + // settle auction and update borrower's collateral with value after settlement + (remainingCollateral_, compensatedCollateral_) = SettlerActions._settleAuction( + auctions_, + buckets_, + deposits_, + borrowerAddress_, + borrower_.collateral, + poolState_.poolType + ); + + borrower_.collateral = remainingCollateral_; + } + + // update loan state, stamp borrower t0Np only when exiting from auction + Loans.update( + loans_, + auctions_, + deposits_, + borrower_, + borrowerAddress_, + poolState_.debt, + poolState_.rate, + newLup_, + !settledAuction_, + settledAuction_ // stamp borrower t0Np if exiting from auction + ); + } + + /** + * @notice Rewards actors of a regular take action. + * @dev write state: + * - update liquidation bond size accumulator + * - update kicker's locked balance accumulator + * - update auctions.totalBondEscrowed accumulator + * @param vars Struct containing take action result details. + */ + function _rewardTake( + AuctionsState storage auctions_, + Liquidation storage liquidation_, + TakeLocalVars memory vars + ) internal { + if (vars.isRewarded) { + // take is below neutralPrice, Kicker is rewarded + liquidation_.bondSize += uint160(vars.bondChange); + auctions_.kickers[vars.kicker].locked += vars.bondChange; + auctions_.totalBondEscrowed += vars.bondChange; + } else { + // take is above neutralPrice, Kicker is penalized + vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); + + liquidation_.bondSize -= uint160(vars.bondChange); + auctions_.kickers[vars.kicker].locked -= vars.bondChange; + auctions_.totalBondEscrowed -= vars.bondChange; + } + } + + /** + * @notice Rewards actors of a bucket take action. + * @dev write state: + * - Buckets.addLenderLP: + * - increment taker lender.lps accumulator and lender.depositTime state + * - increment kicker lender.lps accumulator and lender.depositTime state + * - update liquidation bond size accumulator + * - update kicker's locked balance accumulator + * - update auctions.totalBondEscrowed accumulator + * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): + * - update values array state + * - increment bucket.collateral and bucket.lps accumulator + * @dev emit events: + * - BucketTakeLPAwarded + * @param vars Struct containing take action result details. + */ + function _rewardBucketTake( + AuctionsState storage auctions_, + DepositsState storage deposits_, + mapping(uint256 => Bucket) storage buckets_, + Liquidation storage liquidation_, + uint256 bucketIndex_, + bool depositTake_, + TakeLocalVars memory vars + ) internal { + Bucket storage bucket = buckets_[bucketIndex_]; + + uint256 scaledDeposit = Maths.wmul(vars.unscaledDeposit, vars.bucketScale); + + uint256 exchangeRate = Buckets.getExchangeRate( + bucket.collateral, + bucket.lps, + scaledDeposit, + vars.bucketPrice + ); + + uint256 bankruptcyTime = bucket.bankruptcyTime; + uint256 totalLPsReward; + + // if arb take - taker is awarded collateral * (bucket price - auction price) worth (in quote token terms) units of LPB in the bucket + if (!depositTake_) { + uint256 takerReward = Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice); + + totalLPsReward = Maths.wdiv(takerReward, exchangeRate); + + Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, totalLPsReward); + } + + uint256 kickerLPsReward; + + // the bondholder/kicker is awarded bond change worth of LPB in the bucket + if (vars.isRewarded) { + kickerLPsReward = Maths.wdiv(vars.bondChange, exchangeRate); + totalLPsReward += kickerLPsReward; + + Buckets.addLenderLP(bucket, bankruptcyTime, vars.kicker, kickerLPsReward); + } else { + // take is above neutralPrice, Kicker is penalized + vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); + + liquidation_.bondSize -= uint160(vars.bondChange); + + auctions_.kickers[vars.kicker].locked -= vars.bondChange; + auctions_.totalBondEscrowed -= vars.bondChange; + } + + Deposits.unscaledRemove(deposits_, bucketIndex_, vars.unscaledQuoteTokenAmount); // remove quote tokens from bucket’s deposit + + // total rewarded LP are added to the bucket LP balance + bucket.lps += totalLPsReward; + + // collateral is added to the bucket’s claimable collateral + bucket.collateral += vars.collateralAmount; + + emit BucketTakeLPAwarded( + msg.sender, + vars.kicker, + totalLPsReward - kickerLPsReward, + kickerLPsReward + ); + } + + /** + * @notice Utility function to validate take and calculate take's parameters. + * @dev write state: + * - update liquidation.alreadyTaken state + * @dev reverts on: + * - loan is not in auction NoAuction() + * - in 1 hour cool down period TakeNotPastCooldown() + * @param liquidation_ Liquidation struct holding auction details. + * @param t0Debt_ Borrower t0 debt. + * @param collateral_ Borrower collateral. + * @param inflator_ The pool's inflator, used to calculate borrower debt. + * @return vars The prepared vars for take action. + */ + function _prepareTake( + Liquidation storage liquidation_, + uint256 t0Debt_, + uint256 collateral_, + uint256 inflator_ + ) internal returns (TakeLocalVars memory vars) { + + uint256 kickTime = liquidation_.kickTime; + if (kickTime == 0) revert NoAuction(); + if (block.timestamp - kickTime <= 1 hours) revert TakeNotPastCooldown(); + + vars.t0BorrowerDebt = t0Debt_; + + // if first take borrower debt is increased by 7% penalty + if (!liquidation_.alreadyTaken) { + vars.t0DebtPenalty = Maths.wmul(t0Debt_, 0.07 * 1e18); + vars.t0BorrowerDebt += vars.t0DebtPenalty; + + liquidation_.alreadyTaken = true; + } + + vars.borrowerDebt = Maths.wmul(vars.t0BorrowerDebt, inflator_); + + uint256 neutralPrice = liquidation_.neutralPrice; + + vars.auctionPrice = _auctionPrice(liquidation_.kickMomp, neutralPrice, kickTime); + vars.bpf = _bpf( + vars.borrowerDebt, + collateral_, + neutralPrice, + liquidation_.bondFactor, + vars.auctionPrice + ); + vars.factor = uint256(1e18 - Maths.maxInt(0, vars.bpf)); + vars.kicker = liquidation_.kicker; + vars.isRewarded = (vars.bpf >= 0); + } + + /** + * @notice Computes the flows of collateral, quote token between the borrower, lender and kicker. + * @param totalCollateral_ Total collateral in loan. + * @param inflator_ Current pool inflator. + * @param vars TakeParams for the take/buckettake + */ + function _calculateTakeFlowsAndBondChange( + uint256 totalCollateral_, + uint256 inflator_, + uint256 collateralScale_, + TakeLocalVars memory vars + ) internal pure returns ( + TakeLocalVars memory + ) { + // price is the current auction price, which is the price paid by the LENDER for collateral + // from the borrower point of view, the price is actually (1-bpf) * price, as the rewards to the + // bond holder are effectively paid for by the borrower. + uint256 borrowerPayoffFactor = (vars.isRewarded) ? Maths.WAD - uint256(vars.bpf) : Maths.WAD; + uint256 borrowerPrice = (vars.isRewarded) ? Maths.wmul(borrowerPayoffFactor, vars.auctionPrice) : vars.auctionPrice; + + // If there is no unscaled quote token bound, then we pass in max, but that cannot be scaled without an overflow. So we check in the line below. + vars.quoteTokenAmount = (vars.unscaledDeposit != type(uint256).max) ? Maths.wmul(vars.unscaledDeposit, vars.bucketScale) : type(uint256).max; + + uint256 borrowerCollateralValue = Maths.wmul(totalCollateral_, borrowerPrice); + + if (vars.quoteTokenAmount <= vars.borrowerDebt && vars.quoteTokenAmount <= borrowerCollateralValue) { + // quote token used to purchase is constraining factor + vars.collateralAmount = _roundToScale(Maths.wdiv(vars.quoteTokenAmount, borrowerPrice), collateralScale_); + vars.t0RepayAmount = Maths.wdiv(vars.quoteTokenAmount, inflator_); + vars.unscaledQuoteTokenAmount = vars.unscaledDeposit; + + vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); + + } else if (vars.borrowerDebt <= borrowerCollateralValue) { + // borrower debt is constraining factor + vars.collateralAmount = _roundToScale(Maths.wdiv(vars.borrowerDebt, borrowerPrice), collateralScale_); + vars.t0RepayAmount = vars.t0BorrowerDebt; + vars.unscaledQuoteTokenAmount = Maths.wdiv(vars.borrowerDebt, vars.bucketScale); + + vars.quoteTokenAmount = (vars.isRewarded) ? Maths.wdiv(vars.borrowerDebt, borrowerPayoffFactor) : vars.borrowerDebt; + + } else { + // collateral available is constraint + vars.collateralAmount = totalCollateral_; + vars.t0RepayAmount = Maths.wdiv(borrowerCollateralValue, inflator_); + vars.unscaledQuoteTokenAmount = Maths.wdiv(borrowerCollateralValue, vars.bucketScale); + + vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); + } + + if (vars.isRewarded) { + // take is below neutralPrice, Kicker is rewarded + vars.bondChange = Maths.wmul(vars.quoteTokenAmount, uint256(vars.bpf)); + } else { + // take is above neutralPrice, Kicker is penalized + vars.bondChange = Maths.wmul(vars.quoteTokenAmount, uint256(-vars.bpf)); + } + + return vars; + } + +} diff --git a/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index 27d708e35..3e0285e1e 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -186,15 +186,15 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Returns the amount of collateral calculated for the given amount of LPs. + * @notice Returns the amount of collateral calculated for the given amount of LP. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LPs in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LPs. - * @param lenderLPsBalance_ The amount of LPs to calculate collateral for. + * @param bucketLPs_ Amount of LP in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. + * @param lenderLPsBalance_ The amount of LP to calculate collateral for. * @param bucketPrice_ Bucket price. - * @return collateralAmount_ Amount of collateral calculated for the given LPs amount. + * @return collateralAmount_ Amount of collateral calculated for the given LP amount. */ - function _lpsToCollateral( + function _lpToCollateral( uint256 bucketCollateral_, uint256 bucketLPs_, uint256 deposit_, @@ -213,16 +213,16 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Returns the amount of quote tokens calculated for the given amount of LPs. - * @param bucketLPs_ Amount of LPs in bucket. + * @notice Returns the amount of quote tokens calculated for the given amount of LP. + * @param bucketLPs_ Amount of LP in bucket. * @param bucketCollateral_ Amount of collateral in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LPs. - * @param lenderLPsBalance_ The amount of LPs to calculate quote token amount for. - * @param maxQuoteToken_ The max quote token amount to calculate LPs for. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. + * @param lenderLPsBalance_ The amount of LP to calculate quote token amount for. + * @param maxQuoteToken_ The max quote token amount to calculate LP for. * @param bucketPrice_ Bucket price. - * @return quoteTokenAmount_ Amount of quote tokens calculated for the given LPs amount. + * @return quoteTokenAmount_ Amount of quote tokens calculated for the given LP amount. */ - function _lpsToQuoteToken( + function _lpToQuoteToken( uint256 bucketLPs_, uint256 bucketCollateral_, uint256 deposit_, @@ -295,4 +295,100 @@ import { Maths } from '../internal/Maths.sol'; _price = Maths.rayToWad(1_000_000_000 * Maths.rmul(hoursComponent, minutesComponent)); } + } + + /*************************/ + /*** Auction Utilities ***/ + /*************************/ + + /** + * @notice Calculates auction price. + * @param kickMomp_ MOMP recorded at the time of kick. + * @param neutralPrice_ Neutral Price of the auction. + * @param kickTime_ Time when auction was kicked. + * @return price_ Calculated auction price. + */ + function _auctionPrice( + uint256 kickMomp_, + uint256 neutralPrice_, + uint256 kickTime_ + ) view returns (uint256 price_) { + uint256 elapsedHours = Maths.wdiv((block.timestamp - kickTime_) * 1e18, 1 hours * 1e18); + + elapsedHours -= Maths.min(elapsedHours, 1e18); // price locked during cure period + + int256 timeAdjustment = PRBMathSD59x18.mul(-1 * 1e18, int256(elapsedHours)); + uint256 referencePrice = Maths.max(kickMomp_, neutralPrice_); + + price_ = 32 * Maths.wmul(referencePrice, uint256(PRBMathSD59x18.exp2(timeAdjustment))); + } + + /** + * @notice Calculates bond penalty factor. + * @dev Called in kick and take. + * @param debt_ Borrower debt. + * @param collateral_ Borrower collateral. + * @param neutralPrice_ NP of auction. + * @param bondFactor_ Factor used to determine bondSize. + * @param auctionPrice_ Auction price at the time of call. + * @return bpf_ Factor used in determining bond Reward (positive) or penalty (negative). + */ + function _bpf( + uint256 debt_, + uint256 collateral_, + uint256 neutralPrice_, + uint256 bondFactor_, + uint256 auctionPrice_ + ) pure returns (int256) { + int256 thresholdPrice = int256(Maths.wdiv(debt_, collateral_)); + + int256 sign; + if (thresholdPrice < int256(neutralPrice_)) { + // BPF = BondFactor * min(1, max(-1, (neutralPrice - price) / (neutralPrice - thresholdPrice))) + sign = Maths.minInt( + 1e18, + Maths.maxInt( + -1 * 1e18, + PRBMathSD59x18.div( + int256(neutralPrice_) - int256(auctionPrice_), + int256(neutralPrice_) - thresholdPrice + ) + ) + ); + } else { + int256 val = int256(neutralPrice_) - int256(auctionPrice_); + if (val < 0 ) sign = -1e18; + else if (val != 0) sign = 1e18; + } + + return PRBMathSD59x18.mul(int256(bondFactor_), sign); + } + + /** + * @notice Calculates bond parameters of an auction. + * @param borrowerDebt_ Borrower's debt before entering in liquidation. + * @param collateral_ Borrower's collateral before entering in liquidation. + * @param momp_ Current pool momp. + */ + function _bondParams( + uint256 borrowerDebt_, + uint256 collateral_, + uint256 momp_ + ) pure returns (uint256 bondFactor_, uint256 bondSize_) { + uint256 thresholdPrice = borrowerDebt_ * Maths.WAD / collateral_; + + // bondFactor = min(30%, max(1%, (MOMP - thresholdPrice) / MOMP)) + if (thresholdPrice >= momp_) { + bondFactor_ = 0.01 * 1e18; + } else { + bondFactor_ = Maths.min( + 0.3 * 1e18, + Maths.max( + 0.01 * 1e18, + 1e18 - Maths.wdiv(thresholdPrice, momp_) + ) + ); + } + + bondSize_ = Maths.wmul(bondFactor_, borrowerDebt_); } \ No newline at end of file diff --git a/src/libraries/internal/Buckets.sol b/src/libraries/internal/Buckets.sol index 044a88c68..e374aa4c8 100644 --- a/src/libraries/internal/Buckets.sol +++ b/src/libraries/internal/Buckets.sol @@ -24,15 +24,15 @@ library Buckets { /***********************************/ /** - * @notice Add collateral to a bucket and updates LPs for bucket and lender with the amount coresponding to collateral amount added. + * @notice Add collateral to a bucket and updates LP for bucket and lender with the amount coresponding to collateral amount added. * @dev Increment bucket.collateral and bucket.lps accumulator - * - addLenderLPs: + * - addLenderLP: * - increment lender.lps accumulator and lender.depositTime state * @param lender_ Address of the lender. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LPs + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP * @param collateralAmountToAdd_ Additional collateral amount to add to bucket. * @param bucketPrice_ Bucket price. - * @return addedLPs_ Amount of bucket LPs for the collateral amount added. + * @return addedLPs_ Amount of bucket LP for the collateral amount added. */ function addCollateral( Bucket storage bucket_, @@ -45,33 +45,33 @@ library Buckets { uint256 bankruptcyTime = bucket_.bankruptcyTime; if (bankruptcyTime == block.timestamp) revert BucketBankruptcyBlock(); - // calculate amount of LPs to be added for the amount of collateral added to bucket - addedLPs_ = collateralToLPs( + // calculate amount of LP to be added for the amount of collateral added to bucket + addedLPs_ = collateralToLP( bucket_.collateral, bucket_.lps, deposit_, collateralAmountToAdd_, bucketPrice_ ); - // update bucket LPs balance and collateral + // update bucket LP balance and collateral // update bucket collateral bucket_.collateral += collateralAmountToAdd_; - // update bucket and lender LPs balance and deposit timestamp + // update bucket and lender LP balance and deposit timestamp bucket_.lps += addedLPs_; - addLenderLPs(bucket_, bankruptcyTime, lender_, addedLPs_); + addLenderLP(bucket_, bankruptcyTime, lender_, addedLPs_); } /** - * @notice Add amount of LPs for a given lender in a given bucket. + * @notice Add amount of LP for a given lender in a given bucket. * @dev Increments lender lps accumulator and updates the deposit time. - * @param bucket_ Bucket to record lender LPs. + * @param bucket_ Bucket to record lender LP. * @param bankruptcyTime_ Time when bucket become insolvent. - * @param lender_ Lender address to add LPs for in the given bucket. - * @param lpsAmount_ Amount of LPs to be recorded for the given lender. + * @param lender_ Lender address to add LP for in the given bucket. + * @param lpsAmount_ Amount of LP to be recorded for the given lender. */ - function addLenderLPs( + function addLenderLP( Bucket storage bucket_, uint256 bankruptcyTime_, address lender_, @@ -92,15 +92,15 @@ library Buckets { /**********************/ /** - * @notice Returns the amount of bucket LPs calculated for the given amount of collateral. + * @notice Returns the amount of bucket LP calculated for the given amount of collateral. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LPs in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LPs. - * @param collateral_ The amount of collateral to calculate bucket LPs for. + * @param bucketLPs_ Amount of LP in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. + * @param collateral_ The amount of collateral to calculate bucket LP for. * @param bucketPrice_ Price bucket. - * @return lps_ Amount of LPs calculated for the amount of collateral. + * @return lps_ Amount of LP calculated for the amount of collateral. */ - function collateralToLPs( + function collateralToLP( uint256 bucketCollateral_, uint256 bucketLPs_, uint256 deposit_, @@ -113,15 +113,15 @@ library Buckets { } /** - * @notice Returns the amount of LPs calculated for the given amount of quote tokens. + * @notice Returns the amount of LP calculated for the given amount of quote tokens. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LPs in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LPs. - * @param quoteTokens_ The amount of quote tokens to calculate LPs amount for. + * @param bucketLPs_ Amount of LP in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. + * @param quoteTokens_ The amount of quote tokens to calculate LP amount for. * @param bucketPrice_ Price bucket. - * @return The amount of LPs coresponding to the given quote tokens in current bucket. + * @return The amount of LP coresponding to the given quote tokens in current bucket. */ - function quoteTokensToLPs( + function quoteTokensToLP( uint256 bucketCollateral_, uint256 bucketLPs_, uint256 deposit_, @@ -137,7 +137,7 @@ library Buckets { /** * @notice Returns the exchange rate for a given bucket. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LPs in bucket. + * @param bucketLPs_ Amount of LP in bucket. * @param bucketDeposit_ The amount of quote tokens deposited in the given bucket. * @param bucketPrice_ Bucket's price. */ diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index c66e55fda..8003f6eeb 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -362,4 +362,16 @@ library Deposits { j = j << 1; } } + + /** + * @notice Returns LUP for a given debt value (capped at min bucket price). + * @param debt_ The debt amount to calculate LUP for. + * @return LUP for given debt. + */ + function getLup( + DepositsState storage deposits_, + uint256 debt_ + ) internal view returns (uint256) { + return _priceAt(findIndexOfSum(deposits_, debt_)); + } } diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 313bfb77b..68a0cb87f 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -30,13 +30,13 @@ - **L3**: Loans array (`LoansState.loans`) is a max-heap with respect to t0-threshold price: the t0TP of loan at index `i` is >= the t0-threshold price of the loans at index `2*i` and `2*i+1` ## Buckets -- **B1**: sum of LPs of lenders in bucket (`Lender.lps`) = bucket LPs accumulator (`Bucket.lps`) -- **B2**: bucket LPs accumulator (`Bucket.lps`) = 0 if no deposit / collateral in bucket +- **B1**: sum of LP of lenders in bucket (`Lender.lps`) = bucket LP accumulator (`Bucket.lps`) +- **B2**: bucket LP accumulator (`Bucket.lps`) = 0 if no deposit / collateral in bucket - **B3**: if no collateral or deposit in bucket then the bucket exchange rate is `1e27` -- **B4**: bankrupt bucket LPs accumulator = 0; lender LPs for deposits before bankruptcy time = 0 +- **B4**: bankrupt bucket LP accumulator = 0; lender LP for deposits before bankruptcy time = 0 - **B5**: when adding / moving quote tokens or adding collateral : lender deposit time (`Lender.depositTime`) = timestamp of block when deposit happened (`block.timestamp`) -- **B6**: when receiving transferred LPs : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time -- **B7**: when awarded bucket take LPs : taker/kicker deposit time (`Lender.depositTime`) = timestamp of block when award happened (`block.timestamp`) +- **B6**: when receiving transferred LP : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time +- **B7**: when awarded bucket take LP : taker/kicker deposit time (`Lender.depositTime`) = timestamp of block when award happened (`block.timestamp`) ## Interest - **I1**: interest rate (`InterestState.interestRate`) cannot be updated more than once in a 12 hours period of time (`InterestState.interestRateUpdate`) @@ -71,5 +71,5 @@ - **RE8**: Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. - **RE9**: Reserves increase by 3 months of interest when a loan is kicked - **RE10**: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt -- **RE11**: Reserves decrease by claimableReserves by startClaimableReserveAuction +- **RE11**: Reserves decrease by claimableReserves by kickReserveAuction - **RE12**: Reserves decrease by amount of reserve used to settle a auction diff --git a/tests/brownie/sdk/ajna_protocol.py b/tests/brownie/sdk/ajna_protocol.py index 6218636f0..c49dd4d4c 100644 --- a/tests/brownie/sdk/ajna_protocol.py +++ b/tests/brownie/sdk/ajna_protocol.py @@ -5,9 +5,12 @@ Contract, ERC20PoolFactory, ERC20Pool, - Auctions, + KickerActions, + TakerActions, + SettlerActions, PoolCommons, LenderActions, + LPActions, BorrowerActions, Deposits, Maths, @@ -33,11 +36,14 @@ def __init__(self, ajna) -> None: self.deposits = Deposits.deploy({"from": self.deployer}) self.pool_logic = PoolCommons.deploy({"from": self.deployer}) - self.lender_actions = LenderActions.deploy({"from": self.deployer}) - self.borrower_actions = BorrowerActions.deploy({"from": self.deployer}) self.maths = Maths.deploy({"from": self.deployer}) self.loans = Loans.deploy({"from": self.deployer}) - self.auctions = Auctions.deploy({"from": self.deployer}) + self.lender_actions = LenderActions.deploy({"from": self.deployer}) + self.transfer_actions = LPActions.deploy({"from": self.deployer}) + self.borrower_actions = BorrowerActions.deploy({"from": self.deployer}) + self.kicker_actions = KickerActions.deploy({"from": self.deployer}) + self.taker_auctions = TakerActions.deploy({"from": self.deployer}) + self.settler_auctions = SettlerActions.deploy({"from": self.deployer}) self.pool_info_utils = PoolInfoUtils.deploy({"from": self.deployer}) self.ajna_factory = ERC20PoolFactory.deploy(ajna, {"from": self.deployer}) diff --git a/tests/brownie/test_invariants.py b/tests/brownie/test_invariants.py index 8605cb273..713dd01e7 100644 --- a/tests/brownie/test_invariants.py +++ b/tests/brownie/test_invariants.py @@ -75,7 +75,7 @@ def pool_collateral_balance(self): # - added collateral in bucket (bidder swap) # collateral outflows: # - pull collateral (borrower) - # - remove collateral from buckets (any actor with LPs in bucket: lender, borrower, kicker, taker) + # - remove collateral from buckets (any actor with LP in bucket: lender, borrower, kicker, taker) # - auction take buckets_collateral = 0 @@ -105,7 +105,7 @@ def pool_quote_balance(self): # - kick loan (kicker, lender) # quote outflows: # - draw debt (borrower) - # - remove quote tokens from bucket (any actor with LPs in bucket: lender, borrower, kicker, taker) + # - remove quote tokens from bucket (any actor with LP in bucket: lender, borrower, kicker, taker) # - claim bonds (kicker, lender) # - reward reserves auction @@ -157,7 +157,7 @@ def pool_buckets(self): # Invariant 5: sum of actors lps in bucket = bucket lps accumulator assert bucket_lps == total_lenders_lps - # Invariant 6: if no deposit / collateral in bucket then bucket LPs should be 0 + # Invariant 6: if no deposit / collateral in bucket then bucket LP should be 0 if bucket_collateral == 0 and bucket_deposit == 0: assert bucket_lps == 0 diff --git a/tests/forge/interactions/NFTTakeExample.sol b/tests/forge/interactions/NFTTakeExample.sol index 78d730e26..d6532bd60 100644 --- a/tests/forge/interactions/NFTTakeExample.sol +++ b/tests/forge/interactions/NFTTakeExample.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.14; import './Interfaces.sol'; -import 'src/interfaces/pool/commons/IPoolLiquidationActions.sol'; import 'src/interfaces/pool/erc721/IERC721Taker.sol'; contract NFTTakeExample is IERC721Taker { diff --git a/tests/forge/interactions/UniswapTakeExample.sol b/tests/forge/interactions/UniswapTakeExample.sol index fe9ceab22..ce263da27 100644 --- a/tests/forge/interactions/UniswapTakeExample.sol +++ b/tests/forge/interactions/UniswapTakeExample.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.14; import './Interfaces.sol'; -import 'src/interfaces/pool/commons/IPoolLiquidationActions.sol'; import 'src/interfaces/pool/erc20/IERC20Taker.sol'; contract UniswapTakeExample is IERC20Taker { diff --git a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol index d52ef997f..16afade78 100644 --- a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol @@ -26,7 +26,7 @@ contract ReserveERC20PoolInvariants is ReserveInvariants, LiquidationERC20PoolIn * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. * RE9 : Reserves increase by 3 months of interest when a loan is kicked * RE10: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt - * RE11: Reserves decrease by claimableReserves by startClaimableReserveAuction + * 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/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index 81ff66669..32a322887 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -20,12 +20,12 @@ abstract contract BasicInvariants is BaseInvariants { /*** Invariant Tests ***/ /*************************************************************************************************************************************** * Bucket - * B1: totalBucketLPs === totalLenderLps + * B1: totalBucketLP === totalLenderLps * B2: bucketLps == 0 (if bucket quote and collateral is 0) * B3: exchangeRate == 0 (if bucket quote and collateral is 0) - * B4: bankrupt bucket LPs accumulator = 0; lender LPs for deposits before bankruptcy time = 0 + * B4: bankrupt bucket LP accumulator = 0; lender LP for deposits before bankruptcy time = 0 * B5: block.timestamp == lenderDepositTime (if lps are added to lender lp balance) - * B6: block.timestamp == max(sender's depositTime, receiver's depositTime), when receiving transferred LPs + * B6: block.timestamp == max(sender's depositTime, receiver's depositTime), when receiving transferred LP * B7: lenderDepositTime == block.timestamp (timestamp of block when taker is rewarded by bucketTake) * Quote Token * QT1: poolQtBal + poolDebt >= totalBondEscrowed + poolDepositSize @@ -170,7 +170,7 @@ abstract contract BasicInvariants is BaseInvariants { requireWithinDiff( Maths.wmul(currentExchangeRate, bucketLps), Maths.wmul(previousExchangeRate, bucketLps), - 1e16, // allow changes up to 0.01 qt in value if bucket LPs < 1e-6 + 1e16, // allow changes up to 0.01 qt in value if bucket LP < 1e-6 "Incorrect exchange Rate changed" ); } else { diff --git a/tests/forge/invariants/base/ReserveInvariants.t.sol b/tests/forge/invariants/base/ReserveInvariants.t.sol index 2b687a14d..a530c0fbf 100644 --- a/tests/forge/invariants/base/ReserveInvariants.t.sol +++ b/tests/forge/invariants/base/ReserveInvariants.t.sol @@ -23,7 +23,7 @@ abstract contract ReserveInvariants is LiquidationInvariants { * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. * RE9 : Reserves increase by 3 months of interest when a loan is kicked * RE10: Reserves increase by origination fee: max(1 week interest, 0.05% of borrow amount), on draw debt - * RE11: Reserves decrease by claimableReserves by startClaimableReserveAuction + * 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/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol index 28d441a1f..3c3514f12 100644 --- a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -80,7 +80,7 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); // Action phase - _increaseLPsAllowance(receiver, _lenderBucketIndex, boundedLps); + _increaseLPAllowance(receiver, _lenderBucketIndex, boundedLps); _transferLps(_actor, receiver, _lenderBucketIndex); } @@ -115,13 +115,13 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { boundedToIndex_ = constrictToRange(toIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); boundedAmount_ = constrictToRange(amountToMove_, MIN_AMOUNT, MAX_AMOUNT); - // ensure actor has LPs to move + // ensure actor has LP to move (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); if (lpBalance == 0) _addQuoteToken(boundedAmount_, boundedToIndex_); (uint256 lps, ) = _pool.lenderInfo(boundedFromIndex_, _actor); // restrict amount to move by available deposit inside bucket - uint256 availableDeposit = _poolInfo.lpsToQuoteTokens(address(_pool), lps, boundedFromIndex_); + uint256 availableDeposit = _poolInfo.lpToQuoteTokens(address(_pool), lps, boundedFromIndex_); boundedAmount_ = Maths.min(boundedAmount_, availableDeposit); } @@ -129,7 +129,7 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 toActorIndex_, uint256 lpsToTransfer_ ) internal returns (address receiver_, uint256 boundedLps_) { - // ensure actor has LPs to transfer + // ensure actor has LP to transfer (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); if(senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); diff --git a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol index ef7d05555..e39037166 100644 --- a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol @@ -14,11 +14,11 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation /*** Reserves Test Functions ***/ /*******************************/ - function startClaimableReserveAuction( + function kickReserveAuction( uint256 actorIndex_ ) external useRandomActor(actorIndex_) useTimestamps { // Action phase - _startClaimableReserveAuction(); + _kickReserveAuction(); } function takeReserves( @@ -40,7 +40,7 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation uint256 amountToTake_ ) internal returns (uint256 boundedAmount_) { (, , uint256 claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - if (claimableReservesRemaining == 0) _startClaimableReserveAuction(); + if (claimableReservesRemaining == 0) _kickReserveAuction(); // skip enough time for auction price to decrease skip(24 hours); diff --git a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index 3f5a9d4ad..ab7c6c5f1 100644 --- a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -164,7 +164,7 @@ abstract contract BaseHandler is Test { err == keccak256(abi.encodeWithSignature("MoveToSameIndex()")) || err == keccak256(abi.encodeWithSignature("DustAmountNotExceeded()")) || err == keccak256(abi.encodeWithSignature("InvalidIndex()")) || - err == keccak256(abi.encodeWithSignature("InsufficientLPs()")) || + err == keccak256(abi.encodeWithSignature("InsufficientLP()")) || err == keccak256(abi.encodeWithSignature("AuctionNotCleared()")) || err == keccak256(abi.encodeWithSignature("TransferorNotApproved()")) || err == keccak256(abi.encodeWithSignature("TransferToSameOwner()")) || diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol index 03f1bf874..48d6bafb2 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol @@ -111,7 +111,7 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { } } - function _increaseLPsAllowance( + function _increaseLPAllowance( address receiver_, uint256 bucketIndex_, uint256 amount_ @@ -119,13 +119,13 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { // approve as transferor address[] memory transferors = new address[](1); transferors[0] = receiver_; - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); uint256[] memory buckets = new uint256[](1); buckets[0] = bucketIndex_; uint256[] memory amounts = new uint256[](1); amounts[0] = amount_; - _pool.increaseLPsAllowance(receiver_, buckets, amounts); + _pool.increaseLPAllowance(receiver_, buckets, amounts); } function _transferLps( @@ -138,12 +138,12 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { changePrank(receiver_); - try _pool.transferLPs(sender_, receiver_, buckets) { + try _pool.transferLP(sender_, receiver_, buckets) { (, uint256 senderDepositTime) = _pool.lenderInfo(bucketIndex_, sender_); (, uint256 receiverDepositTime) = _pool.lenderInfo(bucketIndex_, receiver_); - // **B6**: when receiving transferred LPs : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time + // **B6**: when receiving transferred LP : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time lenderDepositTime[receiver_][bucketIndex_] = Maths.max(senderDepositTime, receiverDepositTime); } catch (bytes memory err) { diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol index ec9fdde17..930c3b9cf 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -144,11 +144,11 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { (uint256 takerLpsAfterTake, ) = _pool.lenderInfo(bucketIndex_, _actor); ( , , , uint256 depositAfterAction, ) = _pool.bucketInfo(bucketIndex_); - // **B7**: when awarded bucket take LPs : taker deposit time = timestamp of block when award happened + // **B7**: when awarded bucket take LP : taker deposit time = timestamp of block when award happened if (takerLpsAfterTake > takerLpsBeforeTake) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; if (kickerLpsAfterTake > kickerLpsBeforeTake) { - // **B7**: when awarded bucket take LPs : kicker deposit time = timestamp of block when award happened + // **B7**: when awarded bucket take LP : kicker deposit time = timestamp of block when award happened lenderDepositTime[kicker][bucketIndex_] = block.timestamp; } else { // **RE7**: Reserves increase by bond penalty on take. diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol index 062f4142d..69ca27a49 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedReservePoolHandler.sol @@ -12,13 +12,13 @@ abstract contract UnboundedReservePoolHandler is BaseHandler { /*** Kicker Helper Functions ***/ /*******************************/ - function _startClaimableReserveAuction() internal updateLocalStateAndPoolInterest { + function _kickReserveAuction() internal updateLocalStateAndPoolInterest { (, uint256 claimableReserves, , , ) = _poolInfo.poolReservesInfo(address(_pool)); if (claimableReserves == 0) return; - try _pool.startClaimableReserveAuction() { + try _pool.kickReserveAuction() { - // **RE11**: Reserves increase by claimableReserves by startClaimableReserveAuction + // **RE11**: Reserves increase by claimableReserves by kickReserveAuction decreaseInReserves += claimableReserves; } catch (bytes memory err) { _ensurePoolError(err); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 526aad653..ecbee74c5 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -98,7 +98,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { function test_regression_reserve_12() external { _reserveERC20PoolHandler.drawDebt(7201, 13634); - _reserveERC20PoolHandler.startClaimableReserveAuction(4584); + _reserveERC20PoolHandler.kickReserveAuction(4584); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } @@ -187,7 +187,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { function test_regression_invariant_reserves_settle_1() external { _reserveERC20PoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952); _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886); - _reserveERC20PoolHandler.startClaimableReserveAuction(18407833277983020451007887294192863287187933); + _reserveERC20PoolHandler.kickReserveAuction(18407833277983020451007887294192863287187933); _reserveERC20PoolHandler.settleAuction(2720, 3319, 516); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); @@ -209,7 +209,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { _reserveERC20PoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); _reserveERC20PoolHandler.takeAuction(159178586166894, 2, 2); _reserveERC20PoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653); - _reserveERC20PoolHandler.startClaimableReserveAuction(2162); + _reserveERC20PoolHandler.kickReserveAuction(2162); _reserveERC20PoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524); _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); @@ -227,14 +227,14 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { _reserveERC20PoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208); _reserveERC20PoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765); _reserveERC20PoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383); - _reserveERC20PoolHandler.startClaimableReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); _reserveERC20PoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589); _reserveERC20PoolHandler.drawDebt(3, 2); _reserveERC20PoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855); _reserveERC20PoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758); _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935); _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reserveERC20PoolHandler.startClaimableReserveAuction(0); + _reserveERC20PoolHandler.kickReserveAuction(0); _reserveERC20PoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458); invariant_quoteTokenBalance_QT1(); @@ -243,7 +243,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_3() external { _reserveERC20PoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442); _reserveERC20PoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189); - _reserveERC20PoolHandler.startClaimableReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); + _reserveERC20PoolHandler.kickReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); _reserveERC20PoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804); _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067); _reserveERC20PoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2); @@ -251,7 +251,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { _reserveERC20PoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417); _reserveERC20PoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615); _reserveERC20PoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417); - _reserveERC20PoolHandler.startClaimableReserveAuction(1); + _reserveERC20PoolHandler.kickReserveAuction(1); _reserveERC20PoolHandler.bucketTake(3, 1, true, 2); _reserveERC20PoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893); _reserveERC20PoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932); diff --git a/tests/forge/unit/Auctions.t.sol b/tests/forge/unit/Auctions.t.sol index bc0cfa7f9..e7c1851d1 100644 --- a/tests/forge/unit/Auctions.t.sol +++ b/tests/forge/unit/Auctions.t.sol @@ -3,8 +3,6 @@ pragma solidity 0.8.14; import '../utils/DSTestPlus.sol'; -import 'src/libraries/external/Auctions.sol'; - contract AuctionsTest is DSTestPlus { /** @@ -17,12 +15,12 @@ contract AuctionsTest is DSTestPlus { uint256 neutralPrice = 15 * 1e18; uint256 bondFactor = 0.1 * 1e18; - assertEq(Auctions._bpf(debt, collateral, neutralPrice, bondFactor, price), 0.1 * 1e18); - assertEq(Auctions._bpf(9000 * 1e18, collateral, neutralPrice, bondFactor, price), 0.083333333333333333 * 1e18); - assertEq(Auctions._bpf(debt, collateral, neutralPrice, bondFactor, 9.5 * 1e18), 0.1 * 1e18); - assertEq(Auctions._bpf(9000 * 1e18, collateral, neutralPrice, bondFactor, 9.5 * 1e18), 0.091666666666666667 * 1e18); - assertEq(Auctions._bpf(9000 * 1e18, collateral, 10 * 1e18, bondFactor, 10.5 * 1e18), -0.05 * 1e18); - assertEq(Auctions._bpf(debt, collateral, 5 * 1e18, bondFactor, 10.5 * 1e18), -0.1 * 1e18); + assertEq(_bpf(debt, collateral, neutralPrice, bondFactor, price), 0.1 * 1e18); + assertEq(_bpf(9000 * 1e18, collateral, neutralPrice, bondFactor, price), 0.083333333333333333 * 1e18); + assertEq(_bpf(debt, collateral, neutralPrice, bondFactor, 9.5 * 1e18), 0.1 * 1e18); + assertEq(_bpf(9000 * 1e18, collateral, neutralPrice, bondFactor, 9.5 * 1e18), 0.091666666666666667 * 1e18); + assertEq(_bpf(9000 * 1e18, collateral, 10 * 1e18, bondFactor, 10.5 * 1e18), -0.05 * 1e18); + assertEq(_bpf(debt, collateral, 5 * 1e18, bondFactor, 10.5 * 1e18), -0.1 * 1e18); } /** @@ -35,24 +33,24 @@ contract AuctionsTest is DSTestPlus { uint256 neutralPrice = 8_600.0 * 1e18; uint256 kickTime = block.timestamp; - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 277_712 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 277_712 * 1e18); skip(1444); // price should not change in the first hour - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 277_712 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 277_712 * 1e18); skip(5756); // 2 hours - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 138_856 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 138_856 * 1e18); skip(2394); // 2 hours, 39 minutes, 54 seconds - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 87_574.910740335995562528 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 87_574.910740335995562528 * 1e18); skip(2586); // 3 hours, 23 minutes - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 53_227.960156860514117568 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 53_227.960156860514117568 * 1e18); skip(3); // 3 seconds later - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 53_197.223359425583052544 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 53_197.223359425583052544 * 1e18); skip(20153); // 8 hours, 35 minutes, 53 seconds - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 1_098.26293050754894624 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 1_098.26293050754894624 * 1e18); skip(97264); // 36 hours - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 0.00000808248283696 * 1e18); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 0.00000808248283696 * 1e18); skip(129600); // 72 hours - assertEq(Auctions._auctionPrice(momp, neutralPrice, kickTime), 0); + assertEq(_auctionPrice(momp, neutralPrice, kickTime), 0); } /** diff --git a/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol index 2a746cbb1..c6a392f7d 100644 --- a/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol @@ -408,7 +408,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { _repayDebt(from, borrower, amountToRepay, amountRepaid, collateralToPull, 0); } - function _transferLPs( + function _transferLP( address operator, address from, address to, @@ -417,8 +417,8 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(operator); vm.expectEmit(true, true, true, true); - emit TransferLPs(from, to, indexes, lpBalance); - _pool.transferLPs(from, to, indexes); + emit TransferLP(from, to, indexes, lpBalance); + _pool.transferLP(from, to, indexes); for(uint256 i = 0; i < indexes.length ;i++ ){ if(lenders.contains(from)){ @@ -580,12 +580,12 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ERC20Pool(address(_pool)).removeCollateral(amount, index); } - function _assertRemoveAllCollateralInsufficientLPsRevert( + function _assertRemoveAllCollateralInsufficientLPRevert( address from, uint256 index ) internal { changePrank(from); - vm.expectRevert(IPoolErrors.InsufficientLPs.selector); + vm.expectRevert(IPoolErrors.InsufficientLP.selector); ERC20Pool(address(_pool)).removeCollateral(type(uint256).max, index); } @@ -597,7 +597,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(operator); vm.expectRevert(IPoolErrors.InvalidIndex.selector); - _pool.transferLPs(from, to, indexes); + _pool.transferLP(from, to, indexes); } function _assertTransferNoAllowanceRevert( @@ -608,7 +608,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(operator); vm.expectRevert(IPoolErrors.NoAllowance.selector); - _pool.transferLPs(from, to, indexes); + _pool.transferLP(from, to, indexes); } function _assertTransferToSameOwnerRevert( @@ -619,7 +619,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { ) internal { changePrank(operator); vm.expectRevert(IPoolErrors.TransferToSameOwner.selector); - _pool.transferLPs(from, to, indexes); + _pool.transferLP(from, to, indexes); } function _assertDepositLockedByAuctionDebtRevert( diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol index 87d19912b..05f74af08 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol @@ -352,7 +352,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { lpAward: 12_043.56808879152623138 * 1e18 }); - // check bucket state and bidder's LPs + // check bucket state and bidder's LP _assertBucket({ index: 2550, lpBalance: 12_043.56808879152623138 * 1e18, @@ -380,7 +380,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { lpRedeem: 4_606.664793962758783503 * 1e18 }); - // check bucket state and bidder's LPs + // check bucket state and bidder's LP _assertBucket({ index: 2550, lpBalance: 7_436.903294828767447877 * 1e18, @@ -408,7 +408,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { lpRedeem: 7_436.903294828767447877 * 1e18 }); - // check bucket state and bidder's LPs + // check bucket state and bidder's LP _assertBucket({ index: 2550, lpBalance: 0, @@ -448,7 +448,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { lpRedeem: 243_808.126330587520990921 * 1e18 }); - // check bucket state and bidder's LPs + // check bucket state and bidder's LP _assertBucket({ index: 1530, lpBalance: 243_808.126330587520990920 * 1e18, @@ -476,7 +476,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { lpRedeem: 243_808.126330587520990920 * 1e18 }); - // check bucket state and bidder's LPs + // check bucket state and bidder's LP _assertBucket({ index: 1530, lpBalance: 0, @@ -660,13 +660,13 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: 2570, - lpBalance: 0, // LPs should get back to same value as before add / remove collateral + lpBalance: 0, // LP should get back to same value as before add / remove collateral depositTime: _startTime }); _assertLenderLpBalance({ lender: _bidder, index: 2570, - lpBalance: 6879, // LPs should get back to same value as before add / remove collateral + lpBalance: 6879, // LP should get back to same value as before add / remove collateral depositTime: _startTime }); _assertBucket({ @@ -732,7 +732,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: 2570, - lpBalance: 6879, // LPs should get back to same value as before add / remove collateral + lpBalance: 6879, // LP should get back to same value as before add / remove collateral depositTime: _startTime }); _assertBucket({ @@ -801,7 +801,7 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { }); // lender should not be able to remove any collateral as LP balance is 304 < 2725 - _assertRemoveAllCollateralInsufficientLPsRevert({ + _assertRemoveAllCollateralInsufficientLPRevert({ from: _bidder, index: 2570 }); diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol index a4bcc5fe6..7f8fccd24 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol @@ -11,7 +11,7 @@ import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; import { IPoolFactory } from 'src/interfaces/pool/IPoolFactory.sol'; contract ERC20PoolFactoryTest is ERC20HelperContract { - address immutable poolAddress = 0xeCAF6d240E0AdcaD5FfE4306b7D4301Df130bC02; + address immutable poolAddress = 0x3Cd0C8d97cD0291E178560E5675dD27AeD0A90d1; function setUp() external { // deploy new pool factory for factory tests diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol index 997c62eed..bd0d09337 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol @@ -229,7 +229,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { function testPoolInfoUtilsLPsToCollateralAndQuote() external { assertEq( - _poolUtils.lpsToCollateral( + _poolUtils.lpToCollateral( address(_pool), 100 * 1e18, high @@ -240,35 +240,35 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { ERC20Pool(address(_pool)).addCollateral(10 * 1e18, high, block.timestamp + 5 minutes); assertEq( - _poolUtils.lpsToCollateral( + _poolUtils.lpToCollateral( address(_pool), 5 * 1e18, high ), 1668940620571264 ); assertEq( - _poolUtils.lpsToCollateral( + _poolUtils.lpToCollateral( address(_pool), 20 * 1e18, high ), 6675762482285055 ); assertEq( - _poolUtils.lpsToQuoteTokens( + _poolUtils.lpToQuoteTokens( address(_pool), 100 * 1e18, high ), 100000000000000000000 ); assertEq( - _poolUtils.lpsToQuoteTokens( + _poolUtils.lpToQuoteTokens( address(_pool), 5 * 1e18, high ), 5000000000000000000 ); assertEq( - _poolUtils.lpsToQuoteTokens( + _poolUtils.lpToQuoteTokens( address(_pool), 20 * 1e18, high diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index d26868617..a9f03311a 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -253,7 +253,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { borrower: _borrower, active: true, kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, // should be the same after arb take, kicker will be rewarded with LPs + bondSize: 0.195342779771472726 * 1e18, // should be the same after arb take, kicker will be rewarded with LP bondFactor: 0.01 * 1e18, kickTime: block.timestamp - 6.5 hours, kickMomp: 9.818751856078723036 * 1e18, @@ -295,7 +295,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.144281700983723506 * 1e18, // rewarded with LPs in bucket + lpBalance: 2_000.144281700983723506 * 1e18, // rewarded with LP in bucket depositTime: _startTime + 100 days + 6.5 hours }); _assertBucket({ @@ -325,7 +325,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { borrower: _borrower, active: true, kicker: _lender, - bondSize: 0.195342779771472726 * 1e18, // bond size remains the same, kicker was rewarded with LPs + bondSize: 0.195342779771472726 * 1e18, // bond size remains the same, kicker was rewarded with LP bondFactor: 0.01 * 1e18, kickTime: block.timestamp - 6.5 hours, kickMomp: 9.818751856078723036 * 1e18, @@ -613,7 +613,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { }); _assertBucket({ index: _i10016, - lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LPs awarded for arb taker + lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LP awarded for arb taker collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket deposit: 978.836725452666849367 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index e962f1dc7..f8bd86971 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -621,7 +621,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { }); _assertBucket({ index: _i10016, - lpBalance: 1_000 * 1e18, // LP balance in arbed bucket increased with LPs awarded for deposit taker + lpBalance: 1_000 * 1e18, // LP balance in arbed bucket increased with LP awarded for deposit taker collateral: 0.002156704581707556 * 1e18, // arbed collateral added to the arbed bucket deposit: 978.397365129691616480 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol index 2b7f74dae..3930c3189 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol @@ -152,8 +152,8 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { function testKickWithDepositAmountHigherThanAuctionBond() external tearDown { /** - - kick with deposit amount lower than deposit available (lender can redeem less LPs from bucket than deposit) - - auction bond is covered entirely from lender deposit (bucket still contains LPs) + - kick with deposit amount lower than deposit available (lender can redeem less LP from bucket than deposit) + - auction bond is covered entirely from lender deposit (bucket still contains LP) */ // assert bucket state pre kick with deposit @@ -215,7 +215,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - // assert lenders LPs in bucket used to kick + // assert lenders LP in bucket used to kick _assertLenderLpBalance({ lender: _lender1, index: 2500, @@ -228,7 +228,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { lpBalance: 10_000 * 1e18, depositTime: _startTime }); - // assert bucket LPs + // assert bucket LP _assertBucket({ index: 2500, lpBalance: 53_994.230769230769228 * 1e18, // reduced by amount used to cover auction bond @@ -264,8 +264,8 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { function testKickWithDepositAmountLowerThanAuctionBond() external tearDown { /** - - kick with deposit amount lower than deposit available (lender can redeem less LPs from bucket than deposit) - - bond auction is not covered entirely by removed deposit (bucket still contains LPs), difference to cover bond is sent by lender + - kick with deposit amount lower than deposit available (lender can redeem less LP from bucket than deposit) + - bond auction is not covered entirely by removed deposit (bucket still contains LP), difference to cover bond is sent by lender */ // borrower 1 draws more debt from pool, bond size will increase from 6_005.769230769230772000 in prev scenario to 8_708.365384615384619400 @@ -349,7 +349,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - // assert lenders LPs in bucket used + // assert lenders LP in bucket used _assertLenderLpBalance({ lender: _lender1, index: 2500, @@ -362,7 +362,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { lpBalance: 0, depositTime: _startTime }); - // assert bucket LPs + // assert bucket LP _assertBucket({ index: 2500, lpBalance: 60_000 * 1e18, @@ -397,7 +397,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { function testKickWithDepositUsingAllLpsWithinBucket() external tearDown { /** - - kick using entire deposit / LPs from bucket + - kick using entire deposit / LP from bucket - bond auction is not covered entirely by deposit, deposit is obliterated and difference to cover bond is sent by lender */ @@ -437,7 +437,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { index: 2500 }); - // lender 2 kicks using all LPs from bucket 2499 (10_000) and sending additional quote tokens to cover auction bond (510.096153846153851000) + // lender 2 kicks using all LP from bucket 2499 (10_000) and sending additional quote tokens to cover auction bond (510.096153846153851000) _kickWithDeposit({ from: _lender2, index: 2499, @@ -483,14 +483,14 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - // assert lenders LPs in bucket used + // assert lenders LP in bucket used _assertLenderLpBalance({ lender: _lender2, index: 2499, lpBalance: 0, depositTime: _startTime }); - // assert bucket - LPs and deposit obliterated + // assert bucket - LP and deposit obliterated _assertBucket({ index: 2499, lpBalance: 0, @@ -526,7 +526,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { function testKickWithDepositAmountHigherThanAvailableDeposit() external tearDown { /** - - kick with deposit amount higher than deposit available (lender can redeem more LPs from bucket than deposit) + - kick with deposit amount higher than deposit available (lender can redeem more LP from bucket than deposit) - auction bond is covered entirely from lender deposit */ @@ -603,7 +603,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { assertEq(_quote.balanceOf(_borrower4), 20_000 * 1e18); assertEq(_quote.balanceOf(_borrower5), 20_000 * 1e18); - // assert lenders LPs in bucket used + // assert lenders LP in bucket used _assertLenderLpBalance({ lender: _lender1, index: 2500, @@ -616,7 +616,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { lpBalance: 10_000 * 1e18, depositTime: _startTime }); - // assert bucket LPs + // assert bucket LP _assertBucket({ index: 2500, lpBalance: 92_630.77445790356267464 * 1e18, // reduced by amount used to cover auction bond @@ -1328,7 +1328,7 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { index: 7000 }); - // asert failure when lender has LPs but insufficient quote token balance to post remaining bond + // asert failure when lender has LP but insufficient quote token balance to post remaining bond _addLiquidity({ from: _lender4, amount: 5_000 * 1e18, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol index 398442c4a..c2bfed467 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol @@ -327,7 +327,7 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { assertGt(lastAuctionDebt, 0); assertGt(lastAuctionCollateral, 0); (, uint256 lastBucketDeposit, uint256 lastBucketCollateral, uint256 lastBucketLPs, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); - uint256 lastKickerLPs = _kickerLPs(bucketId); + uint256 lastKickerLPs = _kickerLP(bucketId); assertGt(lastAuctionDebt, 0); assertGt(lastAuctionCollateral, 0); @@ -349,11 +349,11 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { assertEq(bucketCollateral, lastBucketCollateral + collateralTaken); } - // confirm LPs were awarded to the kicker + // confirm LP were awarded to the kicker (address kicker, , , uint256 kickTime, uint256 kickMomp, uint256 neutralPrice, , , , ) = _pool.auctionInfo(_borrower); - uint256 auctionPrice = Auctions._auctionPrice(kickMomp, neutralPrice, kickTime); + uint256 auctionPrice = _auctionPrice(kickMomp, neutralPrice, kickTime); if (auctionPrice < neutralPrice) { - uint256 kickerLPs = _kickerLPs(bucketId); + uint256 kickerLPs = _kickerLP(bucketId); assertGt(kickerLPs, lastKickerLPs); uint256 kickerLpChange = kickerLPs - lastKickerLPs; assertEq(bucketLPs, lastBucketLPs + kickerLpChange); @@ -422,11 +422,11 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { uint256 auctionCollateral_ ){ (, , , uint256 kickTime, uint256 kickMomp, uint256 neutralPrice, , , , ) = _pool.auctionInfo(_borrower); - uint256 lastAuctionPrice = Auctions._auctionPrice(kickMomp, neutralPrice, kickTime); + uint256 lastAuctionPrice = _auctionPrice(kickMomp, neutralPrice, kickTime); (uint256 lastAuctionDebt, uint256 lastAuctionCollateral, ) = _poolUtils.borrowerInfo(address(_pool), _borrower); if (secondsToSkip != 0) { skip(secondsToSkip); - auctionPrice_ = Auctions._auctionPrice(kickMomp, neutralPrice, kickTime); + auctionPrice_ = _auctionPrice(kickMomp, neutralPrice, kickTime); (uint256 auctionDebt, uint256 auctionCollateral, ) = _poolUtils.borrowerInfo(address(_pool), _borrower); // ensure auction price decreases and auction debt increases as time passes assertLt(auctionPrice_, lastAuctionPrice); @@ -441,17 +441,12 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { } } - function _auctionPrice() internal view returns (uint256) { - (, , , uint256 kickTime, uint256 kickMomp, uint256 neutralPrice, , , , ) = _pool.auctionInfo(_borrower); - return Auctions._auctionPrice(kickMomp, neutralPrice, kickTime); - } - function _borrowerCollateralization(address borrower) internal view returns (uint256) { (uint256 debt, uint256 collateral, ) = _poolUtils.borrowerInfo(address(_pool), borrower); return _collateralization(debt, collateral, _lup()); } - function _kickerLPs(uint256 bucketId) internal view returns (uint256) { + function _kickerLP(uint256 bucketId) internal view returns (uint256) { (address kicker, , , , , , , , , ) = _pool.auctionInfo(_borrower); (uint256 kickerLPs, ) = _pool.lenderInfo(bucketId, kicker); return kickerLPs; diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index d7de2626c..962473981 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -795,7 +795,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91 }); - // cannot move LPs in same block when bucket marked insolvent + // cannot move LP in same block when bucket marked insolvent _assertMoveLiquidityBankruptcyBlockRevert({ from: _lender1, amount: 10 * 1e18, @@ -960,7 +960,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).settle(actor1, 1); (bucketLps, collateral, , deposit, ) = _pool.bucketInfo(2571); - assertEq(bucketLps, 0); // entire LPs removed from bucket 2571 + assertEq(bucketLps, 0); // entire LP removed from bucket 2571 assertEq(collateral, 0); // no collateral added in bucket 2571 assertEq(deposit, 0); // entire deposit from bucket 2571 used to settle (borrowerDebt, borrowerCollateral, ) = _pool.borrowerInfo(actor1); @@ -1005,7 +1005,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { changePrank(actor1); ERC20Pool(address(_pool)).updateInterest(); - _startClaimableReserveAuction({ + _kickReserveAuction({ from: actor1, remainingReserves: 785_271_398.552730383160590325 * 1e18, price: 1_000_000_000 * 1e18, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol index 4bf475a45..ada3d5b55 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol @@ -493,10 +493,10 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { // addQuoteToken should add scaled quote token amount validate LP _addLiquidityNoEventCheck(_lender, quoteAmount, bucketId); - // deposit collateral and sanity check bidder LPs + // deposit collateral and sanity check bidder LP _addCollateralWithoutCheckingLP(_bidder, collateralAmount, bucketId); - // check bucket quantities and LPs + // check bucket quantities and LP (, uint256 curDeposit, uint256 availableCollateral, uint256 bucketLpBalance,,) = _poolUtils.bucketInfo(address(_pool), bucketId); assertEq(curDeposit, _roundToScale(quoteAmount, quoteScale)); assertEq(availableCollateral, _roundToScale(collateralAmount, colScale)); @@ -611,7 +611,7 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { newLup: MAX_PRICE }); - // validate from and to buckets have appropriate amounts of deposit and LPs + // validate from and to buckets have appropriate amounts of deposit and LP (, uint256 deposit,, uint256 lps,,) = _poolUtils.bucketInfo(address(_pool), fromBucketId); uint256 remaining = _lenderDepositNormalized - amountToMove; diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol index 06443987e..648f13482 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol @@ -49,7 +49,7 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { lpAward: 12_043.56808879152623138 * 1e18 }); - // check bucket state and LPs + // check bucket state and LP _assertBucket({ index: testIndex, lpBalance: 22_043.56808879152623138 * 1e18, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol index e766d2bd6..a5f99db9b 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -538,7 +538,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { newLup: MAX_PRICE }); - // add collateral in order to give lender LPs in bucket 5_000 with 0 deposit + // add collateral in order to give lender LP in bucket 5_000 with 0 deposit // used to test revert on remove when bucket deposit is 0 _addCollateral({ from: _lender, @@ -1285,7 +1285,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: 2570, - lpBalance: 0, // LPs should get back to same value as before add / remove collateral + lpBalance: 0, // LP should get back to same value as before add / remove collateral depositTime: _startTime }); _assertBucket({ diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol index 495e59e90..92fba6c9e 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolReserveAuction.t.sol @@ -86,7 +86,7 @@ contract ERC20PoolReserveAuctionTest is ERC20HelperContract { }); // kick off a new auction - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: 1.411317962805061080 * 1e18, price: 1000000000 * 1e18, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol index 2da968134..1a403d726 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol @@ -7,7 +7,7 @@ import 'src/interfaces/pool/commons/IPoolErrors.sol'; import 'src/libraries/helpers/PoolHelper.sol'; -contract ERC20PoolTransferLPsTest is ERC20HelperContract { +contract ERC20PoolTransferLPTest is ERC20HelperContract { address internal _lender; address internal _lender1; @@ -25,14 +25,14 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender2); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); } - /**************************/ - /*** Transfer LPs Tests ***/ - /**************************/ + /*************************/ + /*** Transfer LP Tests ***/ + /*************************/ - function testTransferLPsToZeroAddress() external tearDown { + function testTransferLPToZeroAddress() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -52,7 +52,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { approveIndexes[0] = 2550; uint256[] memory amounts = new uint256[](1); amounts[0] = 1_000 * 1e18; - _pool.increaseLPsAllowance(address(0), approveIndexes, amounts); + _pool.increaseLPAllowance(address(0), approveIndexes, amounts); _assertTransferNoAllowanceRevert({ operator: _lender, @@ -62,7 +62,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsToUnallowedAddress() external tearDown { + function testTransferLPToUnallowedAddress() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -74,7 +74,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 1_000 * 1e18; amounts[1] = 1_000 * 1e18; amounts[2] = 1_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); _assertTransferNoAllowanceRevert({ operator: _lender, @@ -84,7 +84,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsToInvalidIndex() external tearDown { + function testTransferLPToInvalidIndex() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 9999; indexes[1] = 2550; @@ -96,7 +96,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 1_000 * 1e18; amounts[1] = 1_000 * 1e18; amounts[2] = 1_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); _assertTransferInvalidIndexRevert({ operator: _lender, @@ -106,7 +106,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsGreaterThanBalance() external tearDown { + function testTransferLPGreaterThanBalance() external tearDown { uint256[] memory indexes = new uint256[](2); indexes[0] = 2550; indexes[1] = 2551; @@ -126,10 +126,10 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 10_000 * 1e18; amounts[1] = 30_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); // only the lender's available balance should be transferred to the new owner - _transferLPs({ + _transferLP({ operator: _lender2, from: _lender1, to: _lender2, @@ -150,7 +150,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsToSameOwner() external { + function testTransferLPToSameOwner() external { uint256[] memory indexes = new uint256[](1); indexes[0] = 2550; @@ -165,10 +165,10 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender1); uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPsAllowance(_lender1, indexes, amounts); + _pool.increaseLPAllowance(_lender1, indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _assertLenderLpBalance({ lender: _lender1, @@ -177,7 +177,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { depositTime: _startTime + 1 hours }); - // should revert if trying to transfer LPs to same address + // should revert if trying to transfer LP to same address _assertTransferToSameOwnerRevert({ operator: _lender, from: _lender1, @@ -198,14 +198,14 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { // increase allowance should revert for invalid input vm.expectRevert(IPoolErrors.InvalidAllowancesInput.selector); - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); // decrease allowance should revert for invalid input vm.expectRevert(IPoolErrors.InvalidAllowancesInput.selector); - _pool.decreaseLPsAllowance(_lender2, indexes, amounts); + _pool.decreaseLPAllowance(_lender2, indexes, amounts); } - function testTransferLPsForAllIndexes() external tearDown { + function testTransferLPForAllIndexes() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -272,10 +272,10 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 10_000 * 1e18; amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); - // transfer LPs for all indexes - _transferLPs({ + // transfer LP for all indexes + _transferLP({ operator: _lender, from: _lender1, to: _lender2, @@ -330,7 +330,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsForTwoIndexes() external tearDown { + function testTransferLPForTwoIndexes() external tearDown { uint256[] memory depositIndexes = new uint256[](3); depositIndexes[0] = 2550; depositIndexes[1] = 2551; @@ -398,10 +398,10 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 10_000 * 1e18; amounts[1] = 30_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, transferIndexes, amounts); + _pool.increaseLPAllowance(_lender2, transferIndexes, amounts); - // transfer LPs for 2 indexes - _transferLPs({ + // transfer LP for 2 indexes + _transferLP({ operator: _lender, from: _lender1, to: _lender2, @@ -456,7 +456,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsToLenderWithLPs() external tearDown { + function testTransferLPToLenderWithLP() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -542,7 +542,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 10_000 * 1e18; amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); _assertLpAllowance({ owner: _lender1, @@ -551,8 +551,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { lpAllowance: 20_000 * 1e18 }); - // transfer LPs for all indexes - _transferLPs({ + // transfer LP for all indexes + _transferLP({ operator: _lender, from: _lender1, to: _lender2, @@ -613,7 +613,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsApproveRevokeTransferors() external tearDown { + function testTransferLPApproveRevokeTransferors() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -642,8 +642,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[1] = 20_000 * 1e18; amounts[2] = 30_000 * 1e18; vm.expectEmit(true, true, false, true); - emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + emit IncreaseLPAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); assertTrue(_pool.approvedTransferors(_lender2, _lender)); @@ -652,24 +652,24 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { address[] memory transferors = new address[](1); transferors[0] = _lender; vm.expectEmit(true, true, false, true); - emit RevokeLPsTransferors(_lender2, transferors); - _pool.revokeLPsTransferors(transferors); + emit RevokeLPTransferors(_lender2, transferors); + _pool.revokeLPTransferors(transferors); assertFalse(_pool.approvedTransferors(_lender2, _lender)); // transfer initiated by lender should fail as it is no longer an approved transferor changePrank(_lender); vm.expectRevert(IPoolErrors.TransferorNotApproved.selector); - _pool.transferLPs(_lender1, _lender2, indexes); + _pool.transferLP(_lender1, _lender2, indexes); // reapprove transferor changePrank(_lender2); vm.expectEmit(true, true, false, true); - emit ApproveLPsTransferors(_lender2, transferors); - _pool.approveLPsTransferors(transferors); + emit ApproveLPTransferors(_lender2, transferors); + _pool.approveLPTransferors(transferors); assertTrue(_pool.approvedTransferors(_lender2, _lender)); - // transfer LPs for all indexes - _transferLPs({ + // transfer LP for all indexes + _transferLP({ operator: _lender, from: _lender1, to: _lender2, @@ -678,7 +678,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferLPsAllowances() external tearDown { + function testTransferLPAllowances() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; @@ -691,8 +691,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { changePrank(_lender1); vm.expectEmit(true, true, false, true); - emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + emit IncreaseLPAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); // check allowance after increasing allowance _assertLpAllowance({ @@ -710,8 +710,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 5_000 * 1e18; amounts[1] = 5_000 * 1e18; vm.expectEmit(true, true, false, true); - emit DecreaseLPsAllowance(_lender1, _lender2, indexes, amounts); - _pool.decreaseLPsAllowance(_lender2, indexes, amounts); + emit DecreaseLPAllowance(_lender1, _lender2, indexes, amounts); + _pool.decreaseLPAllowance(_lender2, indexes, amounts); // check allowances after decreasing allowance _assertLpAllowance({ @@ -735,8 +735,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts[0] = 0; amounts[1] = 0; vm.expectEmit(true, true, false, true); - emit RevokeLPsAllowance(_lender1, _lender2, indexes); - _pool.revokeLPsAllowance(_lender2, indexes); + emit RevokeLPAllowance(_lender1, _lender2, indexes); + _pool.revokeLPAllowance(_lender2, indexes); // check allowance after revoking allowance _assertLpAllowance({ @@ -758,8 +758,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { amounts = new uint256[](1); amounts[0] = 5_000 * 1e18; vm.expectEmit(true, true, false, true); - emit IncreaseLPsAllowance(_lender1, _lender2, indexes, amounts); - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + emit IncreaseLPAllowance(_lender1, _lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); _assertLpAllowance({ owner: _lender1, @@ -769,7 +769,7 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { }); } - function testTransferPartialLPs() external tearDown { + function testTransferPartialLP() external tearDown { uint256[] memory indexes = new uint256[](2); indexes[0] = 2550; indexes[1] = 2551; @@ -789,20 +789,20 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { uint256[] memory amounts = new uint256[](2); amounts[0] = 5_000 * 1e18; amounts[1] = 10_000 * 1e18; - _pool.increaseLPsAllowance(_lender2, indexes, amounts); + _pool.increaseLPAllowance(_lender2, indexes, amounts); amounts = new uint256[](2); amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; - _pool.increaseLPsAllowance(_lender, indexes, amounts); + _pool.increaseLPAllowance(_lender, indexes, amounts); - // lender 2 approves lender as transferor of LPs + // lender 2 approves lender as transferor of LP changePrank(_lender2); address[] memory transferors = new address[](1); transferors[0] = _lender; - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); - // lender transfers allowed LPs from lender1 - _transferLPs({ + // lender transfers allowed LP from lender1 + _transferLP({ operator: _lender, from: _lender1, to: _lender, @@ -816,8 +816,8 @@ contract ERC20PoolTransferLPsTest is ERC20HelperContract { lpAllowance: 0 }); - // lender transfers allowed LPs from lender1 to lender2 - _transferLPs({ + // lender transfers allowed LP from lender1 to lender2 + _transferLP({ operator: _lender, from: _lender1, to: _lender2, diff --git a/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol index 5a2dd7015..b2e425ae9 100644 --- a/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol @@ -92,7 +92,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { uint256 noOfBucketNftsRedeemable = _wadToIntRoundingDown(bucketCollateral); // Calculating redeemable Quote and Collateral Token for Lenders lps - uint256 lpsAsCollateral = _poolUtils.lpsToCollateral(address(_pool), lenderLpBalance, bucketIndex); + uint256 lpsAsCollateral = _poolUtils.lpToCollateral(address(_pool), lenderLpBalance, bucketIndex); // Deposit additional quote token to redeem for all NFTs uint256 lpsRedeemed; @@ -109,7 +109,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { Token(_pool.quoteTokenAddress()).approve(address(_pool) , depositRequired); _pool.addQuoteToken(depositRequired, bucketIndex, block.timestamp + 1 minutes); (lenderLpBalance, ) = _pool.lenderInfo(bucketIndex, lender); - lpsAsCollateral = _poolUtils.lpsToCollateral(address(_pool), lenderLpBalance, bucketIndex); + lpsAsCollateral = _poolUtils.lpToCollateral(address(_pool), lenderLpBalance, bucketIndex); } // First redeem LP for collateral @@ -646,13 +646,13 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { ERC721Pool(address(_pool)).removeCollateral(amount, index); } - function _assertRemoveCollateralInsufficientLPsRevert( + function _assertRemoveCollateralInsufficientLPRevert( address from, uint256 amount, uint256 index ) internal { changePrank(from); - vm.expectRevert(IPoolErrors.InsufficientLPs.selector); + vm.expectRevert(IPoolErrors.InsufficientLP.selector); _pool.removeCollateral(amount, index); } } diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol index d1f1a8015..ab7441a50 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol @@ -538,7 +538,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); // should revert if the actor does not have any LP to remove a token - _assertRemoveCollateralInsufficientLPsRevert({ + _assertRemoveCollateralInsufficientLPRevert({ from: _borrower2, amount: 1, index: 1530 @@ -1017,7 +1017,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { assertEq(_collateral.balanceOf(_borrower), 50); assertEq(_collateral.balanceOf(address(_pool)), 1); - // lender merge his entitled collateral (based on their LPs) in bucket 3069 + // lender merge his entitled collateral (based on their LP) in bucket 3069 uint256[] memory removalIndexes = new uint256[](10); uint256 removalI = 0; for (uint256 i = 3060; i < (3060 + 10); i++) { @@ -1086,14 +1086,14 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { from: _lender, amount: 10 * 1e18, index: 7388, - lpAward: 10 * 1e18, // LPs awarded to lender for depositing quote tokens in bucket 7388 + lpAward: 10 * 1e18, // LP awarded to lender for depositing quote tokens in bucket 7388 newLup: MAX_PRICE }); _assertLenderLpBalance({ lender: _lender, index: 7388, - lpBalance: 10 * 1e18, // lender now owns LPs in bucket 7388 which can be used to merge bucket collateral + lpBalance: 10 * 1e18, // lender now owns LP in bucket 7388 which can be used to merge bucket collateral depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) }); @@ -1125,7 +1125,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { }); _assertBucket({ index: 7388, - lpBalance: 10 * 1e18, // LPs in bucket 7388 diminished when NFT merged and removed + lpBalance: 10 * 1e18, // LP in bucket 7388 diminished when NFT merged and removed collateral: 0, // no collateral remaining as it was merged and removed deposit: 10 * 1e18, exchangeRate: 1 * 1e18 @@ -1133,13 +1133,13 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertLenderLpBalance({ lender: _lender, index: 7388, - lpBalance: 10 * 1e18, // lender LPs decreased with the amount used to merge NFT + lpBalance: 10 * 1e18, // lender LP decreased with the amount used to merge NFT depositTime: _startTime + 10000 days + (32 hours + 4210 minutes) }); _assertLenderLpBalance({ lender: _borrower, index: 7388, - lpBalance: 0, // Borrower LPs remain the same in the bucket + lpBalance: 0, // Borrower LP remain the same in the bucket depositTime: 0 }); @@ -1153,7 +1153,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBucket({ index: 7388, - lpBalance: 0, // LPs in bucket 7388 diminished when NFT merged and removed + lpBalance: 0, // LP in bucket 7388 diminished when NFT merged and removed collateral: 0, // no collateral remaining as it was merged and removed deposit: 0, exchangeRate: 1 * 1e18 diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol index 1aa721c32..2bd1218dd 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol @@ -259,7 +259,7 @@ contract ERC721PoolLiquidationsDepositTakeTest is ERC721HelperContract { neutralPrice: 0 }) ); - // borrower is compensated LPs for fractional collateral + // borrower is compensated LP for fractional collateral _assertLenderLpBalance({ lender: _borrower, index: 3519, diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol index 61f3394f0..bc5ebbfe1 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol @@ -332,7 +332,7 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { }); // should revert if lender2 attempts to remove more collateral than lp is available for - _assertRemoveCollateralInsufficientLPsRevert({ + _assertRemoveCollateralInsufficientLPRevert({ from: _lender2, amount: 1, index: 2350 diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol index f1ca3fddc..28a2f5219 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol @@ -118,7 +118,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); // kick off a new auction - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: 823.269088761017732090 * 1e18, price: 1_000_000_000 * 1e18, @@ -189,7 +189,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); // kick off a new auction - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: 823.269088761017732090 * 1e18, price: 1_000_000_000 * 1e18, @@ -251,7 +251,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { uint256 expectedQuoteBalance = _quote.balanceOf(_bidder) + kickAward; expectedReserves -= kickAward; - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: expectedReserves, price: expectedPrice, @@ -370,7 +370,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { uint256 kickAward = Maths.wmul(expectedReserves, 0.01 * 1e18); expectedReserves -= kickAward; - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: expectedReserves, price: expectedPrice, @@ -445,7 +445,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { expectedPrice = 1_000_000_000 * 1e18; kickAward = Maths.wmul(newClaimableReserves, 0.01 * 1e18); expectedReserves += newClaimableReserves - kickAward; - _startClaimableReserveAuction({ + _kickReserveAuction({ from: _bidder, remainingReserves: expectedReserves, price: expectedPrice, diff --git a/tests/forge/unit/PositionManager.t.sol b/tests/forge/unit/PositionManager.t.sol index 6a5832e1a..a5c553905 100644 --- a/tests/forge/unit/PositionManager.t.sol +++ b/tests/forge/unit/PositionManager.t.sol @@ -31,11 +31,11 @@ abstract contract PositionManagerERC20PoolHelperContract is ERC20HelperContract _quote.approve(address(_pool), type(uint256).max); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); vm.prank(operator_); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); } /** @@ -99,7 +99,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check position info address owner = _positionManager.ownerOf(tokenId); - uint256 lps = _positionManager.getLPs(tokenId, _indexOf(mintPrice)); + uint256 lps = _positionManager.getLP(tokenId, _indexOf(mintPrice)); assertEq(owner, testAddress); assertEq(lps, 0); @@ -115,10 +115,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract /** * @notice Tests attachment of a created position to an already existing NFT. - * LPs are checked to verify ownership of position. + * LP are checked to verify ownership of position. * Reverts: * Attempts to memorialize when lps aren't allowed to be transfered. - * Attempts to set position owner when not owner of the LPs. + * Attempts to set position owner when not owner of the LP. */ function testMemorializePositions() external { address testAddress = makeAddr("testAddress"); @@ -159,7 +159,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenId, indexes ); - // should revert if access hasn't been granted to transfer LPs + // should revert if access hasn't been granted to transfer LP vm.expectRevert(IPoolErrors.NoAllowance.selector); _positionManager.memorializePositions(memorializeParams); @@ -168,21 +168,21 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testAddress, address(_positionManager), indexes, 9_000 * 1e18); + emit TransferLP(testAddress, address(_positionManager), indexes, 9_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId, indexes); _positionManager.memorializePositions(memorializeParams); // check memorialization success - uint256 positionAtPriceOneLPs = _positionManager.getLPs(tokenId, indexes[0]); + uint256 positionAtPriceOneLPs = _positionManager.getLP(tokenId, indexes[0]); assertGt(positionAtPriceOneLPs, 0); // check lps at non added to price - uint256 positionAtWrongPriceLPs = _positionManager.getLPs(tokenId, uint256(MAX_BUCKET_INDEX)); + uint256 positionAtWrongPriceLPs = _positionManager.getLP(tokenId, uint256(MAX_BUCKET_INDEX)); assertEq(positionAtWrongPriceLPs, 0); assertTrue(_positionManager.isIndexInPosition(tokenId, 2550)); @@ -221,7 +221,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // mint an NFT to later memorialize existing positions into uint256 tokenId = _mintNFT(testAddress, testAddress, address(_pool)); - // check LPs + // check LP _assertLenderLpBalance({ lender: testAddress, index: indexes[0], @@ -260,9 +260,9 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -276,11 +276,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testAddress, address(_positionManager), indexes, 9_000 * 1e18); + emit TransferLP(testAddress, address(_positionManager), indexes, 9_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId, indexes); _positionManager.memorializePositions(memorializeParams); @@ -323,9 +323,9 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -399,22 +399,22 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); - // allow position manager to take ownership of the new LPs + // allow position manager to take ownership of the new LP amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testAddress, address(_positionManager), indexes, 6_000 * 1e18); + emit TransferLP(testAddress, address(_positionManager), indexes, 6_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress, tokenId, indexes); _positionManager.memorializePositions(memorializeParams); @@ -458,9 +458,9 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 4_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 5_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 4_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 5_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 6_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -468,7 +468,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract /** * @notice Tests attachment of multiple previously created position to already existing NFTs. - * LPs are checked to verify ownership of position. + * LP are checked to verify ownership of position. */ function testMemorializeMultiple() external { address testLender1 = makeAddr("testLender1"); @@ -516,7 +516,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256 tokenId1 = _mintNFT(testLender1, testLender1, address(_pool)); uint256 tokenId2 = _mintNFT(testLender2, testLender2, address(_pool)); - // check LPs + // check LP _assertLenderLpBalance({ lender: testLender1, index: indexes[0], @@ -590,12 +590,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract depositTime: 0 }); - assertEq(_positionManager.getLPs(tokenId1, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId1, indexes[1]), 0); - assertEq(_positionManager.getLPs(tokenId1, indexes[2]), 0); + assertEq(_positionManager.getLP(tokenId1, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId1, indexes[1]), 0); + assertEq(_positionManager.getLP(tokenId1, indexes[2]), 0); - assertEq(_positionManager.getLPs(tokenId2, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId2, indexes[3]), 0); + assertEq(_positionManager.getLP(tokenId2, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId2, indexes[3]), 0); (uint256 poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); @@ -620,11 +620,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), transferIndexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), transferIndexes, amounts); // memorialize lender 1 quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testLender1, address(_positionManager), lender1Indexes, 9_000 * 1e18); + emit TransferLP(testLender1, address(_positionManager), lender1Indexes, 9_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testLender1, tokenId1, lender1Indexes); _positionManager.memorializePositions(memorializeParams); @@ -679,9 +679,9 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract depositTime: 0 }); - assertEq(_positionManager.getLPs(tokenId1, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId1, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId1, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[2]), 3_000 * 1e18); (poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); @@ -694,7 +694,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract amounts = new uint256[](2); amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), transferIndexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), transferIndexes, amounts); // memorialize lender 2 quote tokens into minted NFT uint256[] memory newIndexes = new uint256[](2); @@ -706,7 +706,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract ); vm.expectEmit(true, true, true, true); - emit TransferLPs(testLender2, address(_positionManager), newIndexes, 6_000 * 1e18); + emit TransferLP(testLender2, address(_positionManager), newIndexes, 6_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testLender2, tokenId2, newIndexes); _positionManager.memorializePositions(memorializeParams); @@ -761,12 +761,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract depositTime: _startTime }); - assertEq(_positionManager.getLPs(tokenId1, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId1, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId1, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId1, indexes[2]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, indexes[3]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId2, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId2, indexes[3]), 3_000 * 1e18); (poolSize, , , , ) = _poolUtils.poolLoansInfo(address(_pool)); assertEq(poolSize, 15_000 * 1e18); @@ -871,7 +871,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndex), 0); + assertEq(_positionManager.getLP(tokenId, testIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndex)); // memorialize positions @@ -880,11 +880,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 2_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1001,8 +1001,8 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract changePrank(testMinter); _pool.addQuoteToken(amounts[0], _i9_91, type(uint256).max); - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); - _pool.approveLPsTransferors(transferors); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); + _pool.approveLPTransferors(transferors); memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes ); @@ -1076,7 +1076,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); - // check LPs + // check LP _assertLenderLpBalance({ lender: testMinter, index: testIndexPrice, @@ -1097,7 +1097,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions @@ -1106,12 +1106,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // allow position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1139,7 +1139,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // approve and transfer NFT to different address @@ -1161,7 +1161,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check new owner can redeem positions changePrank(testReceiver); // allow position manager as transferor - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -1186,7 +1186,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } @@ -1215,7 +1215,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // check owner assertEq(_positionManager.ownerOf(tokenId), testMinter); - // check LPs + // check LP _assertLenderLpBalance({ lender: testMinter, index: testIndexPrice, @@ -1236,7 +1236,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions @@ -1245,7 +1245,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -1273,7 +1273,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // deploy spender contract @@ -1303,7 +1303,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -1328,7 +1328,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } @@ -1449,12 +1449,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; // allow position manager to take ownership of the position of testMinter - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // approve position manager as a transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1578,10 +1578,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId1, moveIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId1, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1593,7 +1593,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[0] = mintIndex; uint256[] memory amounts = new uint256[](1); amounts[0] = 2_500 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testAddress1 IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1641,10 +1641,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId1, mintIndex), 2_500 * 1e18); - assertEq(_positionManager.getLPs(tokenId1, moveIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId1, mintIndex), 2_500 * 1e18); + assertEq(_positionManager.getLP(tokenId1, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId2, moveIndex), 0); assertTrue(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1702,10 +1702,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLP(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1714,7 +1714,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testAddress2 changePrank(testAddress2); amounts[0] = 5_500 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize positions of testAddress2 memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -1762,10 +1762,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, mintIndex), 5_500 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, moveIndex), 0); + assertEq(_positionManager.getLP(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLP(tokenId2, mintIndex), 5_500 * 1e18); + assertEq(_positionManager.getLP(tokenId2, moveIndex), 0); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1830,10 +1830,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId1, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId1, moveIndex), 2_500 * 1e18); - assertEq(_positionManager.getLPs(tokenId2, mintIndex), 0); - assertEq(_positionManager.getLPs(tokenId2, moveIndex), 5_500 * 1e18); + assertEq(_positionManager.getLP(tokenId1, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId1, moveIndex), 2_500 * 1e18); + assertEq(_positionManager.getLP(tokenId2, mintIndex), 0); + assertEq(_positionManager.getLP(tokenId2, moveIndex), 5_500 * 1e18); assertFalse(_positionManager.isIndexInPosition(tokenId1, mintIndex)); assertTrue(_positionManager.isIndexInPosition(tokenId1, moveIndex)); assertFalse(_positionManager.isIndexInPosition(tokenId2, mintIndex)); @@ -1891,7 +1891,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract indexes[0] = mintIndex; uint256[] memory amounts = new uint256[](1); amounts[0] = 2_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams(tokenId1, indexes); _positionManager.memorializePositions(memorializeParams); @@ -1900,7 +1900,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract changePrank(lender2); uint256 tokenId2 = _mintNFT(lender2, lender2, address(_pool)); amounts[0] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams(tokenId2, indexes); _positionManager.memorializePositions(memorializeParams); skip(1 days); @@ -1952,7 +1952,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract changePrank(lender1); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); indexes[0] = moveIndex; IPositionManagerOwnerActions.RedeemPositionsParams memory reedemParams = IPositionManagerOwnerActions.RedeemPositionsParams( @@ -1974,7 +1974,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // lender2 redeems their NFT skip(1 days); changePrank(lender2); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); indexes[0] = mintIndex; reedemParams = IPositionManagerOwnerActions.RedeemPositionsParams( tokenId2, address(_pool), indexes @@ -2018,7 +2018,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions @@ -2027,11 +2027,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -2054,7 +2054,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // redeem positions of testMinter @@ -2088,7 +2088,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // should fail if trying to redeem one more time @@ -2185,7 +2185,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // memorialize positions @@ -2194,11 +2194,11 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position of testMinter uint256[] memory amounts = new uint256[](1); amounts[0] = 15_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize positions of testMinter IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenId, indexes @@ -2244,7 +2244,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 15_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 15_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); // approve and transfer NFT to different address @@ -2273,10 +2273,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract ); changePrank(testReceiver); vm.expectEmit(true, true, true, true); - emit TransferLPs(address(_positionManager), testReceiver, indexes, 15_000 * 1e18); + emit TransferLP(address(_positionManager), testReceiver, indexes, 15_000 * 1e18); vm.expectEmit(true, true, true, true); emit RedeemPosition(testReceiver, tokenId, indexes); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _positionManager.reedemPositions(reedemParams); // check pool state @@ -2318,7 +2318,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, testIndexPrice), 0); + assertEq(_positionManager.getLP(tokenId, testIndexPrice), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, testIndexPrice)); } @@ -2360,10 +2360,10 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // 3rd party minter mints NFT and memorialize lender positions uint256 tokenId = _mintNFT(minter, lender, address(_pool)); @@ -2504,7 +2504,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 10_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // 3rd party minter mints NFT and memorialize lender positions changePrank(minter); @@ -2520,7 +2520,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.safeTransferFrom(lender, minter, tokenId); assertEq(_positionManager.ownerOf(tokenId), minter); - // minter is owner so can reddeem LPs + // minter is owner so can reddeem LP changePrank(minter); IPositionManagerOwnerActions.RedeemPositionsParams memory reedemParams = IPositionManagerOwnerActions.RedeemPositionsParams( tokenId, address(_pool), indexes @@ -2529,7 +2529,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // minter approves position manager as a transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _positionManager.reedemPositions(reedemParams); @@ -2611,7 +2611,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position uint256[] memory amounts = new uint256[](1); amounts[0] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize position IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -2687,19 +2687,19 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract // allow position manager to take ownership of the position changePrank(addresses[i]); - _pool.approveLPsTransferors(transferors); - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.approveLPTransferors(transferors); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(addresses[i], address(_positionManager), indexes, amounts[0]); + emit TransferLP(addresses[i], address(_positionManager), indexes, amounts[0]); vm.expectEmit(true, true, true, true); emit MemorializePosition(addresses[i], tokenIds[i], indexes); _positionManager.memorializePositions(memorializeParams); } - // LPs transferred to position manager + // LP transferred to position manager (lpBalance, depositTime) = _pool.lenderInfo(indexes[0], alice); assertEq(lpBalance, 0); assertEq(depositTime, aliceDepositTime); @@ -2808,7 +2808,7 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac // mint an NFT to later memorialize existing positions into uint256 tokenId = _mintNFT(testAddress1, testAddress1, address(_pool), keccak256("ERC721_NON_SUBSET_HASH")); - // check LPs + // check LP _assertLenderLpBalance({ lender: testAddress1, index: indexes[0], @@ -2847,9 +2847,9 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -2863,16 +2863,16 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac amounts[0] = 3_000 * 1e18; amounts[1] = 3_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor address[] memory transferors = new address[](1); transferors[0] = address(_positionManager); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // memorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testAddress1, address(_positionManager), indexes, 9_000 * 1e18); + emit TransferLP(testAddress1, address(_positionManager), indexes, 9_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress1, tokenId, indexes); _positionManager.memorializePositions(memorializeParams); @@ -2915,9 +2915,9 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -2978,26 +2978,26 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 3_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 3_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 3_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); - // allow position manager to take ownership of the new LPs + // allow position manager to take ownership of the new LP amounts = new uint256[](3); amounts[0] = 1_000 * 1e18; amounts[1] = 2_000 * 1e18; amounts[2] = 3_000 * 1e18; - _pool.increaseLPsAllowance(address(_positionManager), indexes, amounts); + _pool.increaseLPAllowance(address(_positionManager), indexes, amounts); // approve position manager as transferor - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); // rememorialize quote tokens into minted NFT vm.expectEmit(true, true, true, true); - emit TransferLPs(testAddress1, address(_positionManager), indexes, 6_000 * 1e18); + emit TransferLP(testAddress1, address(_positionManager), indexes, 6_000 * 1e18); vm.expectEmit(true, true, true, true); emit MemorializePosition(testAddress1, tokenId, indexes); _positionManager.memorializePositions(memorializeParams); @@ -3041,9 +3041,9 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 4_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 5_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 4_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 5_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 6_000 * 1e18); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -3100,9 +3100,9 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 9_000 * 1e18); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 6_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 9_000 * 1e18); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 6_000 * 1e18); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertTrue(_positionManager.isIndexInPosition(tokenId, indexes[2])); @@ -3142,7 +3142,7 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac // check new owner can redeem positions changePrank(testAddress2); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); _positionManager.reedemPositions(reedemParams); // check pool state @@ -3202,9 +3202,9 @@ contract PositionManagerERC721PoolTest is PositionManagerERC721PoolHelperContrac }); // check position manager state - assertEq(_positionManager.getLPs(tokenId, indexes[0]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[1]), 0); - assertEq(_positionManager.getLPs(tokenId, indexes[2]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[0]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[1]), 0); + assertEq(_positionManager.getLP(tokenId, indexes[2]), 0); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[0])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[1])); assertFalse(_positionManager.isIndexInPosition(tokenId, indexes[2])); diff --git a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol index c380808fc..a54ae2526 100644 --- a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol +++ b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol @@ -308,16 +308,16 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { ERC20Pool(address(pool)).repayDebt(borrower, Maths.wdiv(borrowAmount, Maths.wad(2)), 0, borrower, MAX_FENWICK_INDEX); // start reserve auction - _startClaimableReserveAuction(address(pool), _bidder); + _kickReserveAuction(address(pool), _bidder); } - function _startClaimableReserveAuction( + function _kickReserveAuction( address pool, address bidder ) internal { changePrank(bidder); _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); + ERC20Pool(address(pool)).kickReserveAuction(); } function _mintAndMemorializePositionNFT( @@ -348,7 +348,7 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { (lpBalances[i], ) = ERC20Pool(address(pool)).lenderInfo(indexes[i], minter); } - ERC20Pool(address(pool)).increaseLPsAllowance(address(_positionManager), indexes, lpBalances); + ERC20Pool(address(pool)).increaseLPAllowance(address(_positionManager), indexes, lpBalances); // construct memorialize params struct IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( @@ -395,11 +395,11 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { // start reserve auction changePrank(_bidder); _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); + ERC20Pool(address(pool)).kickReserveAuction(); // Can't trigger reserve auction if less than two weeks have passed since last auction vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - ERC20Pool(address(pool)).startClaimableReserveAuction(); + ERC20Pool(address(pool)).kickReserveAuction(); // allow time to pass for the reserve price to decrease skip(24 hours); @@ -445,11 +445,11 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { // start reserve auction changePrank(_bidder); _ajnaToken.approve(address(pool), type(uint256).max); - ERC20Pool(address(pool)).startClaimableReserveAuction(); + ERC20Pool(address(pool)).kickReserveAuction(); // Can't trigger reserve auction if less than two weeks have passed since last auction vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - ERC20Pool(address(pool)).startClaimableReserveAuction(); + ERC20Pool(address(pool)).kickReserveAuction(); // allow time to pass for the reserve price to decrease skip(24 hours); diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index 58a24afdb..722e4bffa 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -421,11 +421,11 @@ contract RewardsManagerTest is RewardsHelperContract { changePrank(_minterOne); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); changePrank(_minterTwo); _quote.approve(address(_positionManager), type(uint256).max); - _pool.approveLPsTransferors(transferors); + _pool.approveLPTransferors(transferors); /*****************************/ /*** Initialize Pool State ***/ @@ -593,7 +593,7 @@ contract RewardsManagerTest is RewardsHelperContract { _pool.updateInterest(); // start reserve auction - _startClaimableReserveAuction({ + _kickReserveAuction({ pool: address(_pool), bidder: _bidder }); @@ -702,7 +702,7 @@ contract RewardsManagerTest is RewardsHelperContract { /*** First Reserve Auction ***/ /*****************************/ // start reserve auction - _startClaimableReserveAuction({ + _kickReserveAuction({ pool: address(_pool), bidder: _bidder }); diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index f6a4f9f8f..ad24f0e89 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -12,7 +12,7 @@ import 'src/interfaces/pool/commons/IPoolEvents.sol'; import 'src/interfaces/pool/IERC3156FlashBorrower.sol'; import 'src/PoolInfoUtils.sol'; -import 'src/libraries/external/Auctions.sol'; +import { _auctionPrice, _bpf, MAX_PRICE } from 'src/libraries/helpers/PoolHelper.sol'; abstract contract DSTestPlus is Test, IPoolEvents { @@ -361,7 +361,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { assertEq(lpRedeemed, lpRedeem); } - function _startClaimableReserveAuction( + function _kickReserveAuction( address from, uint256 remainingReserves, uint256 price, @@ -369,8 +369,8 @@ abstract contract DSTestPlus is Test, IPoolEvents { ) internal { changePrank(from); vm.expectEmit(true, true, true, true); - emit ReserveAuction(remainingReserves, price, epoch); - _pool.startClaimableReserveAuction(); + emit KickReserveAuction(remainingReserves, price, epoch); + _pool.kickReserveAuction(); } function _take( @@ -466,7 +466,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { assertEq(vars.auctionKickTime, state_.kickTime); assertEq(vars.auctionKickMomp, state_.kickMomp); assertEq(vars.auctionTotalBondEscrowed, state_.totalBondEscrowed); - assertEq(Auctions._auctionPrice( + assertEq(_auctionPrice( vars.auctionKickMomp, vars.auctionNeutralPrice, vars.auctionKickTime), state_.auctionPrice); @@ -764,7 +764,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { function _assertReserveAuctionTooSoon() internal { vm.expectRevert(IPoolErrors.ReserveAuctionTooSoon.selector); - _pool.startClaimableReserveAuction(); + _pool.kickReserveAuction(); } /**********************/ @@ -1137,13 +1137,13 @@ abstract contract DSTestPlus is Test, IPoolEvents { _pool.removeQuoteToken(amount, index); } - function _assertRemoveLiquidityInsufficientLPsRevert( + function _assertRemoveLiquidityInsufficientLPRevert( address from, uint256 amount, uint256 index ) internal { changePrank(from); - vm.expectRevert(IPoolErrors.InsufficientLPs.selector); + vm.expectRevert(IPoolErrors.InsufficientLP.selector); _pool.removeQuoteToken(amount, index); } @@ -1330,7 +1330,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { function _assertTakeReservesNoReservesRevert() internal { vm.expectRevert(IPoolErrors.NoReserves.selector); - _pool.startClaimableReserveAuction(); + _pool.kickReserveAuction(); } function _lup() internal view returns (uint256 lup_) { From 25312a999c7c5f04246b58646e6d65bb43274430 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 13 Apr 2023 09:16:42 +0300 Subject: [PATCH 55/70] Fix warning / testMoveStakedLiquidity test: remove return statement --- tests/forge/unit/Rewards/RewardsManager.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index 722e4bffa..191212bc0 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -1091,8 +1091,6 @@ contract RewardsManagerTest is RewardsHelperContract { lpsAwarded: secondLpsAwarded, expiry: block.timestamp + 1000 }); - return; - /******************************/ /*** Second Reserve Auction ***/ From 5b77b6bcdd162931909936b8d6b70055002b3ada Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Thu, 13 Apr 2023 19:38:21 +0530 Subject: [PATCH 56/70] Add Liquidation, Reserve invariants (#730) * Add liquidation and reserve pool handlers and invariants for ERC721Pool * Restructure Liquidation Pool Handler * Resolve out of gas error and find optimum buckets for 1e28 max quote token amount for NFT pool * Add evm error regression test * Update failing regression test * Split MIN and MAX token limits for ERC20 and ERC721 Pool in invariants * Fix arithmetic overflow/underflow in local fewick remove * Add ZeroThresholdPrice() revert in _ensurePoolError * Update CT4 invariant * Add failing regression test * Rebalance NFT token ids on settle only when - entire bad debt is settled (and not on partial settle) or - if entire amount of pledged collateral was used to settle bad debt (but there's still debt to be settled from reserves / deposits) * Fix CT2 and evm reverts in NFT invariants * Fix CT2 by recording collateral deposited at other buckets * Fix bug * Update MAX_COLLATERAL_AMOUNT to 100 * Revert "Rebalance NFT token ids on settle only when" This reverts commit 87d2dd4dac2c63d948a520cf5f1044184969a289. * Comment CT4 failing regression test * Update collateral calculation in draw debt handler --------- Co-authored-by: grandizzy --- foundry.toml | 4 +- tests/INVARIANTS.md | 2 +- .../ERC20Pool/BasicERC20PoolInvariants.t.sol | 8 +- .../LiquidationERC20PoolInvariants.t.sol | 2 +- .../ReserveERC20PoolInvariants.t.sol | 18 -- .../handlers/BasicERC20PoolHandler.sol | 23 +-- .../handlers/LiquidationERC20PoolHandler.sol | 66 +------- .../handlers/ReserveERC20PoolHandler.sol | 1 - .../unbounded/BaseERC20PoolHandler.sol | 17 ++ .../UnboundedBasicERC20PoolHandler.sol | 29 ++-- .../UnboundedLiquidationERC20PoolHandler.sol | 101 ----------- .../BasicERC721PoolInvariants.t.sol | 20 +-- .../LiquidationERC721PoolInvariants.t.sol | 38 +++++ .../ReserveERC721PoolInvariants.t.sol | 38 +++++ .../handlers/BasicERC721PoolHandler.sol | 30 ++-- .../handlers/LiquidationERC721PoolHandler.sol | 26 +++ .../handlers/ReserveERC721PoolHandler.sol | 21 +++ .../unbounded/BaseERC721PoolHandler.sol | 24 ++- .../UnboundedBasicERC721PoolHandler.sol | 84 ++++++++-- .../forge/invariants/base/BaseInvariants.sol | 3 + .../invariants/base/BasicInvariants.t.sol | 4 - .../base/handlers/BasicPoolHandler.sol | 12 +- .../base/handlers/LiquidationPoolHandler.sol | 132 +++++++++++---- .../base/handlers/ReservePoolHandler.sol | 3 +- .../base/handlers/unbounded/BaseHandler.sol | 35 ++-- .../UnboundedLiquidationPoolHandler.sol | 157 ++++++++++++++++-- .../invariants/interfaces/IBaseHandler.sol | 5 + .../RegressionTestBasicERC721Pool.t.sol | 46 +++++ .../RegressionTestLiquidationERC20Pool.t.sol | 57 +++++++ .../RegressionTestReservesERC721Pool.t.sol | 66 ++++++++ 30 files changed, 728 insertions(+), 344 deletions(-) delete mode 100644 tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol create mode 100644 tests/forge/invariants/ERC721Pool/LiquidationERC721PoolInvariants.t.sol create mode 100644 tests/forge/invariants/ERC721Pool/ReserveERC721PoolInvariants.t.sol create mode 100644 tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol create mode 100644 tests/forge/invariants/ERC721Pool/handlers/ReserveERC721PoolHandler.sol create mode 100644 tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol create mode 100644 tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol create mode 100644 tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol diff --git a/foundry.toml b/foundry.toml index b60dd03d0..dcf017131 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,6 +25,6 @@ runs = 300 [invariant] runs = 1000 # Number of times that a sequence of function calls is generated and run -depth = 200 # Number of function calls made in a given run. +depth = 20 # Number of function calls made in a given run. call_override = false # Override calls -fail_on_revert = false # Fail the test if the contract reverts \ No newline at end of file +fail_on_revert = true # Fail the test if the contract reverts \ No newline at end of file diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 68a0cb87f..24866d478 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -6,7 +6,7 @@ - #### NFT: - **CT2**: number of tokens owned by the pool (`Collateral.balanceOf(pool)`) * `1e18` = sum of collateral across all borrowers (`Borrower.collateral`) + sum of claimable collateral across all buckets (`Bucket.collateral`) - **CT3**: number of tokens owned by the pool (`Collateral.balanceOf(pool)` = length of borrower array token ids (`ERC721Pool.borrowerTokenIds.length`) + length of buckets array token ids (`ERC721Pool.bucketTokenIds.length`) - - **CT4**: number of borrower token ids (`ERC721Pool.borrowerTokenIds.length`) * `1e18` <= borrower balance (`Borrower.collateral`) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids + - **CT4**: number of borrower token ids (`ERC721Pool.borrowerTokenIds.length`) * `1e18` >= borrower balance (`Borrower.collateral`) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids - **CT5**: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) are owned by pool contract (`Collateral.ownerOf(tokenId)`) - **CT6**: in case of subset pools: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) should have a mapping of `True` in allowed token ids mapping (`ERC721Pool.tokenIdsAllowed`) diff --git a/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol index 884cc0363..f1b8d31e2 100644 --- a/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol @@ -11,11 +11,6 @@ import { Maths } from 'src/libraries/internal/Maths.sol'; import { TokenWithNDecimals } from '../../utils/Tokens.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX -} from '../base/handlers/unbounded/BaseHandler.sol'; - import { BasicERC20PoolHandler } from './handlers/BasicERC20PoolHandler.sol'; import { BasicInvariants } from '../base/BasicInvariants.t.sol'; import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; @@ -71,6 +66,9 @@ contract BasicERC20PoolInvariants is BasicInvariants { excludeContract(address(_poolInfo)); excludeContract(address(_impl)); + LENDER_MIN_BUCKET_INDEX = IBaseHandler(_handler).LENDER_MIN_BUCKET_INDEX(); + LENDER_MAX_BUCKET_INDEX = IBaseHandler(_handler).LENDER_MAX_BUCKET_INDEX(); + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_erc20pool), bucketIndex); previousBucketExchangeRate[bucketIndex] = exchangeRate; diff --git a/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol index 07c8c3c27..8baa1aef0 100644 --- a/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/LiquidationERC20PoolInvariants.t.sol @@ -8,7 +8,7 @@ import { BasicInvariants } from '../base/BasicInvariants.t.sol'; import { LiquidationERC20PoolHandler } from './handlers/LiquidationERC20PoolHandler.sol'; import { BasicERC20PoolInvariants } from './BasicERC20PoolInvariants.t.sol'; -contract LiquidationERC20PoolInvariants is LiquidationInvariants, BasicERC20PoolInvariants { +contract LiquidationERC20PoolInvariants is BasicERC20PoolInvariants, LiquidationInvariants { LiquidationERC20PoolHandler internal _liquidationERC20PoolHandler; diff --git a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol index 16afade78..f7e5cf753 100644 --- a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol @@ -11,24 +11,6 @@ import { ReserveERC20PoolHandler } from './handlers/ReserveERC20PoolHandl import { LiquidationERC20PoolInvariants } from './LiquidationERC20PoolInvariants.t.sol'; contract ReserveERC20PoolInvariants is ReserveInvariants, LiquidationERC20PoolInvariants { - - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - * Reserves - * RE1 : Reserves are unchanged by pledging collateral - * RE2 : Reserves are unchanged by removing collateral - * RE3 : Reserves are unchanged by depositing quote token into a bucket - * RE4 : Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired - * RE5 : Reserves are unchanged by adding collateral token into a bucket - * RE6 : Reserves are unchanged by removing collateral token from a bucket - * RE7 : Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) and increase/decrease by bond penalty/reward on take. - * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. - * RE9 : Reserves increase by 3 months of interest when a loan is kicked - * 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 - ****************************************************************************************************************************************/ ReserveERC20PoolHandler internal _reserveERC20PoolHandler; diff --git a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 8df5f77bc..63fd35a17 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol @@ -5,13 +5,6 @@ pragma solidity 0.8.14; import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; import { Maths } from 'src/libraries/internal/Maths.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX, - MIN_AMOUNT, - MAX_AMOUNT -} from '../../base/handlers/unbounded/BaseHandler.sol'; import { BasicPoolHandler } from '../../base/handlers/BasicPoolHandler.sol'; import { UnboundedBasicPoolHandler } from '../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol'; import { UnboundedBasicERC20PoolHandler } from './unbounded/UnboundedBasicERC20PoolHandler.sol'; @@ -139,14 +132,14 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function _preAddCollateral( uint256 amountToAdd_ - ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, MIN_AMOUNT, MAX_AMOUNT); + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); } function _preRemoveCollateral( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); // ensure actor has collateral to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); @@ -156,19 +149,19 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function _prePledgeCollateral( uint256 amountToPledge_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, _erc20Pool.collateralScale(), MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToPledge_, _erc20Pool.collateralScale(), MAX_COLLATERAL_AMOUNT); } function _prePullCollateral( uint256 amountToPull_ - ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPull_, MIN_AMOUNT, MAX_AMOUNT); + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPull_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); } function _preDrawDebt( uint256 amountToBorrow_ ) internal override returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToBorrow_, MIN_AMOUNT, MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToBorrow_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); // Pre Condition // 1. borrower's debt should exceed minDebt @@ -206,7 +199,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function _preRepayDebt( uint256 amountToRepay_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); // ensure actor has debt to repay (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); diff --git a/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol index e7803e730..d15b23e6d 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/LiquidationERC20PoolHandler.sol @@ -2,17 +2,10 @@ pragma solidity 0.8.14; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - MIN_AMOUNT, - MAX_AMOUNT -} from '../../base/handlers/unbounded/BaseHandler.sol'; import { LiquidationPoolHandler } from '../../base/handlers/LiquidationPoolHandler.sol'; -import { UnboundedLiquidationERC20PoolHandler } from './unbounded/UnboundedLiquidationERC20PoolHandler.sol'; import { BasicERC20PoolHandler } from './BasicERC20PoolHandler.sol'; -contract LiquidationERC20PoolHandler is UnboundedLiquidationERC20PoolHandler, LiquidationPoolHandler, BasicERC20PoolHandler { +contract LiquidationERC20PoolHandler is LiquidationPoolHandler, BasicERC20PoolHandler { constructor( address pool_, @@ -26,61 +19,8 @@ contract LiquidationERC20PoolHandler is UnboundedLiquidationERC20PoolHandler, Li } - /****************************/ - /*** Taker Test Functions ***/ - /****************************/ - - function takeAuction( - uint256 borrowerIndex_, - uint256 amount_, - uint256 actorIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { - numberOfCalls['BLiquidationHandler.takeAuction']++; - - amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); - - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - - address borrower = actors[borrowerIndex_]; - address taker = _actor; - - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - - if (kickTime == 0) _kickAuction(borrowerIndex_, amount_ * 100, actorIndex_); - - changePrank(taker); - // skip time to make auction takeable - vm.warp(block.timestamp + 2 hours); - _takeAuction(borrower, amount_, taker); - } - - /******************************/ - /*** Settler Test Functions ***/ - /******************************/ - - function settleAuction( - uint256 actorIndex_, - uint256 borrowerIndex_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - - address borrower = actors[borrowerIndex_]; - uint256 maxDepth = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX; - - address actor = _actor; - - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - - if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); - - changePrank(actor); - // skip time to make auction clearable - vm.warp(block.timestamp + 73 hours); - _settleAuction(borrower, maxDepth); - - _auctionSettleStateReset(borrower); + function _constrictTakeAmount(uint256 amountToTake_) internal view override returns(uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); } } \ No newline at end of file diff --git a/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol index 73ad3e0f1..efbe4d1f4 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/ReserveERC20PoolHandler.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.14; import { UnboundedReservePoolHandler } from '../../base/handlers/unbounded/UnboundedReservePoolHandler.sol'; import { ReservePoolHandler } from '../../base/handlers/ReservePoolHandler.sol'; -import { MIN_AMOUNT } from '../../base/handlers/unbounded/BaseHandler.sol'; import { LiquidationERC20PoolHandler } from './LiquidationERC20PoolHandler.sol'; contract ReserveERC20PoolHandler is ReservePoolHandler, LiquidationERC20PoolHandler { diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol index e695a0c7b..b396d4c0c 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; +import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import { ERC20Pool } from 'src/ERC20Pool.sol'; @@ -12,6 +13,8 @@ import { BaseHandler } from '../../../base/handlers/unbounded/BaseHandler.sol'; abstract contract BaseERC20PoolHandler is BaseHandler { + using EnumerableSet for EnumerableSet.UintSet; + // Token TokenWithNDecimals internal _collateral; @@ -27,6 +30,20 @@ abstract contract BaseERC20PoolHandler is BaseHandler { uint256 numOfActors_, address testContract_ ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { + + LENDER_MIN_BUCKET_INDEX = 2570; + LENDER_MAX_BUCKET_INDEX = 2572; + + MIN_QUOTE_AMOUNT = 1e3; + MAX_QUOTE_AMOUNT = 1e30; + + MIN_COLLATERAL_AMOUNT = 1e3; + MAX_COLLATERAL_AMOUNT = 1e30; + + for (uint256 bucket = LENDER_MIN_BUCKET_INDEX; bucket <= LENDER_MAX_BUCKET_INDEX; bucket++) { + collateralBuckets.add(bucket); + } + // Tokens _collateral = TokenWithNDecimals(collateral_); diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol index 261ac4ef7..35548336e 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedBasicERC20PoolHandler.sol @@ -8,10 +8,6 @@ import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; import { Maths } from "src/libraries/internal/Maths.sol"; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX -} from '../../../base/handlers/unbounded/BaseHandler.sol'; import { UnboundedBasicPoolHandler } from "../../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol"; import { BaseERC20PoolHandler } from './BaseERC20PoolHandler.sol'; @@ -34,16 +30,18 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B (uint256 lpBalanceBeforeAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); - _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; + 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"); + // Post action condition + (uint256 lpBalanceAfterAction, ) = _erc20Pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + } catch (bytes memory err) { + _ensurePoolError(err); + } } function _removeCollateral( @@ -82,7 +80,10 @@ abstract contract UnboundedBasicERC20PoolHandler is UnboundedBasicPoolHandler, B exchangeRateShouldNotChange[bucketIndex] = true; } - _erc20Pool.drawDebt(_actor, 0, 0, amount_); + try _erc20Pool.drawDebt(_actor, 0, 0, amount_) { + } catch (bytes memory err) { + _ensurePoolError(err); + } } function _pullCollateral( diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol deleted file mode 100644 index 575c4d5ca..000000000 --- a/tests/forge/invariants/ERC20Pool/handlers/unbounded/UnboundedLiquidationERC20PoolHandler.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity 0.8.14; - -import { _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; -import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; -import { Maths } from "src/libraries/internal/Maths.sol"; - -import { UnboundedLiquidationPoolHandler } from '../../../base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol'; -import { BaseERC20PoolHandler } from './BaseERC20PoolHandler.sol'; - -abstract contract UnboundedLiquidationERC20PoolHandler is UnboundedLiquidationPoolHandler, BaseERC20PoolHandler { - - /********************************/ - /*** Settler Helper Functions ***/ - /********************************/ - - function _settleAuction( - address borrower_, - uint256 maxDepth_ - ) internal updateLocalStateAndPoolInterest { - ( - uint256 borrowerT0Debt, - uint256 collateral, - ) = _erc20Pool.borrowerInfo(borrower_); - (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); - (uint256 inflator, ) = _erc20Pool.inflatorInfo(); - - try _erc20Pool.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.wmul(collateral, _priceAt(bucketIndex)); - uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; - uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); - - 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.wdiv(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.wdiv(fenwickDeposit, _priceAt(bucketIndex)); - borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); - } - // exchange all collateral with deposit - else { - fenwickDeposits[bucketIndex] -= maxSettleableDebt; - collateral = 0; - borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); - } - } else collateral = 0; - - 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.wdiv(fenwickDeposit, inflator); - } - // bucket deposit is greater than debt - else { - fenwickDeposits[bucketIndex] -= borrowerDebt; - borrowerT0Debt = 0; - } - } - - maxDepth_ -= 1; - } - } - - } catch (bytes memory err) { - _ensurePoolError(err); - } - } -} diff --git a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol index b10d8ac5f..129dcb47e 100644 --- a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol @@ -11,11 +11,6 @@ import { Maths } from 'src/libraries/internal/Maths.sol'; import { NFTCollateralToken } from '../../utils/Tokens.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX -} from '../base/handlers/unbounded/BaseHandler.sol'; - import { BasicERC721PoolHandler } from './handlers/BasicERC721PoolHandler.sol'; import { BasicInvariants } from '../base/BasicInvariants.t.sol'; import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; @@ -30,7 +25,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { * Collateral Token * CT2: number of tokens owned by the pool (Collateral.balanceOf(pool)) * 1e18 = sum of collateral across all borrowers (Borrower.collateral) + sum of claimable collateral across all buckets (Bucket.collateral) * CT3: number of tokens owned by the pool (Collateral.balanceOf(pool) = length of borrower array token ids (ERC721Pool.borrowerTokenIds.length) + length of buckets array token ids (ERC721Pool.bucketTokenIds.length) - * CT4: number of borrower token ids (ERC721Pool.borrowerTokenIds.length) * 1e18 <= borrower balance (Borrower.collateral) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids + * CT4: number of borrower token ids (ERC721Pool.borrowerTokenIds.length) * 1e18 >= borrower balance (Borrower.collateral) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids * CT5: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) are owned by pool contract (Collateral.ownerOf(tokenId)) * CT6: in case of subset pools: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) should have a mapping of True in allowed token ids mapping (ERC721Pool.tokenIdsAllowed) * CT7: total pledged collateral in pool (PoolBalancesState.pledgedCollateral) = sum of collateral balances across all borrowers (Borrower.collateral) @@ -75,6 +70,9 @@ contract BasicERC721PoolInvariants is BasicInvariants { excludeContract(address(_poolInfo)); excludeContract(address(_impl)); + LENDER_MIN_BUCKET_INDEX = IBaseHandler(_handler).LENDER_MIN_BUCKET_INDEX(); + LENDER_MAX_BUCKET_INDEX = IBaseHandler(_handler).LENDER_MAX_BUCKET_INDEX(); + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { ( , , , , ,uint256 exchangeRate) = _poolInfo.bucketInfo(address(_erc721pool), bucketIndex); previousBucketExchangeRate[bucketIndex] = exchangeRate; @@ -89,10 +87,12 @@ contract BasicERC721PoolInvariants is BasicInvariants { function invariant_CT2() public useCurrentTimestamp { uint256 collateralBalance = _collateral.balanceOf(address(_erc721pool)) * 1e18; uint256 bucketCollateral; + uint256 collateral; - for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { - (, uint256 collateral, , , ) = _erc721pool.bucketInfo(bucketIndex); - + uint256[] memory collateralBuckets = IBaseHandler(_handler).getCollateralBuckets(); + for(uint256 i = 0; i < collateralBuckets.length; i++) { + uint256 bucketIndex = collateralBuckets[i]; + (, collateral, , , ) = _erc721pool.bucketInfo(bucketIndex); bucketCollateral += collateral; } @@ -123,7 +123,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { (, uint256 borrowerCollateral, ) = _erc721pool.borrowerInfo(borrower); - assertLe(borrowerTokens * 1e18, borrowerCollateral); + assertGe(borrowerTokens * 1e18, borrowerCollateral); } } diff --git a/tests/forge/invariants/ERC721Pool/LiquidationERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/LiquidationERC721PoolInvariants.t.sol new file mode 100644 index 000000000..504218ed2 --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/LiquidationERC721PoolInvariants.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationInvariants } from '../base/LiquidationInvariants.t.sol'; +import { BaseInvariants } from '../base/BaseInvariants.sol'; +import { BasicInvariants } from '../base/BasicInvariants.t.sol'; +import { LiquidationERC721PoolHandler } from './handlers/LiquidationERC721PoolHandler.sol'; +import { BasicERC721PoolInvariants } from './BasicERC721PoolInvariants.t.sol'; + +contract LiquidationERC721PoolInvariants is BasicERC721PoolInvariants, LiquidationInvariants { + + LiquidationERC721PoolHandler internal _liquidationERC721PoolHandler; + + function setUp() public override(BaseInvariants, BasicERC721PoolInvariants) virtual{ + + super.setUp(); + + excludeContract(address(_basicERC721PoolHandler)); + + _liquidationERC721PoolHandler = new LiquidationERC721PoolHandler( + address(_erc721pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_liquidationERC721PoolHandler); + } + + function invariant_call_summary() public virtual override(BasicInvariants, LiquidationInvariants) useCurrentTimestamp { + super.invariant_call_summary(); + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/ReserveERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/ReserveERC721PoolInvariants.t.sol new file mode 100644 index 000000000..19f34517a --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/ReserveERC721PoolInvariants.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import "@std/console.sol"; + +import { BaseInvariants } from '../base/BaseInvariants.sol'; +import { LiquidationInvariants } from '../base/LiquidationInvariants.t.sol'; +import { ReserveInvariants } from '../base/ReserveInvariants.t.sol'; +import { ReserveERC721PoolHandler } from './handlers/ReserveERC721PoolHandler.sol'; +import { LiquidationERC721PoolInvariants } from './LiquidationERC721PoolInvariants.t.sol'; + +contract ReserveERC721PoolInvariants is ReserveInvariants, LiquidationERC721PoolInvariants { + + ReserveERC721PoolHandler internal _reserveERC721PoolHandler; + + function setUp() public override(BaseInvariants, LiquidationERC721PoolInvariants) virtual { + + super.setUp(); + + excludeContract(address(_liquidationERC721PoolHandler)); + + _reserveERC721PoolHandler = new ReserveERC721PoolHandler( + address(_erc721pool), + address(_ajna), + address(_quote), + address(_collateral), + address(_poolInfo), + NUM_ACTORS, + address(this) + ); + + _handler = address(_reserveERC721PoolHandler); + } + + function invariant_call_summary() public virtual override( LiquidationInvariants, LiquidationERC721PoolInvariants) useCurrentTimestamp {} + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol index b8e2ac3bb..3b9153003 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -5,15 +5,9 @@ pragma solidity 0.8.14; import { PoolInfoUtils, _collateralization } from 'src/PoolInfoUtils.sol'; import { Maths } from 'src/libraries/internal/Maths.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - BORROWER_MIN_BUCKET_INDEX, - MIN_AMOUNT, - MAX_AMOUNT -} from '../../base/handlers/unbounded/BaseHandler.sol'; -import { BasicPoolHandler } from '../../base/handlers/BasicPoolHandler.sol'; -import { UnboundedBasicPoolHandler } from '../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol'; +import { BORROWER_MIN_BUCKET_INDEX } from '../../base/handlers/unbounded/BaseHandler.sol'; +import { BasicPoolHandler } from '../../base/handlers/BasicPoolHandler.sol'; +import { UnboundedBasicPoolHandler } from '../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol'; import { UnboundedBasicERC721PoolHandler } from './unbounded/UnboundedBasicERC721PoolHandler.sol'; import { BaseERC721PoolHandler } from './unbounded/BaseERC721PoolHandler.sol'; @@ -139,14 +133,14 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function _preAddCollateral( uint256 amountToAdd_ - ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, 1, 5); + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToAdd_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); } function _preRemoveCollateral( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, 1, 5); + boundedAmount_ = constrictToRange(amountToRemove_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); // ensure actor has collateral to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); @@ -155,20 +149,20 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function _prePledgeCollateral( uint256 amountToPledge_ - ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPledge_, 1, 5); + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPledge_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); } function _prePullCollateral( uint256 amountToPull_ - ) internal pure returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToPull_, 0, MAX_AMOUNT); + ) internal view returns (uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToPull_, 0, MAX_COLLATERAL_AMOUNT); } function _preDrawDebt( uint256 amountToBorrow_ ) internal override returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToBorrow_, MIN_AMOUNT, MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToBorrow_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); // Pre Condition // 1. borrower's debt should exceed minDebt @@ -206,7 +200,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function _preRepayDebt( uint256 amountToRepay_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToRepay_, Maths.max(_pool.quoteTokenDust(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); // ensure actor has debt to repay (uint256 debt, , ) = PoolInfoUtils(_poolInfo).borrowerInfo(address(_pool), _actor); diff --git a/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol new file mode 100644 index 000000000..34cd42227 --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/handlers/LiquidationERC721PoolHandler.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationPoolHandler } from '../../base/handlers/LiquidationPoolHandler.sol'; +import { BasicERC721PoolHandler } from './BasicERC721PoolHandler.sol'; + +contract LiquidationERC721PoolHandler is LiquidationPoolHandler, BasicERC721PoolHandler { + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) BasicERC721PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) { + + } + + function _constrictTakeAmount(uint256 amountToTake_) internal view override returns(uint256 boundedAmount_) { + boundedAmount_ = constrictToRange(amountToTake_, MIN_COLLATERAL_AMOUNT, MAX_COLLATERAL_AMOUNT); + } + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/handlers/ReserveERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/ReserveERC721PoolHandler.sol new file mode 100644 index 000000000..2ac1acbff --- /dev/null +++ b/tests/forge/invariants/ERC721Pool/handlers/ReserveERC721PoolHandler.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { UnboundedReservePoolHandler } from '../../base/handlers/unbounded/UnboundedReservePoolHandler.sol'; +import { ReservePoolHandler } from '../../base/handlers/ReservePoolHandler.sol'; +import { LiquidationERC721PoolHandler } from './LiquidationERC721PoolHandler.sol'; + +contract ReserveERC721PoolHandler is ReservePoolHandler, LiquidationERC721PoolHandler { + + constructor( + address pool_, + address ajna_, + address quote_, + address collateral_, + address poolInfo_, + uint256 numOfActors_, + address testContract_ + ) LiquidationERC721PoolHandler(pool_, ajna_, quote_, collateral_, poolInfo_, numOfActors_, testContract_) {} + +} \ No newline at end of file diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol index 6506855f1..3b1871134 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import { Strings } from '@openzeppelin/contracts/utils/Strings.sol'; +import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import { ERC721Pool } from 'src/ERC721Pool.sol'; @@ -12,11 +13,13 @@ import { BaseHandler } from '../../../base/handlers/unbounded/BaseHandler.sol'; abstract contract BaseERC721PoolHandler is BaseHandler { + using EnumerableSet for EnumerableSet.UintSet; + // Token NFTCollateralToken internal _collateral; // ERC721Pool - ERC721Pool internal _erc721Pool; + ERC721Pool internal _erc721Pool; constructor( address pool_, @@ -27,11 +30,25 @@ abstract contract BaseERC721PoolHandler is BaseHandler { uint256 numOfActors_, address testContract_ ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { + + LENDER_MIN_BUCKET_INDEX = 850; + LENDER_MAX_BUCKET_INDEX = 852; + + MIN_QUOTE_AMOUNT = 1e3; + MAX_QUOTE_AMOUNT = 1e28; + + MIN_COLLATERAL_AMOUNT = 1; + MAX_COLLATERAL_AMOUNT = 100; + + for (uint256 bucket = LENDER_MIN_BUCKET_INDEX; bucket <= LENDER_MAX_BUCKET_INDEX; bucket++) { + collateralBuckets.add(bucket); + } + // Tokens _collateral = NFTCollateralToken(collateral_); // ERC721Pool - _erc721Pool = ERC721Pool(pool_); + _erc721Pool = ERC721Pool(pool_); // Actors actors = _buildActors(numOfActors_); @@ -53,9 +70,6 @@ abstract contract BaseERC721PoolHandler is BaseHandler { _quote.mint(actor, 1e45); _quote.approve(address(_pool), 1e45); - _collateral.mint(actor, 100); - _collateral.setApprovalForAll(address(_pool), true); - vm.stopPrank(); } diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol index f02d91ea4..97c39bd95 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol @@ -2,18 +2,22 @@ pragma solidity 0.8.14; +import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; + import { ERC20Pool } from 'src/ERC20Pool.sol'; import { ERC20PoolFactory } from 'src/ERC20PoolFactory.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; -import { _borrowFeeRate, _depositFeeRate } from 'src/libraries/helpers/PoolHelper.sol'; +import { + _borrowFeeRate, + _depositFeeRate, + _indexOf, + MIN_PRICE, + MAX_PRICE +} from 'src/libraries/helpers/PoolHelper.sol'; import { Maths } from "src/libraries/internal/Maths.sol"; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX -} from '../../../base/handlers/unbounded/BaseHandler.sol'; import { UnboundedBasicPoolHandler } from "../../../base/handlers/unbounded/UnboundedBasicPoolHandler.sol"; -import { BaseERC721PoolHandler } from './BaseERC721PoolHandler.sol'; +import { BaseERC721PoolHandler } from './BaseERC721PoolHandler.sol'; /** * @dev this contract manages multiple lenders @@ -21,6 +25,8 @@ import { BaseERC721PoolHandler } from './BaseERC721PoolHandler.sol'; * @dev randomly selects a lender contract to make a txn */ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, BaseERC721PoolHandler { + + using EnumerableSet for EnumerableSet.UintSet; /*******************************/ /*** Lender Helper Functions ***/ @@ -34,21 +40,25 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, (uint256 lpBalanceBeforeAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); + _collateral.mint(_actor, amount_); + _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](amount_); for(uint256 i = 0; i < amount_; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } - _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; + 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"); + // Post action condition + (uint256 lpBalanceAfterAction, ) = _erc721Pool.lenderInfo(bucketIndex_, _actor); + require(lpBalanceAfterAction > lpBalanceBeforeAction, "LP balance should increase"); + } catch (bytes memory err) { + _ensurePoolError(err); + } } function _removeCollateral( @@ -82,17 +92,36 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.pledgeCollateral']++; + (uint256 kickTimeBefore, , , , uint256 auctionPrice, ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); + // **R1**: Exchange rates are unchanged by pledging collateral for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { exchangeRateShouldNotChange[bucketIndex] = true; } + _collateral.mint(_actor, amount_); + _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](amount_); for(uint256 i = 0; i < amount_; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } - _erc721Pool.drawDebt(_actor, 0, 0, tokenIds); + try _erc721Pool.drawDebt(_actor, 0, 0, tokenIds) { + (uint256 kickTimeAfter, , , , , ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); + + // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket + if (kickTimeBefore != 0 && kickTimeAfter == 0) { + if (auctionPrice < MIN_PRICE) { + collateralBuckets.add(7388); + } else if (auctionPrice > MAX_PRICE) { + collateralBuckets.add(0); + } else { + collateralBuckets.add(_indexOf(auctionPrice)); + } + } + } catch (bytes memory err) { + _ensurePoolError(err); + } } function _pullCollateral( @@ -122,8 +151,15 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, // find bucket to borrow quote token uint256 bucket = _erc721Pool.depositIndex(amount_ + poolDebt) - 1; uint256 price = _poolInfo.indexToPrice(bucket); - uint256 collateralToPledge = (((amount_ * 1e18 + price / 2) / price) * 101 / 100 ) % 1e18 + 1; + // Pool doesn't have enough deposits to draw debt + 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(amount_, price) / 1e18 + 1; + + _collateral.mint(_actor, collateralToPledge); + _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](collateralToPledge); for(uint256 i = 0; i < collateralToPledge; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); @@ -148,7 +184,21 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, ) internal updateLocalStateAndPoolInterest { numberOfCalls['UBBasicHandler.repayDebt']++; + (uint256 kickTimeBefore, , , , uint256 auctionPrice, ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); + try _erc721Pool.repayDebt(_actor, amountToRepay_, 0, _actor, 7388) { + (uint256 kickTimeAfter, , , , , ) =_poolInfo.auctionStatus(address(_erc721Pool), _actor); + + // **CT2**: Keep track of bucketIndex when borrower is removed from auction to check collateral added into that bucket + if (kickTimeBefore != 0 && kickTimeAfter == 0) { + if (auctionPrice < MIN_PRICE) { + collateralBuckets.add(7388); + } else if (auctionPrice > MAX_PRICE) { + collateralBuckets.add(0); + } else { + collateralBuckets.add(_indexOf(auctionPrice)); + } + } } catch (bytes memory err) { _ensurePoolError(err); diff --git a/tests/forge/invariants/base/BaseInvariants.sol b/tests/forge/invariants/base/BaseInvariants.sol index ae5df5f14..d66d51b31 100644 --- a/tests/forge/invariants/base/BaseInvariants.sol +++ b/tests/forge/invariants/base/BaseInvariants.sol @@ -13,6 +13,9 @@ import { InvariantsTestHelpers } from './InvariantsTestHelpers.sol'; abstract contract BaseInvariants is InvariantsTestHelpers, Test { + uint256 internal LENDER_MIN_BUCKET_INDEX; + uint256 internal LENDER_MAX_BUCKET_INDEX; + TokenWithNDecimals internal _quote; BurnableToken internal _ajna; diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index 32a322887..ae714fa34 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -7,10 +7,6 @@ import "@std/console.sol"; import { Maths } from 'src/libraries/internal/Maths.sol'; import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX -} from './handlers/unbounded/BaseHandler.sol'; import { BaseInvariants } from '../base/BaseInvariants.sol'; // contains invariants for the test diff --git a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol index 3c3514f12..133178ed5 100644 --- a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -4,12 +4,6 @@ pragma solidity 0.8.14; import { Maths } from 'src/libraries/internal/Maths.sol'; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - MIN_AMOUNT, - MAX_AMOUNT -} from './unbounded/BaseHandler.sol'; import { UnboundedBasicPoolHandler } from './unbounded/UnboundedBasicPoolHandler.sol'; /** @@ -91,13 +85,13 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { function _preAddQuoteToken( uint256 amountToAdd_ ) internal view returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenDust(), MIN_AMOUNT), MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToAdd_, Maths.max(_pool.quoteTokenDust(), MIN_QUOTE_AMOUNT), MAX_QUOTE_AMOUNT); } function _preRemoveQuoteToken( uint256 amountToRemove_ ) internal returns (uint256 boundedAmount_) { - boundedAmount_ = constrictToRange(amountToRemove_, MIN_AMOUNT, MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToRemove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); // ensure actor has quote tokens to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); @@ -113,7 +107,7 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { ) 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_AMOUNT, MAX_AMOUNT); + boundedAmount_ = constrictToRange(amountToMove_, MIN_QUOTE_AMOUNT, MAX_QUOTE_AMOUNT); // ensure actor has LP to move (uint256 lpBalance, ) = _pool.lenderInfo(boundedFromIndex_, _actor); diff --git a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol index 8c24a0ebf..ba80bd9da 100644 --- a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol @@ -2,12 +2,6 @@ pragma solidity 0.8.14; -import { - LENDER_MIN_BUCKET_INDEX, - LENDER_MAX_BUCKET_INDEX, - MIN_AMOUNT, - MAX_AMOUNT -} from './unbounded/BaseHandler.sol'; import { UnboundedLiquidationPoolHandler } from './unbounded/UnboundedLiquidationPoolHandler.sol'; import { BasicPoolHandler } from './BasicPoolHandler.sol'; @@ -43,6 +37,25 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas /*** Taker Test Functions ***/ /****************************/ + function takeAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 takerIndex_ + ) external useRandomActor(takerIndex_) useTimestamps { + numberOfCalls['BLiquidationHandler.takeAuction']++; + + // Prepare test phase + address borrower; + address taker = _actor; + (amount_, borrower) = _preTake(amount_, borrowerIndex_, takerIndex_); + + // Action phase + changePrank(taker); + // skip time to make auction takeable + vm.warp(block.timestamp + 2 hours); + _takeAuction(borrower, amount_, taker); + } + function bucketTake( uint256 borrowerIndex_, uint256 bucketIndex_, @@ -51,56 +64,103 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas ) external useRandomActor(takerIndex_) useTimestamps { numberOfCalls['BLiquidationHandler.bucketTake']++; - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - bucketIndex_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); - - address borrower = actors[borrowerIndex_]; - address taker = _actor; - - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - - if (kickTime == 0) _kickAuction(borrowerIndex_, 1e24, bucketIndex_); + // Prepare test phase + address taker = _actor; + (address borrower, uint256 bucketIndex) = _preBucketTake(borrowerIndex_, takerIndex_, bucketIndex_); changePrank(taker); // skip time to make auction takeable vm.warp(block.timestamp + 2 hours); - _bucketTake(taker, borrower, depositTake_, bucketIndex_); + _bucketTake(taker, borrower, depositTake_, bucketIndex); } - /************************/ - /*** Helper Functions ***/ - /************************/ + /******************************/ + /*** Settler Test Functions ***/ + /******************************/ - function _kickAuction( + function settleAuction( + uint256 actorIndex_, uint256 borrowerIndex_, - uint256 amount_, uint256 kickerIndex_ - ) internal useRandomActor(kickerIndex_) { - numberOfCalls['BLiquidationHandler.kickAuction']++; + ) external useRandomActor(actorIndex_) useTimestamps { - borrowerIndex_ = constrictToRange(borrowerIndex_, 0, actors.length - 1); - address borrower = actors[borrowerIndex_]; - address kicker = _actor; - amount_ = constrictToRange(amount_, MIN_AMOUNT, MAX_AMOUNT); + // prepare phase + address actor = _actor; + (address borrower, uint256 maxDepth) = _preSettleAuction(borrowerIndex_, kickerIndex_); + + // Action phase + changePrank(actor); + // skip time to make auction clearable + vm.warp(block.timestamp + 73 hours); + _settleAuction(borrower, maxDepth); - ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); + // Cleanup phase + _auctionSettleStateReset(borrower); + } + + /*******************************/ + /*** Prepare Tests Functions ***/ + /*******************************/ - if (kickTime == 0) { - (uint256 debt, , ) = _pool.borrowerInfo(borrower); + 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); + + ( , , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower_); + + borrowerKicked_ = kickTime != 0; + + if (!borrowerKicked_) { + (uint256 debt, , ) = _pool.borrowerInfo(borrower_); if (debt == 0) { - changePrank(borrower); - _actor = borrower; + changePrank(borrower_); + _actor = borrower_; uint256 drawDebtAmount = _preDrawDebt(amount_); _drawDebt(drawDebtAmount); // skip to make borrower undercollateralized vm.warp(block.timestamp + 200 days); } - - changePrank(kicker); - _actor = kicker; - _kickAuction(borrower); } } + + function _preTake(uint256 amount_, uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(uint256 boundedAmount_, address borrower_){ + boundedAmount_ = _constrictTakeAmount(amount_); + borrower_ = _kickAuction(borrowerIndex_, boundedAmount_ * 100, kickerIndex_); + } + + function _preBucketTake(uint256 borrowerIndex_, uint256 kickerIndex_, uint256 bucketIndex_) internal returns(address borrower_, uint256 bucket_) { + bucket_ = constrictToRange(bucketIndex_, LENDER_MIN_BUCKET_INDEX, LENDER_MAX_BUCKET_INDEX); + borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_); + } + + function _preSettleAuction(uint256 borrowerIndex_, uint256 kickerIndex_) internal returns(address borrower_, uint256 maxDepth_) { + maxDepth_ = LENDER_MAX_BUCKET_INDEX - LENDER_MIN_BUCKET_INDEX; + borrower_ = _kickAuction(borrowerIndex_, 1e24, kickerIndex_); + } + + /************************/ + /*** Helper Functions ***/ + /************************/ + + function _kickAuction( + uint256 borrowerIndex_, + uint256 amount_, + uint256 kickerIndex_ + ) internal useRandomActor(kickerIndex_) returns(address borrower_) { + numberOfCalls['BLiquidationHandler.kickAuction']++; + + // Prepare test phase + address kicker = _actor; + bool borrowerKicked; + (borrower_, borrowerKicked)= _preKick(borrowerIndex_, amount_); + + // Action phase + _actor = kicker; + if(!borrowerKicked) _kickAuction(borrower_); + } + + function _constrictTakeAmount(uint256 amountToTake_) internal view virtual returns(uint256 boundedAmount_); } \ No newline at end of file diff --git a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol index e39037166..fe326493a 100644 --- a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.14; import { Maths } from 'src/libraries/internal/Maths.sol'; import { UnboundedReservePoolHandler } from '../../base/handlers/unbounded/UnboundedReservePoolHandler.sol'; -import { MIN_AMOUNT } from '../../base/handlers/unbounded/BaseHandler.sol'; import { LiquidationPoolHandler } from './LiquidationPoolHandler.sol'; abstract contract ReservePoolHandler is UnboundedReservePoolHandler, LiquidationPoolHandler { @@ -46,7 +45,7 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation skip(24 hours); (, , claimableReservesRemaining, , ) = _poolInfo.poolReservesInfo(address(_pool)); - boundedAmount_ = constrictToRange(amountToTake_, 0, Maths.min(MIN_AMOUNT, claimableReservesRemaining)); + boundedAmount_ = constrictToRange(amountToTake_, 0, Maths.min(MIN_QUOTE_AMOUNT, claimableReservesRemaining)); } } \ No newline at end of file diff --git a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index ab7c6c5f1..75387591c 100644 --- a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.14; import '@std/Test.sol'; +import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import { Pool } from 'src/base/Pool.sol'; import { PoolInfoUtils } from 'src/PoolInfoUtils.sol'; @@ -18,26 +19,31 @@ import { TokenWithNDecimals, BurnableToken } from '../../../../utils/Tokens.sol' import '../../../interfaces/ITestBase.sol'; -uint256 constant LENDER_MIN_BUCKET_INDEX = 2570; -uint256 constant LENDER_MAX_BUCKET_INDEX = 2572; - uint256 constant BORROWER_MIN_BUCKET_INDEX = 2600; uint256 constant BORROWER_MAX_BUCKET_INDEX = 2620; -uint256 constant MIN_AMOUNT = 1e3; -uint256 constant MAX_AMOUNT = 1e30; - abstract contract BaseHandler is Test { + using EnumerableSet for EnumerableSet.UintSet; + // Tokens TokenWithNDecimals internal _quote; - - BurnableToken internal _ajna; + BurnableToken internal _ajna; // Pool - Pool internal _pool; + Pool internal _pool; PoolInfoUtils internal _poolInfo; + // Lender bucket index + uint256 public LENDER_MIN_BUCKET_INDEX; + uint256 public LENDER_MAX_BUCKET_INDEX; + + uint256 internal MIN_QUOTE_AMOUNT; + uint256 internal MAX_QUOTE_AMOUNT; + + uint256 internal MIN_COLLATERAL_AMOUNT; + uint256 internal MAX_COLLATERAL_AMOUNT; + // Test invariant contract ITestBase internal testContract; @@ -63,6 +69,9 @@ abstract contract BaseHandler is Test { uint256 public increaseInReserves; // amount of reserve decrease uint256 public decreaseInReserves; // amount of reserve increase + // Buckets where collateral is added when a borrower is in auction and has partial NFT + EnumerableSet.UintSet internal collateralBuckets; + // auctions invariant test state bool public firstTake; // if take is called on auction first time mapping(address => bool) public alreadyTaken; // mapping borrower address to true if auction taken atleast once @@ -183,6 +192,7 @@ abstract contract BaseHandler is Test { err == keccak256(abi.encodeWithSignature("AuctionNotClearable()")) || err == keccak256(abi.encodeWithSignature("ReserveAuctionTooSoon()")) || err == keccak256(abi.encodeWithSignature("NoReserves()")) || + err == keccak256(abi.encodeWithSignature("ZeroThresholdPrice()")) || err == keccak256(abi.encodeWithSignature("NoReservesAuction()")), "Unexpected revert error" ); @@ -219,7 +229,8 @@ abstract contract BaseHandler is Test { } function _fenwickRemove(uint256 removedAmount_, uint256 bucketIndex_) internal { - fenwickDeposits[bucketIndex_] -= removedAmount_; + // removedAmount can be slightly greater than fenwickDeposits due to rounding in accrue interest + fenwickDeposits[bucketIndex_] -= Maths.min(fenwickDeposits[bucketIndex_], removedAmount_); } function _fenwickAccrueInterest() internal { @@ -396,4 +407,8 @@ abstract contract BaseHandler is Test { if (max_ == type(uint256).max && x_ != 0) result_++; } + function getCollateralBuckets() external view returns(uint256[] memory) { + return collateralBuckets.values(); + } + } diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol index 930c3b9cf..14868e0c0 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -2,12 +2,25 @@ pragma solidity 0.8.14; -import { Maths } from 'src/libraries/internal/Maths.sol'; +import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; + +import { Maths } from 'src/libraries/internal/Maths.sol'; +import { _priceAt, _indexOf, MIN_PRICE, MAX_PRICE } from 'src/libraries/helpers/PoolHelper.sol'; +import { MAX_FENWICK_INDEX } from 'src/libraries/helpers/PoolHelper.sol'; import { BaseHandler } from './BaseHandler.sol'; abstract contract UnboundedLiquidationPoolHandler is BaseHandler { + using EnumerableSet for EnumerableSet.UintSet; + + struct LocalBucketTakeVars { + uint256 kickerLps; + uint256 takerLps; + uint256 deposit; + uint256 kickerBond; + } + /*******************************/ /*** Kicker Helper Functions ***/ /*******************************/ @@ -80,6 +93,8 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { (uint256 borrowerDebtBeforeTake, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); uint256 totalBondBeforeTake = _getKickerBond(kicker); uint256 totalBalanceBeforeTake = _quote.balanceOf(address(_pool)) * 10**(18 - _quote.decimals()); + + ( , , , , uint256 auctionPrice, ) = _poolInfo.auctionStatus(address(_pool), borrower_); try _pool.take(borrower_, amount_, taker_, bytes("")) { @@ -106,6 +121,18 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { // **RE7**: Reserves increase with the quote token paid by taker. increaseInReserves += totalBalanceAfterTake - totalBalanceBeforeTake; + // **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) { + if (auctionPrice < MIN_PRICE) { + collateralBuckets.add(7388); + } else if (auctionPrice > MAX_PRICE) { + collateralBuckets.add(0); + } else { + collateralBuckets.add(_indexOf(auctionPrice)); + } + } + if (!alreadyTaken[borrower_]) { alreadyTaken[borrower_] = true; @@ -132,34 +159,42 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { (uint256 borrowerDebt, , ) = _poolInfo.borrowerInfo(address(_pool), borrower_); (address kicker, , , , , , , , , ) = _pool.auctionInfo(borrower_); - (uint256 kickerLpsBeforeTake, ) = _pool.lenderInfo(bucketIndex_, kicker); - (uint256 takerLpsBeforeTake, ) = _pool.lenderInfo(bucketIndex_, _actor); - ( , , , uint256 depositBeforeAction, ) = _pool.bucketInfo(bucketIndex_); - uint256 totalBondBeforeTake = _getKickerBond(kicker); + LocalBucketTakeVars memory beforeBucketTakeVars = getBucketTakeInfo(bucketIndex_, kicker, _actor); + ( , , , , uint256 auctionPrice, ) = _poolInfo.auctionStatus(address(_pool), borrower_); try _pool.bucketTake(borrower_, depositTake_, bucketIndex_) { - (uint256 kickerLpsAfterTake, ) = _pool.lenderInfo(bucketIndex_, kicker); - (uint256 takerLpsAfterTake, ) = _pool.lenderInfo(bucketIndex_, _actor); - ( , , , uint256 depositAfterAction, ) = _pool.bucketInfo(bucketIndex_); + LocalBucketTakeVars memory afterBucketTakeVars = getBucketTakeInfo(bucketIndex_, kicker, _actor); // **B7**: when awarded bucket take LP : taker deposit time = timestamp of block when award happened - if (takerLpsAfterTake > takerLpsBeforeTake) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; + if (afterBucketTakeVars.takerLps > beforeBucketTakeVars.takerLps) lenderDepositTime[taker_][bucketIndex_] = block.timestamp; - if (kickerLpsAfterTake > kickerLpsBeforeTake) { + if (afterBucketTakeVars.kickerLps > beforeBucketTakeVars.kickerLps) { // **B7**: when awarded bucket take LP : kicker deposit time = timestamp of block when award happened lenderDepositTime[kicker][bucketIndex_] = block.timestamp; } else { // **RE7**: Reserves increase by bond penalty on take. - increaseInReserves += _getKickerBond(kicker) - totalBondBeforeTake; + increaseInReserves += afterBucketTakeVars.kickerBond - beforeBucketTakeVars.kickerBond; } // **R7**: Exchange rates are unchanged under depositTakes // **R8**: Exchange rates are unchanged under arbTakes exchangeRateShouldNotChange[bucketIndex_] = true; - _fenwickRemove(depositBeforeAction - depositAfterAction, bucketIndex_); + // **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) { + if (auctionPrice < MIN_PRICE) { + collateralBuckets.add(7388); + } else if (auctionPrice > MAX_PRICE) { + collateralBuckets.add(0); + } else { + collateralBuckets.add(_indexOf(auctionPrice)); + } + } + + _fenwickRemove(beforeBucketTakeVars.deposit - afterBucketTakeVars.deposit, bucketIndex_); _updateCurrentTakeState(borrower_, borrowerDebt); @@ -168,4 +203,102 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { } } + /********************************/ + /*** Settler Helper Functions ***/ + /********************************/ + + function _settleAuction( + address borrower_, + uint256 maxDepth_ + ) internal updateLocalStateAndPoolInterest { + ( + uint256 borrowerT0Debt, + uint256 collateral, + ) = _pool.borrowerInfo(borrower_); + (uint256 reservesBeforeAction, , , , )= _poolInfo.poolReservesInfo(address(_pool)); + (uint256 inflator, ) = _pool.inflatorInfo(); + + try _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.wmul(collateral, _priceAt(bucketIndex)); + uint256 fenwickDeposit = fenwickDeposits[bucketIndex]; + uint256 borrowerDebt = Maths.wmul(borrowerT0Debt, inflator); + + 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.wdiv(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.wdiv(fenwickDeposit, _priceAt(bucketIndex)); + borrowerT0Debt -= Maths.wdiv(fenwickDeposit, inflator); + } + // exchange all collateral with deposit + else { + fenwickDeposits[bucketIndex] -= maxSettleableDebt; + collateral = 0; + borrowerT0Debt -= Maths.wdiv(maxSettleableDebt, inflator); + } + } else collateral = 0; + + 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.wdiv(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) collateralBuckets.add(7388); + + } catch (bytes memory err) { + _ensurePoolError(err); + } + } + + function getBucketTakeInfo(uint256 bucketIndex_, address kicker_, address taker_) 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_); + } + } diff --git a/tests/forge/invariants/interfaces/IBaseHandler.sol b/tests/forge/invariants/interfaces/IBaseHandler.sol index ad609ab20..74da2422f 100644 --- a/tests/forge/invariants/interfaces/IBaseHandler.sol +++ b/tests/forge/invariants/interfaces/IBaseHandler.sol @@ -4,6 +4,9 @@ pragma solidity 0.8.14; interface IBaseHandler { + function LENDER_MIN_BUCKET_INDEX() external view returns(uint256); + function LENDER_MAX_BUCKET_INDEX() external view returns(uint256); + function getActorsCount() external view returns(uint256); function actors(uint256) external view returns(address); @@ -27,4 +30,6 @@ interface IBaseHandler { function alreadyTaken(address) external view returns(bool); function lenderDepositTime(address lender, uint256 bucketIndex) external view returns(uint256); + + function getCollateralBuckets() external view returns(uint256[] memory); } \ No newline at end of file diff --git a/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol new file mode 100644 index 000000000..b3b0f089b --- /dev/null +++ b/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { BasicERC721PoolInvariants } from "../../invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol"; +import "@std/console.sol"; + +contract RegressionTestBasicERC721Pool is BasicERC721PoolInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_out_of_gas() external { + _basicERC721PoolHandler.drawDebt(6251, 2506); + _basicERC721PoolHandler.drawDebt(5442742850703661819442539517113510923065138686636336073122798635, 3); + + invariant_total_interest_earned_I2(); + } + + function test_regression_evm_revert_1() external { + _basicERC721PoolHandler.drawDebt(0, 29877144463); + _basicERC721PoolHandler.removeQuoteToken(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1206432074572207884421188737151329072317831713860321643282); + } + + function test_regression_evm_revert_2() external { + _basicERC721PoolHandler.transferLps(76782784424641365703739404005502204389486434568092458354824862449636, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 8035404803286); + _basicERC721PoolHandler.pledgeCollateral(342383554214549851552199917128005690776956832171674611333701203702390, 0); + _basicERC721PoolHandler.removeQuoteToken(12567859756068232972011072581, 63626342148664557735680537369075067784627403842721642546686472741089686440047, 8127527276777147088111960971); + _basicERC721PoolHandler.addQuoteToken(20779927945551108207926635157913063675776800121687779034981760002893853699, 57581400339686514759890039148521228145897186273404444222043163327979038812912, 74636668128306553654783413916389199708724482852687925110440797752935581131276); + _basicERC721PoolHandler.pledgeCollateral(1749, 60975514031505374609128672992631717582003685370683632591257473037951960167993); + _basicERC721PoolHandler.pledgeCollateral(25718032335942173737944995798726490502781258789511022074015153606363463, 1325044714694); + _basicERC721PoolHandler.addQuoteToken(234707990811923980957502397929, 291292446109648312501466920531792328617, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _basicERC721PoolHandler.repayDebt(10621200, 1157); + } + + function test_regression_evm_revert_3() external { + _basicERC721PoolHandler.repayDebt(7033399587545693049772672666426104761848542813925583983822212786951755531265, 1108); + _basicERC721PoolHandler.repayDebt(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _basicERC721PoolHandler.removeQuoteToken(0, 134682577920393186680576557312616751188928702587427398297744881288955844683, 85980234897818314); + _basicERC721PoolHandler.removeCollateral(87753968394072371065317166720803, 15237002444774746392696, 4454566448443430136); + _basicERC721PoolHandler.pullCollateral(0, 1131722123054653932602913); + _basicERC721PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + } + +} \ No newline at end of file diff --git a/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol new file mode 100644 index 000000000..ca152df0d --- /dev/null +++ b/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { LiquidationERC721PoolInvariants } from "../../invariants/ERC721Pool/LiquidationERC721PoolInvariants.t.sol"; + +contract RegressionTestLiquidationERC721Pool is LiquidationERC721PoolInvariants { + + function setUp() public override { + super.setUp(); + } + + function test_regression_CT2_1() external { + _liquidationERC721PoolHandler.transferLps(82763479476530761653416180818770120221606073479896485216701663210067343854989, 13965680104257999009544220, 19607095117906083242714379712137487321145009755129413368920688919580383224, 172964); + _liquidationERC721PoolHandler.drawDebt(890267305151917142442750426831409392687842064959563694229432652653, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC721PoolHandler.moveQuoteToken(4235656392303564786676824298, 95172, 43998930769576514260444206117, 19); + _liquidationERC721PoolHandler.removeCollateral(3, 2498246403298170224512157430407755467635042114433885793390715371, 100495355660528314100606431049031638496400849091716919265110381324275); + _liquidationERC721PoolHandler.addQuoteToken(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC721PoolHandler.transferLps(519335861499288467890359611142992274483199326, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 58994010504161811237846499984711170588879896808493104194231, 3); + _liquidationERC721PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 41193994068653137125420839784619, 2); + _liquidationERC721PoolHandler.pledgeCollateral(20422241426722852797507678149773415955101379369266542516369, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + invariant_CT2(); + } + + function test_regression_CT2_2() external { + _liquidationERC721PoolHandler.pledgeCollateral(11364442794296936806062834101, 2); + _liquidationERC721PoolHandler.repayDebt(127588, 83100081073501003261077111710432816811128546597964956253465927696489949357170); + _liquidationERC721PoolHandler.addCollateral(106002141912347165539594289495307219487505255691982710940541429529032942318473, 92001987806333700856071384682550468910212704266158266358190575554223580055372, 10372528004978988523232445248742074319234365108658587623559014019285393461590); + _liquidationERC721PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC721PoolHandler.pullCollateral(123201780725572471227365690095045434559536667998811844, 1); + _liquidationERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 29385674639660674352849281126447, 104213395651073427726178922661176810647437412987911413866648707979037858111, 120099089654852364357199080589352201114952444751633); + _liquidationERC721PoolHandler.takeAuction(20272072497355131279266366599, 62702303947006190164253670404709792694262725188679134323940816202830205182957, 2018886559710986403697166); + _liquidationERC721PoolHandler.removeCollateral(3700863476119406681, 459532160275944001121201486579288279690, 64339); + _liquidationERC721PoolHandler.kickAuction(241465325251293207620629184765800029, 272324, 4286); + _liquidationERC721PoolHandler.addCollateral(14183352841703051060560888149196444796369982703801170254620915975140557318165, 58035546441149173952074400174948456045444728138319576872232054245393948126862, 1516000000000000000000); + _liquidationERC721PoolHandler.repayDebt(113036808365357047396163273250845284856347394197766632691494752070902864551209, 770331998269427329234302); + _liquidationERC721PoolHandler.settleAuction(231577253, 1483514342575063812508584719638203116171392315272235623, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC721PoolHandler.settleAuction(2, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC721PoolHandler.drawDebt(41199243733551491091355970566553968189620029998048, 143444422045802851477052828901); + _liquidationERC721PoolHandler.settleAuction(2, 8106084097424684161337344993693951687492733297750298657163090782893337803, 0); + _liquidationERC721PoolHandler.settleAuction(24837746324089221857760845057323397822024818909964892790635250470570076078486, 76738562199262389151219341912555113757197572023777541085268052336938591410624, 95228427349014753291008393411121665873108183485006042815631164420251653152751); + + invariant_CT2(); + + } + + function test_regression_evm_revert() external { + _liquidationERC721PoolHandler.bucketTake(2, 1349541295405069308566056236594888526270892896988, false, 18293394963373947391940175296817481); + _liquidationERC721PoolHandler.addQuoteToken(994255470879741784854463339406983, 160443962062009775217345068718654486938090, 0); + _liquidationERC721PoolHandler.removeQuoteToken(110349606679412691172957834289542550319383271247755660854362242977991410022932, 20146, 18640181410506725405733865833824324648215384731482764797343269315726072943243); + _liquidationERC721PoolHandler.removeQuoteToken(3, 32353860919711184369008251816, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _liquidationERC721PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 5787821553126, 1); + _liquidationERC721PoolHandler.addCollateral(416000000000000000000, 92001987806333700856071384682550468910212704266158266358190575554223580055260, 210789749744805153960619); + } + + +} \ No newline at end of file diff --git a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol new file mode 100644 index 000000000..883cc3784 --- /dev/null +++ b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.14; + +import { ReserveERC721PoolInvariants } from "../../invariants/ERC721Pool/ReserveERC721PoolInvariants.t.sol"; + +contract RegressionTestReserveERC721Pool is ReserveERC721PoolInvariants { + function setUp() public override { + super.setUp(); + } + + function test_regression_arithmetic_overflow() external { + _reserveERC721PoolHandler.takeAuction(92769370221611464325146803683156031925894702957583423527130966373453460, 1, 0); + _reserveERC721PoolHandler.bucketTake(946681003919344525962988194461032341334826191474892406752540091475466732435, 115792089237316195423570985008687907853269984665640564039457584007913129639932, false, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC721PoolHandler.pledgeCollateral(110349606679412691172957834289542550319383271247755660854362242977991410022199, 14546335109189328620313099); + _reserveERC721PoolHandler.transferLps(7966696646007323951141060300, 1382000000000000000000, 14900528365458273129607000593, 18640181410506725405733865833824324648215384731482764797343269315726072943072); + _reserveERC721PoolHandler.drawDebt(107285134268485238885825019843523094619958942033886535891203702184170570337916, 1008096043491529984); + _reserveERC721PoolHandler.bucketTake(0, 1177, true, 698469034333322743784201375142656365110267526102696086972); + } + + function test_regression_CT4_1() external { + _reserveERC721PoolHandler.takeAuction(12081493032056306060837676478, 17112687674220907985671783478, 156086231189053706777082702350822415); + _reserveERC721PoolHandler.bucketTake(2751921977392940485992662421841654754784896, 0, false, 74485124857288266409128701303509478629061526535257123857425657075); + _reserveERC721PoolHandler.settleAuction(28196, 350662677223461989004552717744870304232548804666, 36769010933687420804596073); + _reserveERC721PoolHandler.bucketTake(83908, 44550000000000000, false, 20000000000000000000000312288); + + invariant_CT4(); + } + + // FIXME: Failing due to rebalance of tokens in case of partial settle + function _test_regression_CT4_2() external { + _reserveERC721PoolHandler.drawDebt(0, 3); + _reserveERC721PoolHandler.addQuoteToken(110722066303045195479382873847756822996893052638415787811385263327686542008, 2595467720355805256177, 44804955487212801727231000414524018578); + _reserveERC721PoolHandler.moveQuoteToken(43739203749898257092507987414800731, 45406433371816793948702636, 12374955966170596958032853251, 781); + _reserveERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 61586, 11856671202668897206441691542968611274078091901056358965450125); + _reserveERC721PoolHandler.pledgeCollateral(349513993113487194057973, 362746040314235282459383005583790844); + _reserveERC721PoolHandler.settleAuction(3, 2, 3); + + invariant_CT4(); + } + + function test_regression_CT2_2() external { + _reserveERC721PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); + _reserveERC721PoolHandler.repayDebt(47903824342862105100722366, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC721PoolHandler.moveQuoteToken(14954617124484181050069718572841414619329, 4019052775, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 18404722369483097182428514137726899016323228344857237503694710754857187987); + _reserveERC721PoolHandler.repayDebt(8642195270788292, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC721PoolHandler.kickAuction(37599242352987749812798760790120682114398140522946909699266021534073157156, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 42022531711777130710006520923822265578840019061180471553959992811); + _reserveERC721PoolHandler.drawDebt(12048316057, 12017048940743955664316982882044887141128535965); + _reserveERC721PoolHandler.removeQuoteToken(17773795768966620525, 1047143, 54863162); + _reserveERC721PoolHandler.withdrawBonds(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3); + _reserveERC721PoolHandler.takeAuction(2698759183557, 47540095330112933707821447439580287140189201532316467969464, 28821914686174180822501529566772569775778735295453392587173140587); + _reserveERC721PoolHandler.repayDebt(11851070455092288342427255581330021498615848370966979414877793886456318988205, 20000469912106847714032076597); + _reserveERC721PoolHandler.pullCollateral(9906355507789251046177658200, 789628541711133703256041458103535389653400352665407731094226888831); + _reserveERC721PoolHandler.drawDebt(0, 5711299); + _reserveERC721PoolHandler.takeReserves(52580967332816855446614075396003761174408900540583074540513, 3066371283933430634405115377931952568434121552); + _reserveERC721PoolHandler.moveQuoteToken(187, 10746658534810329994020169146, 26326378734592892198504991, 3678); + _reserveERC721PoolHandler.kickWithDeposit(9347, 1019767997450901378); + _reserveERC721PoolHandler.pullCollateral(0, 1727508752834082423180670412007678522620836706739773785431403804); + _reserveERC721PoolHandler.addQuoteToken(10272927241872097800945271290053605104341355430184682823901929, 133408017309487439448075733, 17099185418125710911451484450376088); + _reserveERC721PoolHandler.drawDebt(448053659500389508982384470106829047, 1400284447444730491147774097); + _reserveERC721PoolHandler.removeQuoteToken(10288285818208197682336817035, 45968783023960545347406014687, 691); + _reserveERC721PoolHandler.settleAuction(154, 860492567187269218261780934935914770288503137169306025450164292967, 463633433497382452344200590293648002678143898236); + + invariant_CT2(); + } +} From c869ef98034a7b0a33e0b01fc55b5d02351acce3 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:30:35 +0300 Subject: [PATCH 57/70] Do not try to settle with reserves if reserves is not positive (#740) --- src/libraries/external/SettlerActions.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/external/SettlerActions.sol b/src/libraries/external/SettlerActions.sol index 3e0a9852c..d24bf9a6d 100644 --- a/src/libraries/external/SettlerActions.sol +++ b/src/libraries/external/SettlerActions.sol @@ -206,10 +206,11 @@ library SettlerActions { uint256 assets = Maths.wmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; uint256 liabilities = Deposits.treeSum(deposits_) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; - uint256 reserves = (assets > liabilities) ? (assets - liabilities) : 0; - // settle debt from reserves -- round reserves down however - borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(reserves, poolState_.inflator)); + // settle debt from reserves (assets - liabilities) if reserves positive, round reserves down however + if (assets > liabilities) { + borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(assets - liabilities, poolState_.inflator)); + } // if there's still debt after settling from reserves then start to forgive amount from next HPB // loop through remaining buckets if there's still debt to settle From eb46b3e1b8784c0fb9354df0eb8756caa344722f Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Tue, 18 Apr 2023 10:16:30 +0530 Subject: [PATCH 58/70] Update rebalance tokenIds logic (#746) * Add liquidation and reserve pool handlers and invariants for ERC721Pool * Restructure Liquidation Pool Handler * Resolve out of gas error and find optimum buckets for 1e28 max quote token amount for NFT pool * Add evm error regression test * Update failing regression test * Split MIN and MAX token limits for ERC20 and ERC721 Pool in invariants * Fix arithmetic overflow/underflow in local fewick remove * Add ZeroThresholdPrice() revert in _ensurePoolError * Update CT4 invariant * Add failing regression test * Rebalance NFT token ids on settle only when - entire bad debt is settled (and not on partial settle) or - if entire amount of pledged collateral was used to settle bad debt (but there's still debt to be settled from reserves / deposits) * Fix CT2 and evm reverts in NFT invariants * Fix rebalanceCollateral to work even with non-integer collateral amounts, call rebalance every time post-settle * Polish test * Rebalance Tokens only when collateral is used to settle * PR feedback * Make noOfTokensToTransfer logic more readable --------- Co-authored-by: grandizzy Co-authored-by: mwc --- src/ERC721Pool.sol | 10 ++++++++-- .../RegressionTestReservesERC721Pool.t.sol | 3 +-- .../ERC721PoolLiquidationsSettleAuction.t.sol | 12 ++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 6afac8a37..24432fc2f 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -399,7 +399,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { params ); - if (result.collateralSettled > 0) _rebalanceTokens(params.borrower, result.collateralRemaining); + // move token ids from borrower array to pool claimable array if any collateral used to settle bad debt + if (result.collateralSettled != 0) _rebalanceTokens(params.borrower, result.collateralRemaining); // update pool balances state poolBalances.t0Debt -= result.t0DebtSettled; @@ -576,7 +577,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256[] storage borrowerTokens = borrowerTokenIds[borrowerAddress_]; uint256 noOfTokensPledged = borrowerTokens.length; - uint256 noOfTokensToTransfer = borrowerCollateral_ != 0 ? noOfTokensPledged - borrowerCollateral_ / 1e18 : noOfTokensPledged; + /* + eg1. borrowerCollateral_ = 4.1, noOfTokensPledged = 6; noOfTokensToTransfer = 1 + eg2. borrowerCollateral_ = 4, noOfTokensPledged = 6; noOfTokensToTransfer = 2 + */ + uint256 borrowerCollateralRoundedUp = (borrowerCollateral_ + 1e18 - 1) / 1e18; + uint256 noOfTokensToTransfer = noOfTokensPledged - borrowerCollateralRoundedUp; for (uint256 i = 0; i < noOfTokensToTransfer;) { uint256 tokenId = borrowerTokens[--noOfTokensPledged]; // start with moving the last token pledged by borrower diff --git a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol index 883cc3784..100328623 100644 --- a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol +++ b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol @@ -27,8 +27,7 @@ contract RegressionTestReserveERC721Pool is ReserveERC721PoolInvariants { invariant_CT4(); } - // FIXME: Failing due to rebalance of tokens in case of partial settle - function _test_regression_CT4_2() external { + function test_regression_CT4_2() external { _reserveERC721PoolHandler.drawDebt(0, 3); _reserveERC721PoolHandler.addQuoteToken(110722066303045195479382873847756822996893052638415787811385263327686542008, 2595467720355805256177, 44804955487212801727231000414524018578); _reserveERC721PoolHandler.moveQuoteToken(43739203749898257092507987414800731, 45406433371816793948702636, 12374955966170596958032853251, 781); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol index 098503001..da90ec96b 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -162,9 +162,11 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertCollateralInvariants(); - // the 2 token ids are rebalanced and transferred to pool claimable tokens array after settle + // 1 token id (token id 3, the most recent pledged token) was moved from borrower token ids array to pool claimable token ids array after partial bad debt settle + assertEq(ERC721Pool(address(_pool)).totalBorrowerTokens(_borrower), 1); + assertEq(ERC721Pool(address(_pool)).totalBucketTokens(), 1); + assertEq(ERC721Pool(address(_pool)).borrowerTokenIds(_borrower, 0), 1); assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); - assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); // all NFTs are owned by the pool assertEq(_collateral.ownerOf(1), address(_pool)); @@ -195,6 +197,12 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { settledDebt: 70.567900577736070940 * 1e18 }); + // no token id left in borrower token ids array + assertEq(ERC721Pool(address(_pool)).totalBorrowerTokens(_borrower), 0); + assertEq(ERC721Pool(address(_pool)).totalBucketTokens(), 2); + // tokens used to settle entire bad debt (settle auction) are moved to pool claimable array + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(0), 3); + assertEq(ERC721Pool(address(_pool)).bucketTokenIds(1), 1); _assertBucket({ index: 2500, From 3456d1db501bcb6931d0d19d4ea69da8b33486d9 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Tue, 18 Apr 2023 08:22:03 -0400 Subject: [PATCH 59/70] Resolve DIV/0 unstaking in a burn epoch with no burn (#748) * created unit test which reproduces div/0 error upon unstake * add FIXME comment * ported tests to RC4 * brute-force zero checks (#745) * removed FIXME which was fixed --- src/RewardsManager.sol | 4 +- tests/forge/unit/Rewards/RewardsManager.t.sol | 117 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index aca8dad8c..28c4b55ec 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -525,7 +525,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { ) = _getPoolAccumulators(ajnaPool_, nextEpoch_, epoch_); // calculate rewards earned - newRewards_ = Maths.wmul( + newRewards_ = totalInterestEarnedInPeriod == 0 ? 0 : Maths.wmul( REWARD_FACTOR, Maths.wdiv( Maths.wmul(interestEarned_, totalBurnedInPeriod), @@ -783,7 +783,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { (, , , uint256 bucketDeposit, ) = IPool(pool_).bucketInfo(bucketIndex_); uint256 burnFactor = Maths.wmul(totalBurned_, bucketDeposit); - uint256 interestFactor = Maths.wdiv( + uint256 interestFactor = interestEarned_ == 0 ? 0 : Maths.wdiv( Maths.WAD - Maths.wdiv(prevBucketExchangeRate, curBucketExchangeRate), interestEarned_ ); diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index 191212bc0..f5521735e 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -177,6 +177,123 @@ contract RewardsManagerTest is RewardsHelperContract { }); } + function testUnstakeTokenAfterBurnNoInterest() external { + skip(10); + ERC20Pool pool = ERC20Pool(address(_pool)); + + // deposit into a high and low bucket + deal(address(_quoteOne), _minterOne, 400 * 1e18); + changePrank(_minterOne); + _quoteOne.approve(address(_pool), type(uint256).max); + _pool.addQuoteToken(200 * 1e18, 2_000, type(uint256).max); + _pool.addQuoteToken(200 * 1e18, 4_000, type(uint256).max); + skip(1 hours); + + // draw debt between the buckets + uint256 borrowAmount = 100 * 1e18; + uint256 limitIndex = 3_000; + assertGt(_pool.depositSize(), borrowAmount); + ( + uint256 collateralToPledge + ) = _createTestBorrower(address(_pool), _borrower, borrowAmount, limitIndex); + pool.drawDebt(_borrower, borrowAmount, limitIndex, collateralToPledge); + skip(3 days); + (,,, uint256 htpIndex,,) = _poolUtils.poolPricesInfo(address(_pool)); + assertLt(htpIndex, 4_000); + + // mint LP NFT and memorialize position for only the bucket which did not earn interest + (uint256 lpBalance, ) = _pool.lenderInfo(4000, _minterOne); + assertGt(lpBalance, 0); + uint256[] memory indexes = new uint256[](1); + indexes[0] = 4_000; + uint256[] memory lpBalances = new uint256[](1); + lpBalances[0] = lpBalance; + changePrank(_minterOne); + _pool.increaseLPAllowance(address(_positionManager), indexes, lpBalances); + IPositionManagerOwnerActions.MintParams memory mintParams = IPositionManagerOwnerActions.MintParams( + _minterOne, address(_pool), keccak256("ERC20_NON_SUBSET_HASH")); + uint256 tokenId = _positionManager.mint(mintParams); + IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( + tokenId, indexes + ); + _positionManager.memorializePositions(memorializeParams); + _registerLender(address(_positionManager), indexes); + skip(4 days); + + // stake rewards + _stakeToken(address(_pool), _minterOne, tokenId); + skip(7 days); + + // repay debt to accumulate some reserves + changePrank(_borrower); + pool.repayDebt(_borrower, type(uint256).max, collateralToPledge, _borrower, MAX_FENWICK_INDEX); + skip(2 hours); + + // burn + changePrank(_bidder); + pool.kickReserveAuction(); + skip(11 hours); + _ajnaToken.approve(address(_pool), type(uint256).max); + (,, uint256 curClaimableReservesRemaining,,) = _poolUtils.poolReservesInfo(address(_pool)); + _pool.takeReserves(curClaimableReservesRemaining); + + // unstake with no interest earned + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_pool), tokenId); + _rewardsManager.unstake(tokenId); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), _minterOne); + } + + function testUnstakeNoBurn() external { + skip(10); + ERC20Pool pool = ERC20Pool(address(_pool)); + + // deposit into some buckets and mint an NFT + uint256[] memory indexes = new uint256[](3); + indexes[0] = 2000; + indexes[1] = 2500; + indexes[2] = 3000; + uint256 tokenId = _mintAndMemorializePositionNFT({ + indexes: indexes, + minter: _minterOne, + mintAmount: 1_000 * 1e18, + pool: address(_pool) + }); + + // draw debt + uint256 borrowAmount = 1_500 * 1e18; + uint256 limitIndex = 2_500; + assertEq(_pool.depositIndex(borrowAmount), limitIndex); + assertGt(_pool.depositSize(), borrowAmount); + ( + uint256 collateralToPledge + ) = _createTestBorrower(address(_pool), _borrower, borrowAmount, limitIndex); + pool.drawDebt(_borrower, borrowAmount, limitIndex, collateralToPledge); + skip(3 days); + + // stake rewards + _stakeToken(address(_pool), _minterOne, tokenId); + skip(7 days); + + // repay debt to accumulate some reserves + changePrank(_borrower); + pool.repayDebt(_borrower, type(uint256).max, collateralToPledge, _borrower, MAX_FENWICK_INDEX); + skip(2 hours); + + // start auction, but no burn + changePrank(_bidder); + pool.kickReserveAuction(); + skip(11 hours); + + // unstake + changePrank(_minterOne); + vm.expectEmit(true, true, true, true); + emit Unstake(_minterOne, address(_pool), tokenId); + _rewardsManager.unstake(tokenId); + assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), _minterOne); + } + function testUpdateExchangeRatesAndClaimRewards() external { skip(10); From de64f8bb48a8688db81970112b32628bf6a6d732 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:38:57 +0300 Subject: [PATCH 60/70] Update diagrams (#749) * Update diagrams * Docs update changes (#751) * cleaned up some drawios and removed some redundant images * Regenerate html files --------- Co-authored-by: Ian Harvey Co-authored-by: grandizzy --------- Co-authored-by: Ian Harvey Co-authored-by: Ian Harvey --- docs/drawio/addCollateral.drawio | 202 +++++++++++++- docs/drawio/addQuoteToken.drawio | 246 ++++++++++++++++- docs/drawio/ajnaContractsArchitecture.drawio | 150 +++++++++++ docs/drawio/bucketTake.drawio | 235 +++++++++++++++- docs/drawio/components.drawio | 1 - docs/drawio/drawDebt.drawio | 245 ++++++++++++++++- docs/drawio/kick.drawio | 198 +++++++++++++- docs/drawio/kickWithDeposit.drawio | 230 +++++++++++++++- docs/drawio/moveQuoteToken.drawio | 229 +++++++++++++++- docs/drawio/poolContractsArchitecture.drawio | 1 - docs/drawio/removeCollateral.drawio | 162 ++++++++++- docs/drawio/removeQuoteToken.drawio | 200 +++++++++++++- docs/drawio/repayDebt.drawio | 252 +++++++++++++++++- docs/drawio/settle.drawio | 266 ++++++++++++++++++- docs/drawio/take.drawio | 203 +++++++++++++- docs/html/addCollateral.html | 2 +- docs/html/addQuoteToken.html | 2 +- docs/html/bucketTake.html | 2 +- docs/html/components.html | 2 +- docs/html/drawDebt.html | 2 +- docs/html/kick.html | 2 +- docs/html/kickWithDeposit.html | 2 +- docs/html/moveQuoteToken.html | 2 +- docs/html/removeCollateral.html | 2 +- docs/html/removeQuoteToken.html | 2 +- docs/html/repayDebt.html | 2 +- docs/html/settle.html | 4 +- docs/html/take.html | 2 +- docs/jpeg/ajnaContractsArchitecture.jpeg | Bin 83293 -> 86745 bytes docs/jpeg/poolContract.jpeg | Bin 51883 -> 103106 bytes 30 files changed, 2820 insertions(+), 28 deletions(-) create mode 100644 docs/drawio/ajnaContractsArchitecture.drawio delete mode 100644 docs/drawio/components.drawio delete mode 100644 docs/drawio/poolContractsArchitecture.drawio diff --git a/docs/drawio/addCollateral.drawio b/docs/drawio/addCollateral.drawio index b62d8a395..17de80b9b 100644 --- a/docs/drawio/addCollateral.drawio +++ b/docs/drawio/addCollateral.drawio @@ -1 +1,201 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/drawio/addQuoteToken.drawio b/docs/drawio/addQuoteToken.drawio index 0ea7fef45..d16ed372b 100644 --- a/docs/drawio/addQuoteToken.drawio +++ b/docs/drawio/addQuoteToken.drawio @@ -1 +1,245 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/drawio/ajnaContractsArchitecture.drawio b/docs/drawio/ajnaContractsArchitecture.drawio new file mode 100644 index 000000000..c2928128f --- /dev/null +++ b/docs/drawio/ajnaContractsArchitecture.drawio @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/bucketTake.drawio b/docs/drawio/bucketTake.drawio index 942266631..a9ed91da5 100644 --- a/docs/drawio/bucketTake.drawio +++ b/docs/drawio/bucketTake.drawio @@ -1 +1,234 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/components.drawio b/docs/drawio/components.drawio deleted file mode 100644 index ddebbac67..000000000 --- a/docs/drawio/components.drawio +++ /dev/null @@ -1 +0,0 @@ -7V1bc6M4Fv41rtp9SBd3zGPuM1vp3mzSszP9tCWDbLORkVfg2JlfPxJINiDsYEcCsu2ngAxH6NO5fOdIkJF9vdjcE7Ccf8URRCPLiDYj+2ZkWaZjO/QPa3krWnzDLxpmJI74RbuG5/hPyBsN3rqKI5hWLswwRlm8rDaGOElgmFXaACF4Xb1silG11yWYQanhOQRIbv09jrJ50Tq2/F37LzCezUXPphcUvyyAuJiPJJ2DCK9LTfbtyL4mGGfF0WJzDREDT+BS3He359ftgxGYZG1usJZ//rj9/o/pb/Nvz5N79+bmP6/mBZfyCtCKD/hyFWYxTlL+0NmbQCJdxwsEEnp2tZ7HGXxegpD9tKYTT9vm2QLRM5MeTmOErjHCJL/RHk+nBnBou/zEontIMrgpNfER3EO8gBl5o5eIX8ducQtXJ9fn6K53k2MKxOeVieGNgCvEbCt7hxk94LAdAaEnQXgIujQDJONqbqjBxAvsKiaOjIkdBDImtjZM/AZMPJQx3cB0nGVwvP+tsPjhIs2BuaQXUEw2OTzid3o0Y39/TTJIEsAeAcUTAkhM3QMXPiHiKtFCn77oUTTXZoZCnFWVF6B4ltDjELKOaAObiJg6hEv+wyKOInb7FYH0ccEEialc4jjJciTdq5F7w2StMlwMKRedZgS/QGEZCc4VomwsvEnWiYOK1954zAZj8RuMxdGlF04gKcYj9ea/QLSkWCvzONPpBHpTrlTc2Mxma1ODrBX4FRO0ZaD9BgP0PU04jyWYr1bhC8wUevUtxgo82JYUDMery4p6A5fUmIcK4VjY7HAgFIJLGH6lpGiwAJoVAH3D6R1AmZ09YKCSmqk14vHgAAwkqGBE6T0/xSSb4xmmZOJ213pF8CqJYMSD+u6aB4yXHMX/wix743GFxfgaxrugYzERMIkuWSayC/C05S5mQzlMAVO8IiE8ND5ONSmnnMEWAY0N/uBUEohAFr9Wcx/102JJen1IpTVQZtOvGvsAKLNpN4CiiDPfbs6cuaZ6n4g0y9ZCvRL1J5Bcdpiqq4LTsb5UU/gGrtxxmHDdvaYXxa+sR67GO8tCcJrJdnZBr40IWN/ASfa3v5fsKRdTtbLjJRO4BG9V0Y3GW+mste3m/fZhufUcDYEJRFcgfJnlkfg0Oz9eL+tqKWJSSS3HnabKMvejtOFT2rzlBYOzeTnaskLENV4sPhm4blBB1vR6h9b0Jfw6YN1wE2d/8NvZ8Q92TNWuOLvZlH66eRMnCR3vH7sL2ekPIY+d7G7Lz8R9Bxh+iECaxqFKks9N512SL3RnKCx/3KcamCeqgWVWFOGLYXjvKEN+9ghJTGFj5Pc9DVGdA7ZVj2HlgJZcHDo23TH3pTurFLLuKwsFH8h3GGIPjJToS3rq6qKEAwkDlGLJdrWUP9OovCDZFGMujC+Gb1XijAigrbWFC39kYy9JtnynJlcUyoUQPJ2mVLHrCrd9yg94KJn3MxKgLvqHxmQ6CdWULJzAqwAVBHKgt1xHDvTbBWj1Nry/ZHFcdgMmNFkAYVZoJj9UlzxdI2Y3CgU+Qeb0QRK+3a8AiVSK/rpCuRNBKoW2TxdPzU1BFP2LtsLvNOVL1Ke+YLkk+BU+PP5znUCSzuOl+j4WtAOdYyBQdw9MJdMpJA+PqXrhL3H4okfq73E254tr6jtYU+GsJHOFk0gDKnlt+hqBeMFi6RNMIXmFfAuNhgkGL6KP9P+7CNSW4rjHhlGvWvi3AjlbbirumoG2ICov8ikjwhFEcAYyxoZZSEmZCmGWA2x+Hmq8j2gdTYIds8pUTeOQTjTT3eoKqeDQ+pmuKGaes61BqdR2G2krlfqYm+m3KLetwPwYlQowuopyZq3kMkE4fBmVay7moYl9t+ZiOS1rLiI+DaXoIi8q3CGQzhEGCdPZ4Wa/rldfnzEtdyzH7m4r3ZaqhUPNCTDjjlM20Wz7kHpamou+g1BDTgY2d/Jz60ljz3R65x/aL5tWdx0KAWWTNBpM0te2rOf0Eua2IascsErxS1XIiqgt5A96anD9QMwbt4x5QgfUxbxmLu0aUkiw3ZqUYlT8Rg20Wt5nfft0bRlq42gQhOb0IGE5ooosbb8xHdvrO47abq9Wa3ZptadboNg00YMFfmx25aU+aiW+ZQ7XTLxxSzPZbmypGoqjCUrH7sVQGgONeTDQnBoSK7bSYUpn+y2NS7wm0EN486za+0J7wpssS86efOFzhaxi4NpCpdOPix9G/aFpU9BBde1DuQz7ROWSZbnOuFPlsuX3Tbc87I7m0pi73sHFmQY65hlW33TM6SeJOiHKfCxanEoAPxBl2iZRXUWZBqLTeWTwhqNtw48MrVVMt+b4suvq3O03pt88sRi0328wu2387dHvy0vRDEm2NwO9qXw/X/PKgC8W+XuE0pKgbKzxty4l9/hq3YnFZOfoqawVk0XK8F4x2dY2iTKtjOASYfbIy6baxSedvhP2otbWrV1PXotr2kajb6rkUHCeKh6mBzZVrlwOfIJsQGmPn4E5Gtft9muOq+fKaVunn3xx5eD9DKbFVlNIvoGFbmTHeoD1G+qu3QIrh/JHtlc1xsm3u+/P/75XB6vKL6XZfv1zHL2TIlfV+wL5zlua3hU7VvS8bf1J9zuzLcLqpU7yr0d91yI730ItdjWft04PgTSYNR/sjn3Zd9hNvsPQ5juaPrP4gbdVKIgIUJ4HkIa3VfS+DPP5X1QpetA5BUUPX8GmqZOz1e9ZjDBPs3ptm5bd/d8RLU0Xm+/VMqKznH8bFKbZEz1upVMlmw1DstoKOOvKe5/0EKxcsEurZfVS2/dSXLkCcFGU2ihMlFXkruy3p1/Z1P6ktQC79q5160nTVgvw5FpA/g2lvHTDuP1PPFuOXSuyeWJza2m6gk5nSy4xnGdLrOj40mzJdYtuZ0uuW5zOpzr9qplCHq6fY+rsoahxfOYiwZk+7aHaNW/huA2h2Ow0w/ZUVufO/qIhb4dkBqmws984+41TWcZ43MJvOIr8BtPV7b+nKTbt7P7Jj337Fw== \ No newline at end of file diff --git a/docs/drawio/drawDebt.drawio b/docs/drawio/drawDebt.drawio index 2653b64ad..e4f8da895 100644 --- a/docs/drawio/drawDebt.drawio +++ b/docs/drawio/drawDebt.drawio @@ -1 +1,244 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/kick.drawio b/docs/drawio/kick.drawio index c4758a438..55484ddb1 100644 --- a/docs/drawio/kick.drawio +++ b/docs/drawio/kick.drawio @@ -1 +1,197 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/kickWithDeposit.drawio b/docs/drawio/kickWithDeposit.drawio index 6c1076aed..45fad8060 100644 --- a/docs/drawio/kickWithDeposit.drawio +++ b/docs/drawio/kickWithDeposit.drawio @@ -1 +1,229 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/moveQuoteToken.drawio b/docs/drawio/moveQuoteToken.drawio index 5ec0cbef3..5508749ec 100644 --- a/docs/drawio/moveQuoteToken.drawio +++ b/docs/drawio/moveQuoteToken.drawio @@ -1 +1,228 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/poolContractsArchitecture.drawio b/docs/drawio/poolContractsArchitecture.drawio deleted file mode 100644 index 6000897e3..000000000 --- a/docs/drawio/poolContractsArchitecture.drawio +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/docs/drawio/removeCollateral.drawio b/docs/drawio/removeCollateral.drawio index b1f8199cb..81412a0d5 100644 --- a/docs/drawio/removeCollateral.drawio +++ b/docs/drawio/removeCollateral.drawio @@ -1 +1,161 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/removeQuoteToken.drawio b/docs/drawio/removeQuoteToken.drawio index dfe6768ca..69b3ce1d4 100644 --- a/docs/drawio/removeQuoteToken.drawio +++ b/docs/drawio/removeQuoteToken.drawio @@ -1 +1,199 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/repayDebt.drawio b/docs/drawio/repayDebt.drawio index a9a221166..f35d240d5 100644 --- a/docs/drawio/repayDebt.drawio +++ b/docs/drawio/repayDebt.drawio @@ -1 +1,251 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/settle.drawio b/docs/drawio/settle.drawio index 30d3e9999..f0ce64d71 100644 --- a/docs/drawio/settle.drawio +++ b/docs/drawio/settle.drawio @@ -1 +1,265 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/drawio/take.drawio b/docs/drawio/take.drawio index f8a6de366..1cf7fec86 100644 --- a/docs/drawio/take.drawio +++ b/docs/drawio/take.drawio @@ -1 +1,202 @@ -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 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/html/addCollateral.html b/docs/html/addCollateral.html index 2e0301cd4..40d331e82 100644 --- a/docs/html/addCollateral.html +++ b/docs/html/addCollateral.html @@ -5,7 +5,7 @@ addCollateral -
+
\ No newline at end of file diff --git a/docs/html/addQuoteToken.html b/docs/html/addQuoteToken.html index 11fd15bda..ffe6f6629 100644 --- a/docs/html/addQuoteToken.html +++ b/docs/html/addQuoteToken.html @@ -5,7 +5,7 @@ addQuoteToken -
+
\ No newline at end of file diff --git a/docs/html/bucketTake.html b/docs/html/bucketTake.html index ff4d63be4..943234193 100644 --- a/docs/html/bucketTake.html +++ b/docs/html/bucketTake.html @@ -5,7 +5,7 @@ bucketTake -
+
\ No newline at end of file diff --git a/docs/html/components.html b/docs/html/components.html index 08bd8ff7a..15936d82d 100644 --- a/docs/html/components.html +++ b/docs/html/components.html @@ -5,7 +5,7 @@ components -
+
\ No newline at end of file diff --git a/docs/html/drawDebt.html b/docs/html/drawDebt.html index 1e57c7099..5d72460f2 100644 --- a/docs/html/drawDebt.html +++ b/docs/html/drawDebt.html @@ -5,7 +5,7 @@ drawDebt -
+
\ No newline at end of file diff --git a/docs/html/kick.html b/docs/html/kick.html index af55df35c..15326004e 100644 --- a/docs/html/kick.html +++ b/docs/html/kick.html @@ -5,7 +5,7 @@ kick -
+
\ No newline at end of file diff --git a/docs/html/kickWithDeposit.html b/docs/html/kickWithDeposit.html index 45b15fa7b..297065bdf 100644 --- a/docs/html/kickWithDeposit.html +++ b/docs/html/kickWithDeposit.html @@ -5,7 +5,7 @@ kickWithDeposit -
+
\ No newline at end of file diff --git a/docs/html/moveQuoteToken.html b/docs/html/moveQuoteToken.html index 4df0727f6..919247cc7 100644 --- a/docs/html/moveQuoteToken.html +++ b/docs/html/moveQuoteToken.html @@ -5,7 +5,7 @@ moveQuoteToken -
+
\ No newline at end of file diff --git a/docs/html/removeCollateral.html b/docs/html/removeCollateral.html index b7622c8af..46f3acde3 100644 --- a/docs/html/removeCollateral.html +++ b/docs/html/removeCollateral.html @@ -5,7 +5,7 @@ removeCollateral -
+
\ No newline at end of file diff --git a/docs/html/removeQuoteToken.html b/docs/html/removeQuoteToken.html index 5c04479b9..d79f643d7 100644 --- a/docs/html/removeQuoteToken.html +++ b/docs/html/removeQuoteToken.html @@ -5,7 +5,7 @@ removeQuoteToken -
+
\ No newline at end of file diff --git a/docs/html/repayDebt.html b/docs/html/repayDebt.html index 02d7c2d78..b0771b020 100644 --- a/docs/html/repayDebt.html +++ b/docs/html/repayDebt.html @@ -5,7 +5,7 @@ repayDebt -
+
\ No newline at end of file diff --git a/docs/html/settle.html b/docs/html/settle.html index e8fedd42e..9dad03996 100644 --- a/docs/html/settle.html +++ b/docs/html/settle.html @@ -2,10 +2,10 @@ -addCollateral +settle -
+
\ No newline at end of file diff --git a/docs/html/take.html b/docs/html/take.html index 308899a7d..29246f7c1 100644 --- a/docs/html/take.html +++ b/docs/html/take.html @@ -5,7 +5,7 @@ take -
+
\ No newline at end of file diff --git a/docs/jpeg/ajnaContractsArchitecture.jpeg b/docs/jpeg/ajnaContractsArchitecture.jpeg index 7a4ae3937199e9e058a5069861b7656a26db5da8..d8afd034b2e480f03051220adee2689d3289b03d 100644 GIT binary patch literal 86745 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 bV*SFHOjY{$)w-AVS2;PR+$Fd2zVZvdS0j5~ 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 literal 83293 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

6oVID^`x_l^ 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 M_@9jh_dl=y1xXK_l>h($ diff --git a/docs/jpeg/poolContract.jpeg b/docs/jpeg/poolContract.jpeg index 5612d316d5abcf1bc82de73b6ac9e07771b02031..efa64cd2334a951664599da37c32327cb650bb67 100644 GIT binary patch literal 103106 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 From 9267b41548efc7de3c380ef7d11fc756d39fc32e Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Wed, 19 Apr 2023 22:00:20 +0530 Subject: [PATCH 61/70] Invariants for multiple bucket ranges (#742) * Add multiple buckets range for erc20Pool invariants * Add multiple buckets range for erc721Pool invariants by reducing MAX_QUOTE_LIMIT with respect to bucket price * Update README * Code cleanup * Update github workflow env * Add NO_OF_BUCKETS from env, add 1 and 7388 buckets to multiple bucket ranges --- .env.example | 7 +++++++ .github/workflows/forge-test.yml | 3 +++ Makefile | 19 +++++++++++------ foundry.toml | 4 ++-- tests/README.md | 8 +++++++ .../unbounded/BaseERC20PoolHandler.sol | 4 ++-- .../unbounded/BaseERC721PoolHandler.sol | 21 ++++++++++++++++--- .../test-invariant-erc20-buckets.sh | 4 ++++ .../test-invariant-erc721-buckets.sh | 4 ++++ 9 files changed, 61 insertions(+), 13 deletions(-) create mode 100755 tests/forge/invariants/test-invariant-erc20-buckets.sh create mode 100755 tests/forge/invariants/test-invariant-erc721-buckets.sh diff --git a/.env.example b/.env.example index 6dc2cf5ab..78b33e0aa 100644 --- a/.env.example +++ b/.env.example @@ -30,3 +30,10 @@ DEPLOY_KEY= # Default token precisions for (invariant) testing QUOTE_PRECISION = 18 COLLATERAL_PRECISION = 18 + +# Default bucket Index for (invariant) testing +BUCKET_INDEX_ERC20 = 2570 +BUCKET_INDEX_ERC721 = 850 + +# Default no of buckets to use for (invariant) testing +NO_OF_BUCKETS = 3 \ No newline at end of file diff --git a/.github/workflows/forge-test.yml b/.github/workflows/forge-test.yml index 094753398..7be5ad751 100644 --- a/.github/workflows/forge-test.yml +++ b/.github/workflows/forge-test.yml @@ -11,6 +11,9 @@ jobs: env: QUOTE_PRECISION: 18 COLLATERAL_PRECISION: 18 + BUCKET_INDEX_ERC20: 2570 + BUCKET_INDEX_ERC721: 850 + NO_OF_BUCKETS: 3 strategy: fail-fast: true diff --git a/Makefile b/Makefile index 0b7dc49cc..e0d3f619a 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,11 @@ QUOTE_PRECISION = 18 COLLATERAL_PRECISION = 18 +# Default buckets for invariant testing +BUCKET_INDEX_ERC20 = 2570 +BUCKET_INDEX_ERC721 = 850 +NO_OF_BUCKETS = 3 + all: clean install build # Clean the repo @@ -21,14 +26,16 @@ build :; forge clean && forge build test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM test-with-gas-report :; forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM test-load :; forge test --match-test testLoad --gas-report -test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest -test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC20 -test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt invariant --nmc RegressionTest --mc ERC721 -test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression -test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} forge t --mt test_regression --mc ERC20 -test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} forge t --mt test_regression --mc ERC721 +test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest +test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC20 +test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC721 +test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression +test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC20 +test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC721 coverage :; forge coverage --no-match-test "testLoad|invariant" test-invariant-erc20-precision :; ./tests/forge/invariants/test-invariant-erc20-precision.sh +test-invariant-erc20-buckets :; ./tests/forge/invariants/test-invariant-erc20-buckets.sh +test-invariant-erc721-buckets :; ./tests/forge/invariants/test-invariant-erc721-buckets.sh # Generate Gas Snapshots snapshot :; forge clean && forge snapshot diff --git a/foundry.toml b/foundry.toml index dcf017131..9b89e209c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -25,6 +25,6 @@ runs = 300 [invariant] runs = 1000 # Number of times that a sequence of function calls is generated and run -depth = 20 # Number of function calls made in a given run. +depth = 20 # Number of function calls made in a given run. call_override = false # Override calls -fail_on_revert = true # Fail the test if the contract reverts \ No newline at end of file +fail_on_revert = true # Fail the test if the contract reverts \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 71a894e95..0f7aa902d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -36,6 +36,14 @@ make test-invariant-erc721 QUOTE_PRECISION= ```bash make test-invariant-erc20-precision ``` +- run ERC20 Pool invariant tests for multiple bucket ranges: +```bash +make test-invariant-erc20-buckets +``` +- run ERC721 Pool invariant tests for multiple bucket ranges: +```bash +make test-invariant-erc721-buckets +``` - run regression test for both ERC20 and ERC721 Pool: ```bash make test-regression diff --git a/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol index b396d4c0c..a3bfc66b6 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/unbounded/BaseERC20PoolHandler.sol @@ -31,8 +31,8 @@ abstract contract BaseERC20PoolHandler is BaseHandler { address testContract_ ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { - LENDER_MIN_BUCKET_INDEX = 2570; - LENDER_MAX_BUCKET_INDEX = 2572; + LENDER_MIN_BUCKET_INDEX = vm.envUint("BUCKET_INDEX_ERC20"); + LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envUint("NO_OF_BUCKETS") - 1; MIN_QUOTE_AMOUNT = 1e3; MAX_QUOTE_AMOUNT = 1e30; diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol index 3b1871134..3acdc6b17 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/BaseERC721PoolHandler.sol @@ -31,11 +31,26 @@ abstract contract BaseERC721PoolHandler is BaseHandler { address testContract_ ) BaseHandler(pool_, ajna_, quote_, poolInfo_, testContract_) { - LENDER_MIN_BUCKET_INDEX = 850; - LENDER_MAX_BUCKET_INDEX = 852; + LENDER_MIN_BUCKET_INDEX = vm.envUint("BUCKET_INDEX_ERC721"); + LENDER_MAX_BUCKET_INDEX = LENDER_MIN_BUCKET_INDEX + vm.envUint("NO_OF_BUCKETS") - 1; MIN_QUOTE_AMOUNT = 1e3; - MAX_QUOTE_AMOUNT = 1e28; + /* + Lower the bucket price, higher number of NFT mints and transfers. + So this formulae is used to avoid out of gas error and also run the invariants in a reasonable time + + BUCKET_INDEX MAX_QUOTE_AMOUNT + 1 1e31 + 500 1e30 + 1500 1e26 + 2500 1e22 + 3500 1e18 + 4500 1e14 + 5500 1e10 + 6500 1e6 + 7368 1e3 + */ + MAX_QUOTE_AMOUNT = 10 ** (31 - (LENDER_MIN_BUCKET_INDEX / 260)); MIN_COLLATERAL_AMOUNT = 1; MAX_COLLATERAL_AMOUNT = 100; diff --git a/tests/forge/invariants/test-invariant-erc20-buckets.sh b/tests/forge/invariants/test-invariant-erc20-buckets.sh new file mode 100755 index 000000000..4e76a2c0e --- /dev/null +++ b/tests/forge/invariants/test-invariant-erc20-buckets.sh @@ -0,0 +1,4 @@ +for bucket_index in 1 500 1500 2500 3500 4500 5500 6500 7369 +do + make test-invariant-erc20 QUOTE_PRECISION=18 COLLATERAL_PRECISION=18 BUCKET_INDEX_ERC20=${bucket_index} +done \ No newline at end of file diff --git a/tests/forge/invariants/test-invariant-erc721-buckets.sh b/tests/forge/invariants/test-invariant-erc721-buckets.sh new file mode 100755 index 000000000..897400f1a --- /dev/null +++ b/tests/forge/invariants/test-invariant-erc721-buckets.sh @@ -0,0 +1,4 @@ +for bucket_index in 1 500 1500 2500 3500 4500 5500 6500 7369 +do + make test-invariant-erc721 QUOTE_PRECISION=18 BUCKET_INDEX_ERC721=${bucket_index} +done \ No newline at end of file From ededde72c01240d6efd0e3a394f81a637008f0cc Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 20 Apr 2023 07:55:07 +0300 Subject: [PATCH 62/70] Improve `SettlerActions.settlePoolDebt` function (#752) * Add _settlePoolDebtWithDeposit function * Add _forgiveBadDebt function * Changes after review: improve comments * Changes after review: remove usage of struct local vars in _forgiveBadDebt function, added more comments * Changes after review: add more comments --- src/libraries/external/SettlerActions.sol | 313 +++++++++++++--------- 1 file changed, 192 insertions(+), 121 deletions(-) diff --git a/src/libraries/external/SettlerActions.sol b/src/libraries/external/SettlerActions.sol index d24bf9a6d..8dd4c22b9 100644 --- a/src/libraries/external/SettlerActions.sol +++ b/src/libraries/external/SettlerActions.sol @@ -47,7 +47,6 @@ library SettlerActions { struct SettleLocalVars { uint256 collateralUsed; // [WAD] collateral used to settle debt uint256 debt; // [WAD] debt to settle - uint256 depositToRemove; // [WAD] deposit used by settle auction uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle uint256 hpbLPs; // [WAD] amount of LP in HPB bucket @@ -82,7 +81,10 @@ library SettlerActions { /***************************/ /** - * @notice Settles the debt of the given loan / borrower. + * @notice Settles the debt of the given loan / borrower by performing following steps: + * 1. settle debt with `HPB`s deposit, up to specified buckets depth. + * 2. settle debt with pool reserves (if there's still debt and no collateral left after step 1). + * 3. forgive bad debt from next `HPB`, up to remaining buckets depth (and if there's still debt after step 2). * @dev write state: * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): * - update values array state @@ -120,135 +122,41 @@ library SettlerActions { result_.t0DebtSettled = borrower.t0Debt; result_.collateralSettled = borrower.collateral; - // auction has debt to cover with remaining collateral - while (params_.bucketDepth != 0 && borrower.t0Debt != 0 && borrower.collateral != 0) { - SettleLocalVars memory vars; - - (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); - vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); - vars.unscaledDeposit = vars.hpbUnscaledDeposit; - vars.price = _priceAt(vars.index); - - if (vars.unscaledDeposit != 0) { - vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // current debt to be settled - vars.maxSettleableDebt = Maths.floorWmul(borrower.collateral, vars.price); // max debt that can be settled with existing collateral - vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); - - // enough deposit in bucket and collateral avail to settle entire debt - if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { - // remove only what's needed to settle the debt - vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); - vars.collateralUsed = Maths.wdiv(vars.debt, vars.price); - - // settle the entire debt - borrower.t0Debt = 0; - } - // enough collateral, therefore not enough deposit to settle entire debt, we settle only deposit amount - else if (vars.maxSettleableDebt >= vars.scaledDeposit) { - vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); - - // subtract from debt the corresponding t0 amount of deposit - borrower.t0Debt -= Maths.floorWdiv(vars.scaledDeposit, poolState_.inflator); - } - // settle constrained by collateral available - else { - vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); - vars.collateralUsed = borrower.collateral; - - borrower.t0Debt -= Maths.floorWdiv(vars.maxSettleableDebt, poolState_.inflator); - } - - // remove settled collateral from loan - borrower.collateral -= vars.collateralUsed; - - Bucket storage hpb = buckets_[vars.index]; - vars.hpbLPs = hpb.lps; - vars.hpbCollateral = hpb.collateral + vars.collateralUsed; - - // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) - vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); - vars.hpbUnscaledDeposit -= vars.unscaledDeposit; - - // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) - Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); - - // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs - if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { - hpb.lps = 0; - hpb.bankruptcyTime = block.timestamp; - - emit BucketBankruptcy( - vars.index, - vars.hpbLPs - ); - } else { - // add settled collateral into bucket - hpb.collateral = vars.hpbCollateral; - } - - } else { - // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 - Buckets.addCollateral( - buckets_[vars.index], - params_.borrower, - 0, // zero deposit in bucket - borrower.collateral, - vars.price - ); - borrower.collateral = 0; // entire collateral added into bucket - } - - --params_.bucketDepth; - } + // 1. settle debt with HPB deposit + ( + borrower.t0Debt, + borrower.collateral, + params_.bucketDepth + ) = _settlePoolDebtWithDeposit( + buckets_, + deposits_, + params_, + borrower, + poolState_.inflator + ); - // if there's still debt and no collateral if (borrower.t0Debt != 0 && borrower.collateral == 0) { - + // 2. settle debt with pool reserves uint256 assets = Maths.wmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; uint256 liabilities = Deposits.treeSum(deposits_) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; - // settle debt from reserves (assets - liabilities) if reserves positive, round reserves down however if (assets > liabilities) { borrower.t0Debt -= Maths.min(borrower.t0Debt, Maths.floorWdiv(assets - liabilities, poolState_.inflator)); } - // if there's still debt after settling from reserves then start to forgive amount from next HPB - // loop through remaining buckets if there's still debt to settle - while (params_.bucketDepth != 0 && borrower.t0Debt != 0) { - SettleLocalVars memory vars; - - (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); - vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); - vars.depositToRemove = Maths.wmul(vars.scale, vars.unscaledDeposit); - vars.debt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - - // enough deposit in bucket to settle entire debt - if (vars.depositToRemove >= vars.debt) { - Deposits.unscaledRemove(deposits_, vars.index, Maths.wdiv(vars.debt, vars.scale)); - borrower.t0Debt = 0; // no remaining debt to settle - - // not enough deposit to settle entire debt, we settle only deposit amount - } else { - borrower.t0Debt -= Maths.floorWdiv(vars.depositToRemove, poolState_.inflator); // subtract from remaining debt the corresponding t0 amount of deposit - - Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // Remove all deposit from bucket - Bucket storage hpbBucket = buckets_[vars.index]; - - if (hpbBucket.collateral == 0) { // existing LP for the bucket shall become unclaimable. - hpbBucket.lps = 0; - hpbBucket.bankruptcyTime = block.timestamp; - - emit BucketBankruptcy( - vars.index, - hpbBucket.lps - ); - } - } - - --params_.bucketDepth; + // 3. forgive bad debt from next HPB + if (borrower.t0Debt != 0) { + borrower.t0Debt = _forgiveBadDebt( + buckets_, + deposits_, + params_, + borrower, + poolState_.inflator + ); } } + // complete result struct with debt settled result_.t0DebtSettled -= borrower.t0Debt; emit Settle( @@ -256,8 +164,8 @@ library SettlerActions { result_.t0DebtSettled ); + // if entire debt was settled then settle auction if (borrower.t0Debt == 0) { - // settle auction (borrower.collateral, ) = _settleAuction( auctions_, buckets_, @@ -268,8 +176,9 @@ library SettlerActions { ); } + // complete result struct with debt and collateral post action and collateral settled result_.debtPostAction = borrower.t0Debt; - result_.collateralRemaining = borrower.collateral; + result_.collateralRemaining = borrower.collateral; result_.collateralSettled -= result_.collateralRemaining; // update borrower state @@ -397,4 +306,166 @@ library SettlerActions { delete auctions_.liquidations[borrower_]; } + /** + * @notice Called to settle debt using `HPB` deposits. + * @param buckets_ Struct for pool buckets state. + * @param deposits_ Struct for pool deposits state. + * @param params_ Struct containing params for settle action. + * @param borrower_ Struct containing borrower details. + * @param inflator_ Current pool inflator. + * @return remainingt0Debt_ Remaining borrower `t0` debt after settle with `HPB`. + * @return remainingCollateral_ Remaining borrower collateral after settle with `HPB`. + * @return bucketDepth_ Number of buckets to use for forgiving debt in case there's more remaining. + */ + function _settlePoolDebtWithDeposit( + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + SettleParams memory params_, + Borrower memory borrower_, + uint256 inflator_ + ) internal returns (uint256 remainingt0Debt_, uint256 remainingCollateral_, uint256 bucketDepth_) { + remainingt0Debt_ = borrower_.t0Debt; + remainingCollateral_ = borrower_.collateral; + bucketDepth_ = params_.bucketDepth; + + while (bucketDepth_ != 0 && remainingt0Debt_ != 0 && remainingCollateral_ != 0) { + SettleLocalVars memory vars; + + (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); + vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); + vars.unscaledDeposit = vars.hpbUnscaledDeposit; + vars.price = _priceAt(vars.index); + + if (vars.unscaledDeposit != 0) { + vars.debt = Maths.wmul(remainingt0Debt_, inflator_); // current debt to be settled + vars.maxSettleableDebt = Maths.floorWmul(remainingCollateral_, vars.price); // max debt that can be settled with existing collateral + vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); + + // 1) bucket deposit covers remaining loan debt to settle, loan's collateral can cover remaining loan debt to settle + if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { + // remove only what's needed to settle the debt + vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); + vars.collateralUsed = Maths.wdiv(vars.debt, vars.price); + + // settle the entire debt + remainingt0Debt_ = 0; + } + // 2) bucket deposit can not cover all of loan's remaining debt, bucket deposit is the constraint + else if (vars.maxSettleableDebt >= vars.scaledDeposit) { + vars.collateralUsed = Maths.wdiv(vars.scaledDeposit, vars.price); + + // subtract from debt the corresponding t0 amount of deposit + remainingt0Debt_ -= Maths.floorWdiv(vars.scaledDeposit, inflator_); + } + // 3) loan's collateral can not cover remaining loan debt to settle, loan collateral is the constraint + else { + vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); + vars.collateralUsed = remainingCollateral_; + + remainingt0Debt_ -= Maths.floorWdiv(vars.maxSettleableDebt, inflator_); + } + + // remove settled collateral from loan + remainingCollateral_ -= vars.collateralUsed; + + // use HPB bucket to swap loan collateral for loan debt + Bucket storage hpb = buckets_[vars.index]; + vars.hpbLPs = hpb.lps; + vars.hpbCollateral = hpb.collateral + vars.collateralUsed; + + // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) + vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); + vars.hpbUnscaledDeposit -= vars.unscaledDeposit; + + // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) + Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); + + // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs + if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { + hpb.lps = 0; + hpb.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + vars.index, + vars.hpbLPs + ); + } else { + // add settled collateral into bucket + hpb.collateral = vars.hpbCollateral; + } + + } else { + // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 + Buckets.addCollateral( + buckets_[vars.index], + params_.borrower, + 0, // zero deposit in bucket + remainingCollateral_, + vars.price + ); + // entire collateral added into bucket, no borrower pledged collateral remaining + remainingCollateral_ = 0; + } + + --bucketDepth_; + } + } + + /** + * @notice Called to forgive bad debt starting from next `HPB`. + * @param buckets_ Struct for pool buckets state. + * @param deposits_ Struct for pool deposits state. + * @param params_ Struct containing params for settle action. + * @param borrower_ Struct containing borrower details. + * @param inflator_ Current pool inflator. + * @return remainingt0Debt_ Remaining borrower `t0` debt after forgiving bad debt in case not enough buckets used. + */ + function _forgiveBadDebt( + mapping(uint256 => Bucket) storage buckets_, + DepositsState storage deposits_, + SettleParams memory params_, + Borrower memory borrower_, + uint256 inflator_ + ) internal returns (uint256 remainingt0Debt_) { + remainingt0Debt_ = borrower_.t0Debt; + + // loop through remaining buckets if there's still debt to forgive + while (params_.bucketDepth != 0 && remainingt0Debt_ != 0) { + + (uint256 index, , uint256 scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); + uint256 unscaledDeposit = Deposits.unscaledValueAt(deposits_, index); + uint256 depositToRemove = Maths.wmul(scale, unscaledDeposit); + uint256 debt = Maths.wmul(remainingt0Debt_, inflator_); + + // 1) bucket deposit covers entire loan debt to settle, no constraints needed + if (depositToRemove >= debt) { + Deposits.unscaledRemove(deposits_, index, Maths.wdiv(debt, scale)); + // no remaining debt to forgive + remainingt0Debt_ = 0; + + // 2) loan debt to settle exceeds bucket deposit, bucket deposit is the constraint + } else { + // subtract from remaining debt the corresponding t0 amount of deposit + remainingt0Debt_ -= Maths.floorWdiv(depositToRemove, inflator_); + + // Remove all deposit from bucket + Deposits.unscaledRemove(deposits_, index, unscaledDeposit); + + Bucket storage hpbBucket = buckets_[index]; + if (hpbBucket.collateral == 0) { + // existing LP for the bucket shall become unclaimable + hpbBucket.lps = 0; + hpbBucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + index, + hpbBucket.lps + ); + } + } + + --params_.bucketDepth; + } + } + } From 0cab84d7661857d616bafa51a6ed0016f7f0e6e2 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Thu, 20 Apr 2023 12:21:42 +0530 Subject: [PATCH 63/70] ERC20 invariants zero reverts (#747) * Fix evm revert in moveQuoteToken: test_regression_evm_revert_1 * Fix evm revert in bucketTake: test_regression_evm_revert_1, test_regression_reserve_15 * Fix evm revert in kickWithDeposit: test_regression_evm_revert_2 * Add failing reasons and fix description in regression tests * Comments cleanup * Add failing regression test * bring factor inside squaring in EMA calc to avoid overflow * Add description and fix in regression test * Pr feedback * Add failing regression test * Update regression test logs * Check t0Debt2ToCollateral values for ranges before performing arithmetic * Revert "Check t0Debt2ToCollateral values for ranges before performing arithmetic" This reverts commit 7c5a8a377491264e146354ed1cde0947d3908932. * Revert "bring factor inside squaring in EMA calc to avoid overflow" This reverts commit 2f3204d550377246f18337fc0dca423caabd5bf6. * Revert "Fix evm revert in kickWithDeposit: test_regression_evm_revert_2" This reverts commit 9131f2493c9081aaeafa0ea48d9e9afe98d06171. * Comment failing regression tests --------- Co-authored-by: mwc --- .../unbounded/UnboundedBasicPoolHandler.sol | 3 +- .../UnboundedLiquidationPoolHandler.sol | 7 +- .../RegressionTestLiquidationERC20Pool.t.sol | 58 ++++++++++++++ .../RegressionTestReservesERC20Pool.t.sol | 80 +++++++++++++++++-- 4 files changed, 138 insertions(+), 10 deletions(-) diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol index 48d6bafb2..5b70e1c27 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol @@ -101,7 +101,8 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { // **B5**: when moving quote tokens: lender deposit time = timestamp of block when move happened lenderDepositTime[_actor][toIndex_] = Maths.max(fromBucketDepositTime, toBucketDepositTime); // **RE3**: Reserves increase only when moving quote tokens into a bucket below LUP. - increaseInReserves += amount_ - movedAmount_; + // movedAmount_ can be greater than amount_ in case when bucket gets empty by moveQuoteToken + if (amount_ > movedAmount_) increaseInReserves += amount_ - movedAmount_; _fenwickRemove(amount_, fromIndex_); _fenwickAdd(movedAmount_, toIndex_); diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol index 14868e0c0..5a0e82916 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedLiquidationPoolHandler.sol @@ -173,11 +173,12 @@ abstract contract UnboundedLiquidationPoolHandler is BaseHandler { if (afterBucketTakeVars.kickerLps > beforeBucketTakeVars.kickerLps) { // **B7**: when awarded bucket take LP : kicker deposit time = timestamp of block when award happened lenderDepositTime[kicker][bucketIndex_] = block.timestamp; - } else { + } + + if (beforeBucketTakeVars.kickerBond > afterBucketTakeVars.kickerBond) { // **RE7**: Reserves increase by bond penalty on take. - increaseInReserves += afterBucketTakeVars.kickerBond - beforeBucketTakeVars.kickerBond; + increaseInReserves += beforeBucketTakeVars.kickerBond - afterBucketTakeVars.kickerBond; } - // **R7**: Exchange rates are unchanged under depositTakes // **R8**: Exchange rates are unchanged under arbTakes exchangeRateShouldNotChange[bucketIndex_] = true; diff --git a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol index c97b3615a..3218dc647 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -282,4 +282,62 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { invariant_Buckets_B2_B3(); } + /* + Test was reverting when kicker is penalized when auction price > neutral price. + Fixed by making changes in increaseInReserve calculation in case of kicker penalty in 'bucketTake' handler + */ + function test_regression_evm_revert_1() external { + _liquidationERC20PoolHandler.settleAuction(91509897037395202876797812344977844707030753189520454312427981040645023300162, 2439649222, 11529); + _liquidationERC20PoolHandler.bucketTake(6611, 46752666614331262781920, false, 2023645493297626462000000); + } + + // Test reverting with overflow error in dwatp calculation in _meaningfulDeposit in updateInterestState + function _test_regression_evm_revert_2() external { + _liquidationERC20PoolHandler.drawDebt(13141077791835967310451371165744721774, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.kickAuction(3, 53758605435723729358784, 3); + _liquidationERC20PoolHandler.repayDebt(668608315443216098571064749198163965820, 18932325376258851353179065817321260901); + _liquidationERC20PoolHandler.pullCollateral(1660943750216, 7613674427701330576720241); + _liquidationERC20PoolHandler.removeQuoteToken(1900819281467749758886813834006, 976636367449728350520609392573, 4111426995539375716119348324981); + _liquidationERC20PoolHandler.bucketTake(1125834907286324, 3, false, 1); + _liquidationERC20PoolHandler.repayDebt(275298270790660321974310940, 2799); + _liquidationERC20PoolHandler.pullCollateral(0, 9824); + _liquidationERC20PoolHandler.takeAuction(8838, 14328, 1104); + _liquidationERC20PoolHandler.pullCollateral(96246301775147975236686390, 1999999999999999999999999999999999999999999994); + _liquidationERC20PoolHandler.drawDebt(1799754463155649601, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.kickAuction(226, 13555, 999999999999999999929986989979848322131950166); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 326707485783, 5838136568597409883953276821351359808349885898573251821); + _liquidationERC20PoolHandler.bucketTake(3690337820519065642096193886544, 3089359908022049919104337883638, false, 2000000000000000000000000000000000000000744145); + _liquidationERC20PoolHandler.withdrawBonds(117305399704678010034227969424174482909936628260540487, 3984313975337); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 26153396219420651226355270050749728507266848485707520383); + _liquidationERC20PoolHandler.removeCollateral(1999999999999997567145382180442515092966583434, 2757, 245342563594047897888988147512); + _liquidationERC20PoolHandler.moveQuoteToken(298628345, 6995466652341859760028193450571035, 212241589093381072912741572164, 1961348773154021480632696081492); + _liquidationERC20PoolHandler.moveQuoteToken(302104028381701071862379310831040316417001692505762, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 60362684677889414469182462544723956045205775540295406540387737, 2); + _liquidationERC20PoolHandler.bucketTake(2, 75288272353, true, 1); + _liquidationERC20PoolHandler.addCollateral(5005, 147331, 401909674630078222417654); + _liquidationERC20PoolHandler.transferLps(1029198447942867385425606035931054127523339423727036067, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.kickWithDeposit(293745346792645466008958291, 16403); + _liquidationERC20PoolHandler.withdrawBonds(2, 2); + _liquidationERC20PoolHandler.moveQuoteToken(32224, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6849415706965240690489344969517578351041775953402620986, 2148695314889429664161453614417855608352221); + _liquidationERC20PoolHandler.takeAuction(94448273410401913677340426062, 725811011514389123331573619988789182755239580450547667740684, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.kickAuction(741587127707329942048624377800, 636948139808655918956339428997, 1638145676855893651071922500909); + _liquidationERC20PoolHandler.removeCollateral(1, 5658503566441554287849, 301974090866276112427896384335355); + + /* Logs for t0Debt2ToCollateral, dwatp calculation in `drawDebt(AmountToBorrow: 0 ,collateralPledged: 1)` + Time skipped after previous t0Debt2ToCollateral update = 2 hours + colPreAction_ = 0 + colPostAction_ = 1 + debtPreAction_ = 855551635718176874927403250766 + debtPostAction_ = 855551635718176874927403250766 + debt2ColAccumPreAction = 0 + debt2ColAccumPostAction = 731968601380048024642438738674823616825204760499344279586756 + t0Debt2ToCollateral Before _updateT0Debt2ToCollateral = 2387420128063989411532277676228153 + t0Debt2ToCollateral After _updateT0Debt2ToCollateral = 731968601380048024642438741062243744889194172031621955814909 + inflator_ = 1140475256675307106 + t0Debt2ToCollateral_ = 731968601380048024642438741062243744889194172031621955814909 + t0Debt_ = 1783948099616302823918411453377 + */ + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + } + } \ No newline at end of file diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index ecbee74c5..63b9f73dd 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -117,6 +117,17 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } + /* + Test was reverting when kicker and taker are same and kicker is penalized while taker is getting lps. + Fixed by separating kicker and taker condition in 'bucketTake' handler + */ + function test_regression_reserve_15() external { + _reserveERC20PoolHandler.settleAuction(412239579320, 197270, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.bucketTake(2751494394076294863634, 102579647081354295527475262786, false, 20636732314060673687564); + + invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); + } + function test_regression_reserve_16() external { _reserveERC20PoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); _reserveERC20PoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); @@ -282,14 +293,8 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { _reserveERC20PoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0); _reserveERC20PoolHandler.repayDebt(46968019084877, 3); _reserveERC20PoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3); - // _reserveERC20PoolHandler.bucketTake(17377, 2748873005452892812548622619587, false, 999999999999999989712357375741033502535274466); skip(2 hours); _pool.updateInterest(); - /* - TODO: Check why deposit change is more than debt change in accrue interest in "updateInterest" - debt change --> 236352821760996207141053 - deposit change --> 236352821761181451576056 - */ currentTimestamp = block.timestamp; invariant_quoteTokenBalance_QT1(); } @@ -419,4 +424,67 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } + /* + Test was reverting when movedQuoteToken > amountPassed, this happens in case when buckets gets empty. + Fixed by adding check in increaseInReserve calculation in 'moveQuoteToken' handler + */ + function test_regression_evm_revert_1() external { + _reserveERC20PoolHandler.takeAuction(2520000968586068692750846759781727871330432521653849554728884, 842011260485420553676704792240552622539346320776828594643332095169708168242, 8055807); + _reserveERC20PoolHandler.moveQuoteToken(1, 231772084031809103573597600784780758697344200147089918620804085, 2, 664746035165900383390596853874831864040676775434826512224949736661840288899); + _reserveERC20PoolHandler.removeCollateral(1208370290348562883186186, 1, 1); + _reserveERC20PoolHandler.takeAuction(5259923301902342744141092410691697923135981, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); + _reserveERC20PoolHandler.moveQuoteToken(8974296309703512994561526975050221771958436087084360221, 0, 12487898, 0); + } + + /* + FIXME + Test was reverting when redeemedLps = bucketLps but lenderlps < redeemedLps, this happens due to slight rounding error in deposit calculation from lps + Can be fixed by updating lenderLps calculations to `lender.lps -= Maths.min(lender.lps, vars.redeemedLPs)` + */ + function _test_regression_evm_revert_2() external { + _reserveERC20PoolHandler.moveQuoteToken(1, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 158129467307529830729349478455); + _reserveERC20PoolHandler.removeQuoteToken(2999999999999999484865294266928579000517539849, 20462, 1576762402919713971836094859031); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 54458727957125675340786496871311264885201, 3); + _reserveERC20PoolHandler.drawDebt(3, 105); + _reserveERC20PoolHandler.moveQuoteToken(6852481993231188391093194134731580567074964777826972502281, 329445, 0, 0); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 7021699771963113994546981195098591531040470835170729980); + _reserveERC20PoolHandler.kickAuction(222097115856593934764213313250516314101951310591954099083410060419874648, 2249513945015671956, 607419547883141581230100154864); + _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 900686135540961542876969152475416605137543635582763473); + _reserveERC20PoolHandler.kickReserveAuction(0); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); + _reserveERC20PoolHandler.addCollateral(942680573168415959, 1199548961510475789275654540025108262785374840317057516601691579134979, 2999999999999999999695465971169782999351812188); + _reserveERC20PoolHandler.takeReserves(1471, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.kickReserveAuction(32594801409066350134162972068); + _reserveERC20PoolHandler.kickWithDeposit(2999999999999999574088451549152362060347655934, 48090144344287028180951883593); + } + + /* + FIXME + Test was reverting with overflow in `(tu + mau102 - 1e18) ** 2)` calculation in _calculateInterestRate + can be fixed by updating `((tu + mau102 - 1e18) ** 2) / 1e18` to `((tu / 1e9 + mau102 / 1e9 - 1e9) ** 2)` + */ + function _test_regression_evm_revert_3() external { + _reserveERC20PoolHandler.drawDebt(1000011592650618236, 427626464706163901647666438633); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); + _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 222282777921247831223488066461); + _reserveERC20PoolHandler.pullCollateral(15340, 22233328162); + _reserveERC20PoolHandler.bucketTake(43296517, 189401876327916916281126992296, false, 5424); + _reserveERC20PoolHandler.addCollateral(2777407746290631507500, 333697625318607566355528255834, 1000018044967910249); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935); + _reserveERC20PoolHandler.takeReserves(90818049107020612953852548512599042076744953, 1699960882759932026837663149104517717038509322264532007389148); + _reserveERC20PoolHandler.withdrawBonds(106568634815518113798129275, 2460874687943872413578450072374086568386094010814407501); + _reserveERC20PoolHandler.addCollateral(988315461147551209368455480062, 1604895841620880925866279541638, 947957157261063855554739172111710); + _reserveERC20PoolHandler.pullCollateral(1000000000000000000000200, 9090); + _reserveERC20PoolHandler.addQuoteToken(1, 29592092711738557487974299548920457245440815108037318587348855786852817187, 835462594306266473781056); + _reserveERC20PoolHandler.takeAuction(598847416317922228449817163835395963171238146640666936329941450597, 1609410932486951851606214282597, 95354512998795826382552523444); + _reserveERC20PoolHandler.addCollateral(833040298029642855894082982789, 1076477294087, 1797816964051823308646324438005); + _reserveERC20PoolHandler.pledgeCollateral(282614408102076551687763800227, 3); + _reserveERC20PoolHandler.pullCollateral(324581451894516867771201351665741275651124, 4); + _reserveERC20PoolHandler.pledgeCollateral(3990116583418393958345352592252524240487111685089080421377559185333257001186, 63643723788868280847); + _reserveERC20PoolHandler.drawDebt(2420222920583996676329196545, 1); + _reserveERC20PoolHandler.transferLps(2999999999999999866089865785362154170272346882, 1201303985144971, 3000000000000000000069282600099281847535823208, 2516); + _reserveERC20PoolHandler.kickAuction(11585421328272707882885111971840751049901594256465502255118203311426017450, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + } } From b97455f83de54d0e645a2af301d1abc5049a0439 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:13:21 +0300 Subject: [PATCH 64/70] Fix forge docs (#750) * Base contracts forge docs * Pool contracts and interfaces forge docs * Libraries forge doc * If and for consistent style * Rewards and Position manager forge doc * Add docs gitignore * Use index_ instead i_ in Loans library * Consistent LPs to LP naming * Update SettlerActions LPs to LP * Changes after review: fix typo, update comment of index * Add Documentation how to in README file --- README.md | 7 + docs/.gitignore | 1 + src/ERC20Pool.sol | 127 ++++---- src/ERC20PoolFactory.sol | 25 +- src/ERC721Pool.sol | 176 ++++++----- src/ERC721PoolFactory.sol | 43 +-- src/PoolInfoUtils.sol | 154 +++++---- src/PositionManager.sol | 173 +++++----- src/RewardsManager.sol | 143 +++++---- src/base/FlashloanablePool.sol | 20 +- src/base/PermitERC721.sol | 33 +- src/base/Pool.sol | 171 +++++----- src/base/PoolDeployer.sol | 11 +- src/interfaces/pool/IERC3156FlashBorrower.sol | 2 +- src/interfaces/pool/IERC3156FlashLender.sol | 1 + src/interfaces/pool/IPool.sol | 5 +- src/interfaces/pool/IPoolFactory.sol | 4 +- .../pool/commons/IPoolBorrowerActions.sol | 6 +- .../pool/commons/IPoolDerivedState.sol | 11 +- src/interfaces/pool/commons/IPoolErrors.sol | 40 +-- src/interfaces/pool/commons/IPoolEvents.sol | 112 +++---- .../pool/commons/IPoolImmutables.sol | 8 +- .../pool/commons/IPoolInternals.sol | 11 + .../pool/commons/IPoolKickerActions.sol | 26 +- .../pool/commons/IPoolLPActions.sol | 76 ++--- .../pool/commons/IPoolLenderActions.sol | 70 ++-- .../pool/commons/IPoolSettlerActions.sol | 10 +- src/interfaces/pool/commons/IPoolState.sol | 282 ++++++++++------- .../pool/commons/IPoolTakerActions.sol | 42 +-- src/interfaces/pool/erc20/IERC20Pool.sol | 10 +- .../pool/erc20/IERC20PoolBorrowerActions.sol | 13 +- .../pool/erc20/IERC20PoolEvents.sol | 4 +- .../pool/erc20/IERC20PoolFactory.sol | 20 +- .../pool/erc20/IERC20PoolImmutables.sol | 2 +- .../pool/erc20/IERC20PoolLenderActions.sol | 16 +- src/interfaces/pool/erc20/IERC20Taker.sol | 4 +- src/interfaces/pool/erc721/IERC721Pool.sol | 8 +- .../erc721/IERC721PoolBorrowerActions.sol | 15 +- .../pool/erc721/IERC721PoolErrors.sol | 2 +- .../pool/erc721/IERC721PoolEvents.sol | 2 +- .../pool/erc721/IERC721PoolFactory.sol | 26 +- .../pool/erc721/IERC721PoolImmutables.sol | 4 +- .../pool/erc721/IERC721PoolLenderActions.sol | 32 +- .../pool/erc721/IERC721PoolState.sol | 32 +- src/interfaces/pool/erc721/IERC721Taker.sol | 6 +- .../position/IPositionManagerDerivedState.sol | 48 +-- .../position/IPositionManagerErrors.sol | 12 +- .../position/IPositionManagerEvents.sol | 22 +- .../position/IPositionManagerOwnerActions.sol | 42 +-- .../position/IPositionManagerState.sol | 9 +- .../rewards/IRewardsManagerDerivedState.sol | 12 +- .../rewards/IRewardsManagerErrors.sol | 4 +- .../rewards/IRewardsManagerEvents.sol | 34 +- .../rewards/IRewardsManagerOwnerActions.sol | 48 +-- .../rewards/IRewardsManagerState.sol | 46 +-- src/libraries/external/BorrowerActions.sol | 112 ++++--- src/libraries/external/KickerActions.sol | 128 ++++---- src/libraries/external/LPActions.sol | 82 ++--- src/libraries/external/LenderActions.sol | 299 +++++++++--------- src/libraries/external/PoolCommons.sol | 26 +- src/libraries/external/PositionNFTSVG.sol | 2 +- src/libraries/external/SettlerActions.sol | 90 +++--- src/libraries/external/TakerActions.sol | 131 ++++---- src/libraries/helpers/PoolHelper.sol | 143 +++++---- src/libraries/helpers/RevertsHelper.sol | 30 +- src/libraries/internal/Buckets.sol | 80 ++--- src/libraries/internal/Deposits.sol | 54 ++-- src/libraries/internal/Loans.sol | 142 ++++----- .../handlers/BasicERC20PoolHandler.sol | 2 +- .../BasicERC721PoolInvariants.t.sol | 2 +- .../handlers/BasicERC721PoolHandler.sol | 2 +- .../UnboundedBasicERC721PoolHandler.sol | 6 +- .../invariants/base/BasicInvariants.t.sol | 4 +- .../base/handlers/BasicPoolHandler.sol | 2 +- .../base/handlers/LiquidationPoolHandler.sol | 2 +- .../forge/unit/ERC20Pool/ERC20DSTestPlus.sol | 14 +- .../unit/ERC20Pool/ERC20PoolInfoUtils.t.sol | 10 +- .../ERC20PoolLiquidationsScaled.t.sol | 42 +-- .../unit/ERC20Pool/ERC20PoolPrecision.t.sol | 4 +- .../unit/ERC20Pool/ERC20PoolTransferLPs.t.sol | 2 +- .../unit/ERC721Pool/ERC721DSTestPlus.sol | 2 +- tests/forge/unit/PositionManager.t.sol | 14 +- .../forge/unit/Rewards/RewardsDSTestPlus.sol | 8 +- tests/forge/unit/Rewards/RewardsManager.t.sol | 8 +- tests/forge/utils/DSTestPlus.sol | 8 +- 85 files changed, 1960 insertions(+), 1754 deletions(-) create mode 100644 docs/.gitignore diff --git a/README.md b/README.md index 9abab940a..4a1811fdd 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,13 @@ make all ### Implementation notes Pool external calls carry the `nonReentrant` modifier to prevent invocation from `flashLoan` and `take` callbacks. +## Documentation +Documentation can be generated as mdbook from Solidity NatSpecs by using `forge doc` command. +For example, to generate documentation and serve it locally on port 4000 (http://localhost:4000/): +```bash +forge doc --serve --port 4000 +``` + ## Tests ### Forge tests - run tests without the gas load tests (good for checking validity) diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..4e42a1bcd --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index eaad51730..02c397431 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -57,17 +57,17 @@ import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC20 Pool contract - * @notice Entrypoint of ERC20 Pool actions for pool actors: - * - Lenders: add, remove and move quote tokens; transfer LP - * - Borrowers: draw and repay debt - * - Traders: add, remove and move quote tokens; add and remove collateral - * - Kickers: kick undercollateralized loans; settle auctions; claim bond rewards - * - Bidders: take auctioned collateral - * - Reserve purchasers: start auctions; take reserves - * - Flash borrowers: initiate flash loans on quote tokens and collateral - * @dev Contract is FlashloanablePool with flash loan logic. - * @dev Contract is base Pool with logic to handle ERC20 collateral. - * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and auction actions libraries. + * @notice Entrypoint of `ERC20` Pool actions for pool actors: + * - `Lenders`: add, remove and move quote tokens; transfer `LP` + * - `Borrowers`: draw and repay debt + * - `Traders`: add, remove and move quote tokens; add and remove collateral + * - `Kickers`: kick undercollateralized loans; settle auctions; claim bond rewards + * - `Bidders`: take auctioned collateral + * - `Reserve purchasers`: start auctions; take reserves + * - `Flash borrowers`: initiate flash loans on quote tokens and collateral + * @dev Contract is `FlashloanablePool` with flash loan logic. + * @dev Contract is base `Pool` with logic to handle `ERC20` collateral. + * @dev Calls logic from external `PoolCommons`, `LenderActions`, `BorrowerActions` and `Auction` actions libraries. */ contract ERC20Pool is FlashloanablePool, IERC20Pool { using SafeERC20 for IERC20; @@ -76,7 +76,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /*** Constants ***/ /*****************/ - // immutable args offset + /// @dev Immutable collateral scale arg offset. uint256 internal constant COLLATERAL_SCALE = 93; /****************************/ @@ -111,8 +111,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { } /// @inheritdoc IERC20Pool - function bucketCollateralDust(uint256 bucketIndex) external pure override returns (uint256) { - return _bucketCollateralDust(bucketIndex); + function bucketCollateralDust(uint256 bucketIndex_) external pure override returns (uint256) { + return _bucketCollateralDust(bucketIndex_); } /***********************************/ @@ -121,12 +121,12 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IERC20PoolBorrowerActions - * @dev write state: - * - decrement poolBalances.t0DebtInAuction accumulator - * - increment poolBalances.pledgedCollateral accumulator - * - increment poolBalances.t0Debt accumulator - * @dev emit events: - * - DrawDebt + * @dev === Write state === + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - increment `poolBalances.pledgedCollateral` accumulator + * @dev - increment `poolBalances.t0Debt` accumulator + * @dev === Emit events === + * @dev - `DrawDebt` */ function drawDebt( address borrowerAddress_, @@ -189,12 +189,12 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IERC20PoolBorrowerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator - * @dev emit events: - * - RepayDebt + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt accumulator` + * @dev - decrement `poolBalances.t0DebtInAuction accumulator` + * @dev - decrement `poolBalances.pledgedCollateral accumulator` + * @dev === Emit events === + * @dev - `RepayDebt` */ function repayDebt( address borrowerAddress_, @@ -262,16 +262,16 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IERC20PoolLenderActions - * @dev reverts on: - * - DustAmountNotExceeded() - * @dev emit events: - * - AddCollateral + * @dev === Reverts on === + * @dev - `DustAmountNotExceeded()` + * @dev === Emit events === + * @dev - `AddCollateral` */ function addCollateral( uint256 amountToAdd_, uint256 index_, uint256 expiry_ - ) external override nonReentrant returns (uint256 bucketLPs_) { + ) external override nonReentrant returns (uint256 bucketLP_) { _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); @@ -279,14 +279,14 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToAdd_ != 0 && amountToAdd_ < _bucketCollateralDust(index_)) revert DustAmountNotExceeded(); amountToAdd_ = _roundToScale(amountToAdd_, _getArgUint256(COLLATERAL_SCALE)); - bucketLPs_ = LenderActions.addCollateral( + bucketLP_ = LenderActions.addCollateral( buckets, deposits, amountToAdd_, index_ ); - emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLPs_); + emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); @@ -297,13 +297,13 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IPoolLenderActions - * @dev emit events: - * - RemoveCollateral + * @dev === Emit events === + * @dev - `RemoveCollateral` */ function removeCollateral( uint256 maxAmount_, uint256 index_ - ) external override nonReentrant returns (uint256 collateralAmount_, uint256 lpAmount_) { + ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); @@ -311,20 +311,20 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // round the collateral amount appropriately based on token precision maxAmount_ = _roundToScale(maxAmount_, _getArgUint256(COLLATERAL_SCALE)); - (collateralAmount_, lpAmount_) = LenderActions.removeMaxCollateral( + (removedAmount_, redeemedLP_) = LenderActions.removeMaxCollateral( buckets, deposits, maxAmount_, index_ ); - emit RemoveCollateral(msg.sender, index_, collateralAmount_, lpAmount_); + emit RemoveCollateral(msg.sender, index_, removedAmount_, redeemedLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move collateral from pool to lender - _transferCollateral(msg.sender, collateralAmount_); + _transferCollateral(msg.sender, removedAmount_); } /*******************************/ @@ -333,10 +333,10 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IPoolSettlerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function settle( address borrowerAddress_, @@ -380,14 +380,14 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IPoolTakerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function take( address borrowerAddress_, - uint256 collateral_, + uint256 maxAmount_, address callee_, bytes calldata data_ ) external override nonReentrant { @@ -396,7 +396,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { uint256 collateralDust = _bucketCollateralDust(0); // round requested collateral to an amount which can actually be transferred - collateral_ = _roundToScale(collateral_, collateralDust); + maxAmount_ = _roundToScale(maxAmount_, collateralDust); TakeResult memory result = TakerActions.take( auctions, @@ -405,7 +405,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { loans, poolState, borrowerAddress_, - collateral_, + maxAmount_, collateralDust ); // round quote token up to cover the cost of purchasing the collateral @@ -449,10 +449,10 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /** * @inheritdoc IPoolTakerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function bucketTake( address borrowerAddress_, @@ -516,17 +516,32 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /*** Helper Functions ***/ /************************/ + /** + * @notice Helper function to transfer amount of collateral tokens (in collateral token precision) from sender to pool contract. + * @param from_ Sender address. + * @param amount_ Amount to transfer from sender. + */ function _transferCollateralFrom(address from_, uint256 amount_) internal { IERC20(_getArgAddress(COLLATERAL_ADDRESS)).safeTransferFrom(from_, address(this), amount_ / _getArgUint256(COLLATERAL_SCALE)); } + /** + * @notice Helper function to transfer amount of collateral tokens (in collateral token precision) from pool contract. + * @param to_ Receiver address. + * @param amount_ Amount to transfer to receiver. + */ function _transferCollateral(address to_, uint256 amount_) internal { IERC20(_getArgAddress(COLLATERAL_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(COLLATERAL_SCALE)); } - function _bucketCollateralDust(uint256 bucketIndex) internal pure returns (uint256) { + /** + * @notice Helper function to calculate the minimum amount of collateral an actor may have in a bucket. + * @param bucketIndex_ Bucket index. + * @return Amount of collateral dust amount of the bucket. + */ + function _bucketCollateralDust(uint256 bucketIndex_) internal pure returns (uint256) { // price precision adjustment will always be 0 for encumbered collateral - uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex); + uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex_); // difference between the normalized scale and the collateral token's scale return Maths.max(_getArgUint256(COLLATERAL_SCALE), 10 ** pricePrecisionAdjustment); } diff --git a/src/ERC20PoolFactory.sol b/src/ERC20PoolFactory.sol index 6098ea682..cced2aeef 100644 --- a/src/ERC20PoolFactory.sol +++ b/src/ERC20PoolFactory.sol @@ -13,17 +13,18 @@ import { PoolDeployer } from './base/PoolDeployer.sol'; /** * @title ERC20 Pool Factory - * @notice Pool factory contract for creating ERC20 pools. Actors actions: - * - Pool creators: create pool by providing a fungible token for quote and collateral and an interest rate between 1-10% + * @notice Pool factory contract for creating `ERC20` pools. Actors actions: + * - `Pool creators`: create pool by providing a fungible token for quote and collateral and an interest rate between `1%-10%` * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { using ClonesWithImmutableArgs for address; + /// @dev `ERC20` clonable pool contract used to deploy the new pool. ERC20Pool public implementation; - /// @dev Default bytes32 hash used by ERC20 Non-NFTSubset pool types + /// @dev Default `bytes32` hash used by `ERC20` `Non-NFTSubset` pool types bytes32 public constant ERC20_NON_SUBSET_HASH = keccak256("ERC20_NON_SUBSET_HASH"); constructor(address ajna_) { @@ -37,15 +38,15 @@ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { /** * @inheritdoc IERC20PoolFactory * @dev immutable args: pool type; ajna, collateral and quote address; quote and collateral scale - * @dev write state: - * - deployedPools mapping - * - deployedPoolsList array - * @dev reverts on: - * - 0x address provided as quote or collateral DeployWithZeroAddress() - * - pool with provided quote / collateral pair already exists PoolAlreadyExists() - * - invalid interest rate provided PoolInterestRateInvalid() - * @dev emit events: - * - PoolCreated + * @dev === Write state === + * @dev - `deployedPools` mapping + * @dev - `deployedPoolsList` array + * @dev === Reverts on === + * @dev - `0x` address provided as quote or collateral `DeployWithZeroAddress()` + * @dev - pool with provided quote / collateral pair already exists `PoolAlreadyExists()` + * @dev - invalid interest rate provided `PoolInterestRateInvalid()` + * @dev === Emit events === + * @dev - `PoolCreated` */ function deployPool( address collateral_, address quote_, uint256 interestRate_ diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 24432fc2f..1f5b2264d 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -47,17 +47,17 @@ import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC721 Pool contract - * @notice Entrypoint of ERC721 Pool actions for pool actors: - * - Lenders: add, remove and move quote tokens; transfer LP - * - Borrowers: draw and repay debt - * - Traders: add, remove and move quote tokens; add and remove collateral - * - Kickers: auction undercollateralized loans; settle auctions; claim bond rewards - * - Bidders: take auctioned collateral - * - Reserve purchasers: start auctions; take reserves - * - Flash borrowers: initiate flash loans on ERC20 quote tokens - * @dev Contract is FlashloanablePool with flash loan logic. - * @dev Contract is base Pool with logic to handle ERC721 collateral. - * @dev Calls logic from external PoolCommons, LenderActions, BorrowerActions and auction actions libraries. + * @notice Entrypoint of `ERC721` Pool actions for pool actors: + * - `Lenders`: add, remove and move quote tokens; transfer `LP` + * - `Borrowers`: draw and repay debt + * - `Traders`: add, remove and move quote tokens; add and remove collateral + * - `Kickers`: auction undercollateralized loans; settle auctions; claim bond rewards + * - `Bidders`: take auctioned collateral + * - `Reserve purchasers`: start auctions; take reserves + * - `Flash borrowers`: initiate flash loans on ERC20 quote tokens + * @dev Contract is `FlashloanablePool` with flashloan logic. + * @dev Contract is base `Pool` with logic to handle `ERC721` collateral. + * @dev Calls logic from external `PoolCommons`, `LenderActions`, `BorrowerActions` and `Auction` actions libraries. */ contract ERC721Pool is FlashloanablePool, IERC721Pool { @@ -65,16 +65,19 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /*** Constants ***/ /*****************/ - // immutable args offset - uint256 internal constant SUBSET = 93; + /// @dev Immutable NFT subset pool arg offset. + uint256 internal constant SUBSET = 93; /***********************/ /*** State Variables ***/ /***********************/ - mapping(uint256 => bool) public tokenIdsAllowed; // set of tokenIds that can be used for a given NFT Subset type pool - mapping(address => uint256[]) public borrowerTokenIds; // borrower address => array of tokenIds pledged by borrower - uint256[] public bucketTokenIds; // array of tokenIds added in pool buckets + /// @dev Mapping of `tokenIds` allowed in `NFT` Subset type pool. + mapping(uint256 => bool) public tokenIdsAllowed; + /// @dev Borrower `address => array` of tokenIds pledged by borrower mapping. + mapping(address => uint256[]) public borrowerTokenIds; + /// @dev Array of `tokenIds` in pool buckets (claimable from pool). + uint256[] public bucketTokenIds; /****************************/ /*** Initialize Functions ***/ @@ -125,13 +128,13 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IERC721PoolBorrowerActions - * @dev write state: - * - decrement poolBalances.t0DebtInAuction accumulator - * - increment poolBalances.pledgedCollateral accumulator - * - increment poolBalances.t0Debt accumulator - * - update borrowerTokenIds and bucketTokenIds arrays - * @dev emit events: - * - DrawDebtNFT + * @dev === Write state === + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - increment `poolBalances.pledgedCollateral` accumulator + * @dev - increment `poolBalances.t0Debt` accumulator + * @dev - update `borrowerTokenIds` and `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `DrawDebtNFT` */ function drawDebt( address borrowerAddress_, @@ -194,13 +197,13 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IERC721PoolBorrowerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator - * - update borrowerTokenIds and bucketTokenIds arrays - * @dev emit events: - * - RepayDebt + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt accumulator` + * @dev - decrement `poolBalances.t0DebtInAuction accumulator` + * @dev - decrement `poolBalances.pledgedCollateral accumulator` + * @dev - update `borrowerTokenIds` and `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `RepayDebt` */ function repayDebt( address borrowerAddress_, @@ -266,47 +269,47 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IERC721PoolLenderActions - * @dev write state: - * - update bucketTokenIds arrays - * @dev emit events: - * - AddCollateralNFT + * @dev === Write state === + * @dev - update `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `AddCollateralNFT` */ function addCollateral( - uint256[] calldata tokenIdsToAdd_, + uint256[] calldata tokenIds_, uint256 index_, uint256 expiry_ - ) external override nonReentrant returns (uint256 bucketLPs_) { + ) external override nonReentrant returns (uint256 bucketLP_) { _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); - bucketLPs_ = LenderActions.addCollateral( + bucketLP_ = LenderActions.addCollateral( buckets, deposits, - Maths.wad(tokenIdsToAdd_.length), + Maths.wad(tokenIds_.length), index_ ); - emit AddCollateralNFT(msg.sender, index_, tokenIdsToAdd_, bucketLPs_); + emit AddCollateralNFT(msg.sender, index_, tokenIds_, bucketLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move required collateral from sender to pool - _transferFromSenderToPool(bucketTokenIds, tokenIdsToAdd_); + _transferFromSenderToPool(bucketTokenIds, tokenIds_); } /** * @inheritdoc IERC721PoolLenderActions - * @dev write state: - * - update bucketTokenIds arrays - * @dev emit events: - * - MergeOrRemoveCollateralNFT + * @dev === Write state === + * @dev - update `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `MergeOrRemoveCollateralNFT` */ function mergeOrRemoveCollateral( uint256[] calldata removalIndexes_, uint256 noOfNFTsToRemove_, uint256 toIndex_ - ) external override nonReentrant returns (uint256 collateralMerged_, uint256 bucketLPs_) { + ) external override nonReentrant returns (uint256 collateralMerged_, uint256 bucketLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); @@ -314,7 +317,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ( collateralMerged_, - bucketLPs_ + bucketLP_ ) = LenderActions.mergeOrRemoveCollateral( buckets, deposits, @@ -323,7 +326,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { toIndex_ ); - emit MergeOrRemoveCollateralNFT(msg.sender, collateralMerged_, bucketLPs_); + emit MergeOrRemoveCollateralNFT(msg.sender, collateralMerged_, bucketLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); @@ -337,28 +340,29 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IPoolLenderActions - * @dev write state: - * - update bucketTokenIds arrays - * @dev emit events: - * - RemoveCollateral + * @dev === Write state === + * @dev - update `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `RemoveCollateral` + * @param noOfNFTsToRemove_ Number of `NFT` tokens to remove. */ function removeCollateral( uint256 noOfNFTsToRemove_, uint256 index_ - ) external override nonReentrant returns (uint256 collateralAmount_, uint256 lpAmount_) { + ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); - collateralAmount_ = Maths.wad(noOfNFTsToRemove_); - lpAmount_ = LenderActions.removeCollateral( + removedAmount_ = Maths.wad(noOfNFTsToRemove_); + redeemedLP_ = LenderActions.removeCollateral( buckets, deposits, - collateralAmount_, + removedAmount_, index_ ); - emit RemoveCollateral(msg.sender, index_, noOfNFTsToRemove_, lpAmount_); + emit RemoveCollateral(msg.sender, index_, noOfNFTsToRemove_, redeemedLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); @@ -372,10 +376,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IPoolSettlerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function settle( address borrowerAddress_, @@ -424,10 +428,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IPoolTakerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function take( address borrowerAddress_, @@ -502,10 +506,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /** * @inheritdoc IPoolTakerActions - * @dev write state: - * - decrement poolBalances.t0Debt accumulator - * - decrement poolBalances.t0DebtInAuction accumulator - * - decrement poolBalances.pledgedCollateral accumulator + * @dev === Write state === + * @dev - decrement `poolBalances.t0Debt` accumulator + * @dev - decrement `poolBalances.t0DebtInAuction` accumulator + * @dev - decrement `poolBalances.pledgedCollateral` accumulator */ function bucketTake( address borrowerAddress_, @@ -561,11 +565,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /**************************/ /** - * @notice Rebalance NFT token and transfer difference to floor collateral from borrower to pool claimable array - * @dev write state: - * - update borrowerTokens and bucketTokenIds arrays - * @dev emit events: - * - RemoveCollateral + * @notice Rebalance `NFT` token and transfer difference to floor collateral from borrower to pool claimable array. + * @dev === Write state === + * @dev - update `borrowerTokens` and `bucketTokenIds` arrays + * @dev === Emit events === + * @dev - `RemoveCollateral` * @param borrowerAddress_ Address of borrower. * @param borrowerCollateral_ Current borrower collateral to be rebalanced. */ @@ -594,10 +598,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { } /** - * @notice Helper function for transferring multiple NFT tokens from msg.sender to pool. - * @notice Reverts in case token id is not supported by subset pool. - * @param poolTokens_ Array in pool that tracks NFT ids (could be tracking NFTs pledged by borrower or NFTs added by a lender in a specific bucket). - * @param tokenIds_ Array of NFT token ids to transfer from msg.sender to pool. + * @notice Helper function for transferring multiple `NFT` tokens from msg.sender to pool. + * @dev Reverts in case token id is not supported by subset pool. + * @param poolTokens_ Array in pool that tracks `NFT` ids (could be tracking `NFT`s pledged by borrower or `NFT`s added by a lender in a specific bucket). + * @param tokenIds_ Array of `NFT` token ids to transfer from `msg.sender` to pool. */ function _transferFromSenderToPool( uint256[] storage poolTokens_, @@ -617,11 +621,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { } /** - * @notice Helper function for transferring multiple NFT tokens from pool to given address. - * @notice It transfers NFTs from the most recent one added into the pool (pop from array tracking NFTs in pool). + * @notice Helper function for transferring multiple `NFT` tokens from pool to given address. + * @dev It transfers `NFT`s from the most recent one added into the pool (pop from array tracking `NFT`s in pool). * @param toAddress_ Address where pool should transfer tokens to. - * @param poolTokens_ Array in pool that tracks NFT ids (could be tracking NFTs pledged by borrower or NFTs added by a lender in a specific bucket). - * @param amountToRemove_ Number of NFT tokens to transfer from pool to given address. + * @param poolTokens_ Array in pool that tracks `NFT` ids (could be tracking `NFT`s pledged by borrower or `NFT`s added by a lender in a specific bucket). + * @param amountToRemove_ Number of `NFT` tokens to transfer from pool to given address. * @return Array containing token ids that were transferred from pool to address. */ function _transferFromPoolToAddress( @@ -648,11 +652,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { } /** - * @dev Helper function to transfer an NFT from owner to target address (reused in code to reduce contract deployment bytecode size). - * @dev Since transferFrom is used instead of safeTransferFrom, calling smart contracts must be careful to check that they support any received NFTs. - * @param from_ NFT owner address. - * @param to_ New NFT owner address. - * @param tokenId_ NFT token id to be transferred. + * @notice Helper function to transfer an `NFT` from owner to target address (reused in code to reduce contract deployment bytecode size). + * @dev Since `transferFrom` is used instead of `safeTransferFrom`, calling smart contracts must be careful to check that they support any received `NFT`s. + * @param from_ `NFT` owner address. + * @param to_ New `NFT` owner address. + * @param tokenId_ `NFT` token id to be transferred. */ function _transferNFT(address from_, address to_, uint256 tokenId_) internal { // slither-disable-next-line calls-loop diff --git a/src/ERC721PoolFactory.sol b/src/ERC721PoolFactory.sol index ef1da0440..645d1490f 100644 --- a/src/ERC721PoolFactory.sol +++ b/src/ERC721PoolFactory.sol @@ -14,17 +14,19 @@ import { PoolDeployer } from './base/PoolDeployer.sol'; /** * @title ERC721 Pool Factory - * @notice Pool factory contract for creating ERC721 pools. If a list with token ids is provided then a subset ERC721 pool is created for the NFT. - * @notice Pool creators can: create pool by providing a fungible token for quote, a non fungible token for collateral and an interest rate between 1-10% + * @notice Pool factory contract for creating `ERC721` pools. If a list with token ids is provided then a subset `ERC721` pool is created for the `NFT`. + * @notice Pool factory contract for creating `ERC20` pools. If a list with token ids is provided then a subset `ERC721` pool is created for the `NFT`. Actors actions: + * - `Pool creators`: create pool by providing a fungible token for quote, a non fungible token for collateral and an interest rate between `1%-10%`. * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { using ClonesWithImmutableArgs for address; + /// @dev `ERC721` clonable pool contract used to deploy the new pool. ERC721Pool public implementation; - /// @dev Default bytes32 hash used by ERC721 Non-NFTSubset pool types + /// @dev Default `bytes32` hash used by `ERC721` `Non-NFTSubset` pool types bytes32 public constant ERC721_NON_SUBSET_HASH = keccak256("ERC721_NON_SUBSET_HASH"); constructor(address ajna_) { @@ -37,18 +39,17 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { /** * @inheritdoc IERC721PoolFactory - * @dev immutable args: - * - pool type; ajna, collateral and quote address; quote scale; number of token ids in subset; NFT type - * @dev write state: - * - deployedPools mapping - * - deployedPoolsList array - * @dev reverts on: - * - 0x address provided as quote or collateral DeployWithZeroAddress() - * - pool with provided quote / collateral pair already exists PoolAlreadyExists() - * - invalid interest rate provided PoolInterestRateInvalid() - * - not supported NFT provided NFTNotSupported() - * @dev emit events: - * - PoolCreated + * @dev immutable args: pool type; ajna, collateral and quote address; quote scale; number of token ids in subset + * @dev === Write state === + * @dev - `deployedPools` mapping + * @dev - `deployedPoolsList` array + * @dev === Reverts on === + * @dev - `0x` address provided as quote or collateral `DeployWithZeroAddress()` + * @dev - pool with provided quote / collateral pair already exists `PoolAlreadyExists()` + * @dev - invalid interest rate provided `PoolInterestRateInvalid()` + * @dev - not supported `NFT` provided `NFTNotSupported()` + * @dev === Emit events === + * @dev - `PoolCreated` */ function deployPool( address collateral_, address quote_, uint256[] memory tokenIds_, uint256 interestRate_ @@ -91,10 +92,10 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { /*******************************/ /** - * @notice Get the hash of the subset of NFTs that will be used to create the pool - * @dev If no tokenIds are provided, the default ERC721_NON_SUBSET_HASH is returned - * @param tokenIds_ The array of token ids that will be used to create the pool - * @return bytes32 The hash of the subset of NFTs that will be used to create the pool + * @notice Get the hash of the subset of `NFT`s that will be used to create the pool. + * @dev If no `tokenIds` are provided, the default `ERC721_NON_SUBSET_HASH` is returned. + * @param tokenIds_ The array of token ids that will be used to create the pool. + * @return The hash of the subset of `NFT`s that will be used to create the pool. */ function getNFTSubsetHash(uint256[] memory tokenIds_) public pure returns (bytes32) { if (tokenIds_.length == 0) return ERC721_NON_SUBSET_HASH; @@ -110,8 +111,8 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { /** * @notice Check that the array of token ids is sorted in ascending order, else revert. - * @dev The counters are modified in unchecked blocks due to being bounded by array length - * @param tokenIds_ The array of token ids to check for sorting + * @dev The counters are modified in unchecked blocks due to being bounded by array length. + * @param tokenIds_ The array of token ids to check for sorting. */ function _checkTokenIdSortOrder(uint256[] memory tokenIds_) internal pure { for (uint256 i = 0; i < tokenIds_.length - 1; ) { diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index 96f0f80ec..a87dbbb42 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -27,20 +27,21 @@ import { PoolCommons } from './libraries/external/PoolCommons.sol'; /** * @title Pool Info Utils contract - * @notice Contract for providing pools information for any deployed pool. - * @dev Pool info is calculated using same helper functions / logic as in Pool contracts. + * @notice Contract for providing information for any deployed pool. + * @dev Pool info is calculated using same helper functions / logic as in `Pool` contracts. */ contract PoolInfoUtils { /** - * @notice Exposes status of a liquidation auction - * @param borrower_ Identifies the loan being liquidated - * @return kickTime_ Time auction was kicked, implying end time - * @return collateral_ Remaining collateral available to be purchased (WAD) - * @return debtToCover_ Borrower debt to be covered (WAD) - * @return isCollateralized_ If true, takes will revert - * @return price_ Current price of the auction (WAD) - * @return neutralPrice_ Price at which bond holder is neither rewarded nor penalized (WAD) + * @notice Exposes status of a liquidation auction. + * @param ajnaPool_ Address of `Ajna` pool. + * @param borrower_ Identifies the loan being liquidated. + * @return kickTime_ Time auction was kicked, implying end time. + * @return collateral_ Remaining collateral available to be purchased. (`WAD`) + * @return debtToCover_ Borrower debt to be covered. (`WAD`) + * @return isCollateralized_ `True` if loan is collateralized. + * @return price_ Current price of the auction. (`WAD`) + * @return neutralPrice_ Price at which bond holder is neither rewarded nor penalized. (`WAD`) */ function auctionStatus(address ajnaPool_, address borrower_) external @@ -68,13 +69,21 @@ contract PoolInfoUtils { } } + /** + * @notice Retrieves info of a given borrower in a given `Ajna` pool. + * @param ajnaPool_ Address of `Ajna` pool. + * @param borrower_ Borrower's address. + * @return debt_ Current debt owed by borrower (`WAD`). + * @return collateral_ Pledged collateral, including encumbered (`WAD`). + * @return t0Np_ `Neutral price` (`WAD`). + */ function borrowerInfo(address ajnaPool_, address borrower_) external view returns ( - uint256 debt_, // current debt owed by borrower (WAD) - uint256 collateral_, // deposited collateral including encumbered (WAD) - uint256 t0Np_ // Np / inflator, used in neutralPrice calc (WAD) + uint256 debt_, + uint256 collateral_, + uint256 t0Np_ ) { IPool pool = IPool(ajnaPool_); @@ -96,13 +105,14 @@ contract PoolInfoUtils { /** * @notice Get a bucket struct for a given index. + * @param ajnaPool_ Address of `Ajna` pool. * @param index_ The index of the bucket to retrieve. - * @return price_ Bucket price (WAD) - * @return quoteTokens_ Amount of quote token in bucket, deposit + interest (WAD) - * @return collateral_ Unencumbered collateral in bucket (WAD). - * @return bucketLPs_ Outstanding LP balance in bucket (WAD) - * @return scale_ Lender interest multiplier (WAD). - * @return exchangeRate_ The exchange rate of the bucket, in WAD units. + * @return price_ Bucket's price (`WAD`). + * @return quoteTokens_ Amount of quote token in bucket, `deposit + interest` (`WAD`). + * @return collateral_ Unencumbered collateral in bucket (`WAD`). + * @return bucketLP_ Outstanding `LP` balance in bucket (`WAD`). + * @return scale_ Lender interest multiplier (`WAD`). + * @return exchangeRate_ The exchange rate of the bucket, in `WAD` units. */ function bucketInfo(address ajnaPool_, uint256 index_) external @@ -111,7 +121,7 @@ contract PoolInfoUtils { uint256 price_, uint256 quoteTokens_, uint256 collateral_, - uint256 bucketLPs_, + uint256 bucketLP_, uint256 scale_, uint256 exchangeRate_ ) @@ -120,15 +130,16 @@ contract PoolInfoUtils { price_ = _priceAt(index_); - (bucketLPs_, collateral_, , quoteTokens_, scale_) = pool.bucketInfo(index_); - exchangeRate_ = Buckets.getExchangeRate(collateral_, bucketLPs_, quoteTokens_, price_); + (bucketLP_, collateral_, , quoteTokens_, scale_) = pool.bucketInfo(index_); + exchangeRate_ = Buckets.getExchangeRate(collateral_, bucketLP_, quoteTokens_, price_); } /** * @notice Returns info related to pool loans. - * @return poolSize_ The total amount of quote tokens in pool (WAD). + * @param ajnaPool_ Address of `Ajna` pool. + * @return poolSize_ The total amount of quote tokens in pool (`WAD`). * @return loansCount_ The number of loans in pool. - * @return maxBorrower_ The address with the highest TP in pool. + * @return maxBorrower_ The address with the highest `TP` in pool. * @return pendingInflator_ Pending inflator in pool. * @return pendingInterestFactor_ Factor used to scale the inflator. */ @@ -161,12 +172,13 @@ contract PoolInfoUtils { /** * @notice Returns info related to pool prices. - * @return hpb_ The price value of the current Highest Price Bucket (HPB), in WAD units. - * @return hpbIndex_ The index of the current Highest Price Bucket (HPB), in WAD units. - * @return htp_ The price value of the current Highest Threshold Price (HTP) bucket, in WAD units. - * @return htpIndex_ The index of the current Highest Threshold Price (HTP) bucket, in WAD units. - * @return lup_ The price value of the current Lowest Utilized Price (LUP) bucket, in WAD units. - * @return lupIndex_ The index of the current Lowest Utilized Price (LUP) bucket, in WAD units. + * @param ajnaPool_ Address of `Ajna` pool. + * @return hpb_ The price value of the current `Highest Price Bucket` (`HPB`), in `WAD` units. + * @return hpbIndex_ The index of the current `Highest Price Bucket` (`HPB`), in `WAD` units. + * @return htp_ The price value of the current `Highest Threshold Price` (`HTP`) bucket, in `WAD` units. + * @return htpIndex_ The index of the current `Highest Threshold Price` (`HTP`) bucket, in `WAD` units. + * @return lup_ The price value of the current `Lowest Utilized Price` (LUP) bucket, in `WAD` units. + * @return lupIndex_ The index of the current `Lowest Utilized Price` (`LUP`) bucket, in `WAD` units. */ function poolPricesInfo(address ajnaPool_) external @@ -196,11 +208,12 @@ contract PoolInfoUtils { } /** - * @notice Returns info related to Claimaible Reserve Auction. + * @notice Returns info related to `Claimaible Reserve Auction`. + * @param ajnaPool_ Address of `Ajna` pool. * @return reserves_ The amount of excess quote tokens. - * @return claimableReserves_ Denominated in quote token, or 0 if no reserves can be auctioned. + * @return claimableReserves_ Denominated in quote token, or `0` if no reserves can be auctioned. * @return claimableReservesRemaining_ Amount of claimable reserves which has not yet been taken. - * @return auctionPrice_ Current price at which 1 quote token may be purchased, denominated in Ajna. + * @return auctionPrice_ Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @return timeRemaining_ Seconds remaining before takes are no longer allowed. */ function poolReservesInfo(address ajnaPool_) @@ -224,7 +237,7 @@ contract PoolInfoUtils { (uint256 bondEscrowed, uint256 unclaimedReserve, uint256 auctionKickTime, ) = pool.reservesInfo(); // due to rounding issues, especially in Auction.settle, this can be slighly negative - if( poolDebt + quoteTokenBalance >= poolSize + bondEscrowed + unclaimedReserve) { + if ( poolDebt + quoteTokenBalance >= poolSize + bondEscrowed + unclaimedReserve) { reserves_ = poolDebt + quoteTokenBalance - poolSize - bondEscrowed - unclaimedReserve; } @@ -242,11 +255,12 @@ contract PoolInfoUtils { } /** - * @notice Returns info related to Claimaible Reserve Auction. + * @notice Returns info related to pool utilization. + * @param ajnaPool_ Address of `Ajna` pool. * @return poolMinDebtAmount_ Minimum debt amount. * @return poolCollateralization_ Current pool collateralization ratio. - * @return poolActualUtilization_ The current pool actual utilization, in WAD units. - * @return poolTargetUtilization_ The current pool Target utilization, in WAD units. + * @return poolActualUtilization_ The current pool actual utilization, in `WAD` units. + * @return poolTargetUtilization_ The current pool Target utilization, in `WAD` units. */ function poolUtilizationInfo(address ajnaPool_) external @@ -278,6 +292,8 @@ contract PoolInfoUtils { /** * @notice Returns the proportion of interest rate which is awarded to lenders; * the remainder accumulates in reserves. + * @param ajnaPool_ Address of `Ajna` pool. + * @return lenderInterestMargin_ Lender interest margin in pool. */ function lenderInterestMargin(address ajnaPool_) external @@ -291,6 +307,9 @@ contract PoolInfoUtils { lenderInterestMargin_ = PoolCommons.lenderInterestMargin(utilization); } + /** + * @notice Returns bucket price for a given bucket index. + */ function indexToPrice( uint256 index_ ) external pure returns (uint256) @@ -298,6 +317,9 @@ contract PoolInfoUtils { return _priceAt(index_); } + /** + * @notice Returns bucket index for a given bucket price. + */ function priceToIndex( uint256 price_ ) external pure returns (uint256) @@ -305,6 +327,9 @@ contract PoolInfoUtils { return _indexOf(price_); } + /** + * @notice Returns current `LUP` for a given pool. + */ function lup( address ajnaPool_ ) external view returns (uint256) { @@ -316,6 +341,9 @@ contract PoolInfoUtils { return _priceAt(currentLupIndex); } + /** + * @notice Returns current `LUP` index for a given pool. + */ function lupIndex( address ajnaPool_ ) external view returns (uint256) { @@ -326,6 +354,9 @@ contract PoolInfoUtils { return pool.depositIndex(debt); } + /** + * @notice Returns current `HPB` for a given pool. + */ function hpb( address ajnaPool_ ) external view returns (uint256) { @@ -336,6 +367,9 @@ contract PoolInfoUtils { return _priceAt(hbpIndex); } + /** + * @notice Returns current `HPB` index for a given pool. + */ function hpbIndex( address ajnaPool_ ) external view returns (uint256) { @@ -344,12 +378,18 @@ contract PoolInfoUtils { return pool.depositIndex(1); } + /** + * @notice Returns current `HTP` for a given pool. + */ function htp( address ajnaPool_ ) external view returns (uint256 htp_) { (, htp_, ) = IPool(ajnaPool_).loansInfo(); } + /** + * @notice Returns current `MOMP` for a given pool. + */ function momp( address ajnaPool_ ) external view returns (uint256) { @@ -369,7 +409,7 @@ contract PoolInfoUtils { /** * @notice Calculates origination fee rate for a pool. - * @notice Calculated as greater of the current annualized interest rate divided by 52 (one week of interest) or 5 bps. + * @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps. * @return Fee rate calculated from the pool interest rate. */ function borrowFeeRate( @@ -381,7 +421,7 @@ contract PoolInfoUtils { /** * @notice Calculates unutilized deposit fee rate for a pool. - * @notice Calculated as current annualized rate divided by 365 (24 hours of interest). + * @notice Calculated as current annualized rate divided by `365` (`24` hours of interest). * @return Fee rate calculated from the pool interest rate. */ function unutilizedDepositFeeRate( @@ -392,46 +432,46 @@ contract PoolInfoUtils { } /** - * @notice Calculate the amount of quote tokens in bucket for a given amount of LP. - * @param lps_ The number of LP to calculate amounts for. + * @notice Calculate the amount of quote tokens in bucket for a given amount of `LP`. + * @param lp_ The number of `LP` to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. - * @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given LP, WAD units. + * @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given `LP`, `WAD` units. */ function lpToQuoteTokens( address ajnaPool_, - uint256 lps_, + uint256 lp_, uint256 index_ ) external view returns (uint256 quoteAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLPs_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); quoteAmount_ = _lpToQuoteToken( - bucketLPs_, + bucketLP_, bucketCollateral, bucketDeposit, - lps_, + lp_, bucketDeposit, _priceAt(index_) ); } /** - * @notice Calculate the amount of collateral tokens in bucket for a given amount of LP. - * @param lps_ The number of LP to calculate amounts for. + * @notice Calculate the amount of collateral tokens in bucket for a given amount of `LP`. + * @param lp_ The number of `LP` to calculate amounts for. * @param index_ The price bucket index for which the value should be calculated. - * @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given LP, WAD units. + * @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given `LP`, `WAD` units. */ function lpToCollateral( address ajnaPool_, - uint256 lps_, + uint256 lp_, uint256 index_ ) external view returns (uint256 collateralAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLPs_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); collateralAmount_ = _lpToCollateral( bucketCollateral, - bucketLPs_, + bucketLP_, bucketDeposit, - lps_, + lp_, _priceAt(index_) ); } @@ -459,7 +499,7 @@ contract PoolInfoUtils { * @param debt_ The debt amount. * @param collateral_ The collateral amount. * @param price_ The price to calculate collateralization at. - * @return Collateralization value. 1**18 if debt amount is 0. + * @return Collateralization value. `1 WAD` if debt amount is `0`. */ function _collateralization( uint256 debt_, @@ -471,9 +511,9 @@ contract PoolInfoUtils { } /** - * @notice Calculates target utilization for given EMA values. - * @param debtColEma_ The EMA of debt squared to collateral. - * @param lupt0DebtEma_ The EMA of LUP * t0 debt. + * @notice Calculates target utilization for given `EMA` values. + * @param debtColEma_ The `EMA` of debt squared to collateral. + * @param lupt0DebtEma_ The `EMA` of `LUP * t0 debt`. * @return Target utilization of the pool. */ function _targetUtilization( diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 19f07702f..261fbc146 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -31,13 +31,13 @@ import { PositionNFTSVG } from './libraries/external/PositionNFTSVG.sol'; /** * @title Position Manager Contract - * @notice Used by Pool lenders to optionally mint NFT that represents their positions. - * Lenders can: - * - mint positions NFT token for a specific pool - * - memorialize positions for given buckets - * - move liquidity in pool - * - redeem positions for given buckets - * - burn positions NFT + * @notice Used by Pool lenders to optionally mint `NFT` that represents their positions. + * `Lenders` can: + * - `mint` positions `NFT` token for a specific pool + * - `memorialize` positions for given buckets + * - `move liquidity` in pool + * - `redeem` positions for given buckets + * - `burn` positions `NFT` */ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, ReentrancyGuard { @@ -48,27 +48,35 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /*** State Variables ***/ /***********************/ - mapping(uint256 => address) public override poolKey; // token id => ajna pool address for which token was minted + /// @dev Mapping of `token id => ajna pool address` for which token was minted. + mapping(uint256 => address) public override poolKey; - mapping(uint256 => mapping(uint256 => Position)) internal positions; // token id => bucket index => Position struct - mapping(uint256 => uint96) internal nonces; // token id => nonce value used for permit - mapping(uint256 => EnumerableSet.UintSet) internal positionIndexes; // token id => bucket indexes associated with position + /// @dev Mapping of `token id => ajna pool address` for which token was minted. + mapping(uint256 => mapping(uint256 => Position)) internal positions; + /// @dev Mapping of `token id => nonce` value used for permit. + mapping(uint256 => uint96) internal nonces; + /// @dev Mapping of `token id => bucket indexes` associated with position. + mapping(uint256 => EnumerableSet.UintSet) internal positionIndexes; - uint176 private _nextId = 1; // id of the next token that will be minted. Skips 0 + /// @dev Id of the next token that will be minted. Skips `0`. + uint176 private _nextId = 1; /******************/ /*** Immutables ***/ /******************/ - ERC20PoolFactory private immutable erc20PoolFactory; // The ERC20 pools factory contract, used to check if address is an Ajna pool - ERC721PoolFactory private immutable erc721PoolFactory; // The ERC721 pools factory contract, used to check if address is an Ajna pool + /// @dev The `ERC20` pools factory contract, used to check if address is an `Ajna` pool. + ERC20PoolFactory private immutable erc20PoolFactory; + /// @dev The `ERC721` pools factory contract, used to check if address is an `Ajna` pool. + ERC721PoolFactory private immutable erc721PoolFactory; /*************************/ /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `moveLiquidity` function local vars. struct MoveLiquidityLocalVars { - uint256 bucketLPs; // [WAD] amount of LP in from bucket + uint256 bucketLP; // [WAD] amount of LP in from bucket uint256 bucketCollateral; // [WAD] amount of collateral in from bucket uint256 bankruptcyTime; // from bucket bankruptcy time uint256 bucketDeposit; // [WAD] from bucket deposit @@ -82,6 +90,11 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /*** Modifiers ***/ /*****************/ + /** + * @dev Modifier used to check if sender can interact with token id. + * @param pool_ `Ajna` pool address. + * @param tokenId_ Id of positions `NFT`. + */ modifier mayInteract(address pool_, uint256 tokenId_) { // revert if token id is not a valid / minted id @@ -114,17 +127,17 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @inheritdoc IPositionManagerOwnerActions - * @dev write state: - * - nonces: remove tokenId nonce - * - poolKey: remove tokenId => pool mapping - * @dev revert on: - * - mayInteract: - * - token id is not a valid / minted id - * - sender is not owner NoAuth() - * - token id not minted for given pool WrongPool() - * - positions token to burn has liquidity LiquidityNotRemoved() - * @dev emit events: - * - Burn + * @dev === Write state === + * @dev `nonces`: remove `tokenId` nonce + * @dev `poolKey`: remove `tokenId => pool` mapping + * @dev === Revert on === + * @dev - `mayInteract`: + * @dev token id is not a valid / minted id + * @dev sender is not owner `NoAuth()` + * @dev token id not minted for given pool `WrongPool()` + * @dev - positions token to burn has liquidity `LiquidityNotRemoved()` + * @dev === Emit events === + * @dev - `Burn` */ function burn( BurnParams calldata params_ @@ -143,16 +156,16 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @inheritdoc IPositionManagerOwnerActions - * @dev External calls to Pool contract: - * - lenderInfo(): get lender position in bucket - * - transferLP(): transfer LP ownership to PositionManager contracts - * @dev write state: - * - positionIndexes: add bucket index - * - positions: update tokenId => bucket id position - * @dev revert on: - * - positions token to burn has liquidity LiquidityNotRemoved() - * @dev emit events: - * - MemorializePosition + * @dev External calls to `Pool` contract: + * @dev - `lenderInfo()`: get lender position in bucket + * @dev - `transferLP()`: transfer `LP` ownership to `PositionManager` contract + * @dev === Write state === + * @dev `positionIndexes`: add bucket index + * @dev `positions`: update `tokenId => bucket id` position + * @dev === Revert on === + * @dev positions token to burn has liquidity `LiquidityNotRemoved()` + * @dev === Emit events === + * @dev - `MemorializePosition` */ function memorializePositions( MemorializePositionsParams calldata params_ @@ -204,12 +217,12 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @inheritdoc IPositionManagerOwnerActions - * @dev write state: - * - poolKey: update tokenId => pool mapping - * @dev revert on: - * - provided pool not valid NotAjnaPool() - * @dev emit events: - * - Mint + * @dev === Write state === + * @dev `poolKey`: update `tokenId => pool` mapping + * @dev === Revert on === + * @dev provided pool not valid `NotAjnaPool()` + * @dev === Emit events === + * @dev - `Mint` */ function mint( MintParams calldata params_ @@ -229,22 +242,22 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @inheritdoc IPositionManagerOwnerActions - * @dev External calls to Pool contract: - * - bucketInfo(): get from bucket info - * - moveQuoteToken(): move liquidity between buckets - * @dev write state: - * - positionIndexes: remove from bucket index - * - positionIndexes: add to bucket index - * - positions: update from bucket position - * - positions: update to bucket position - * @dev revert on: - * - mayInteract: - * - token id is not a valid / minted id - * - sender is not owner NoAuth() - * - token id not minted for given pool WrongPool() - * - positions token to burn has liquidity LiquidityNotRemoved() - * @dev emit events: - * - MoveLiquidity + * @dev External calls to `Pool` contract: + * @dev `bucketInfo()`: get from bucket info + * @dev `moveQuoteToken()`: move liquidity between buckets + * @dev === Write state === + * @dev `positionIndexes`: remove from bucket index + * @dev `positionIndexes`: add to bucket index + * @dev `positions`: update from bucket position + * @dev `positions`: update to bucket position + * @dev === Revert on === + * @dev - `mayInteract`: + * @dev token id is not a valid / minted id + * @dev sender is not owner `NoAuth()` + * @dev token id not minted for given pool `WrongPool()` + * @dev - positions token to burn has liquidity `LiquidityNotRemoved()` + * @dev === Emit events === + * @dev - `MoveLiquidity` */ function moveLiquidity( MoveLiquidityParams calldata params_ @@ -262,7 +275,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R // retrieve info of bucket from which liquidity is moved ( - vars.bucketLPs, + vars.bucketLP, vars.bucketCollateral, vars.bankruptcyTime, vars.bucketDeposit, @@ -273,7 +286,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R // calculate the max amount of quote tokens that can be moved, given the tracked LP vars.maxQuote = _lpToQuoteToken( - vars.bucketLPs, + vars.bucketLP, vars.bucketCollateral, vars.bucketDeposit, fromPosition.lps, @@ -321,20 +334,20 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @inheritdoc IPositionManagerOwnerActions - * @dev External calls to Pool contract: - * - increaseLPAllowance(): approve ownership for transfer - * - transferLP(): transfer LP ownership from PositionManager contract - * @dev write state: - * - positionIndexes: remove from bucket index - * - positions: delete bucket position - * @dev revert on: - * - mayInteract: - * - token id is not a valid / minted id - * - sender is not owner NoAuth() - * - token id not minted for given pool WrongPool() - * - position not tracked RemoveLiquidityFailed() - * @dev emit events: - * - RedeemPosition + * @dev External calls to `Pool` contract: + * @dev `increaseLPAllowance()`: approve ownership for transfer + * @dev `transferLP()`: transfer `LP` ownership from `PositionManager` contract + * @dev === Write state === + * @dev `positionIndexes`: remove from bucket index + * @dev `positions`: delete bucket position + * @dev === Revert on === + * @dev - `mayInteract`: + * @dev token id is not a valid / minted id + * @dev sender is not owner `NoAuth()` + * @dev token id not minted for given pool `WrongPool()` + * @dev - position not tracked `RemoveLiquidityFailed()` + * @dev === Emit events === + * @dev - `RedeemPosition` */ function reedemPositions( RedeemPositionsParams calldata params_ @@ -385,7 +398,7 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R /** * @notice Retrieves token's next nonce for permit. - * @param tokenId_ Address of the Ajna pool to retrieve accumulators of. + * @param tokenId_ Address of the `Ajna` pool to retrieve accumulators of. * @return Incremented token permit nonce. */ function _getAndIncrementNonce( @@ -395,10 +408,10 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R } /** - * @notice Checks that a provided pool address was deployed by an Ajna factory. - * @param pool_ Address of the Ajna pool. + * @notice Checks that a provided pool address was deployed by an `Ajna` factory. + * @param pool_ Address of the `Ajna` pool. * @param subsetHash_ Factory's subset hash pool. - * @return True if a valid Ajna pool false otherwise. + * @return `True` if a valid `Ajna` pool, `false` otherwise. */ function _isAjnaPool( address pool_, @@ -414,11 +427,11 @@ contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, R } /** - * @notice Checks that a bucket index associated with a given NFT didn't go bankrupt after memorialization. + * @notice Checks that a bucket index associated with a given `NFT` didn't go bankrupt after memorialization. * @param pool_ The address of the pool of memorialized position. * @param index_ The bucket index to check deposit time for. * @param depositTime_ The recorded deposit time of the position. - * @return True if the bucket went bankrupt after that position memorialzied their lpb. + * @return `True` if the bucket went bankrupt after that position memorialzied their `LP`. */ function _bucketBankruptAfterDeposit( IPool pool_, diff --git a/src/RewardsManager.sol b/src/RewardsManager.sol index 28c4b55ec..314b476bf 100644 --- a/src/RewardsManager.sol +++ b/src/RewardsManager.sol @@ -24,13 +24,13 @@ import { Maths } from './libraries/internal/Maths.sol'; /** * @title Rewards (staking) Manager contract - * @notice Pool lenders can optionally mint NFT that represents their positions. - * The Rewards contract allows pool lenders with positions NFT to stake and earn AJNA tokens. - * Lenders with NFTs can: - * - stake token - * - update bucket exchange rate and earn rewards - * - claim rewards - * - unstake token + * @notice Pool lenders can optionally mint `NFT` that represents their positions. + * The Rewards contract allows pool lenders with positions `NFT` to stake and earn `Ajna` tokens. + * Lenders with `NFT`s can: + * - `stake` token + * - `update bucket exchange rate` and earn rewards + * - `claim` rewards + * - `unstake` token */ contract RewardsManager is IRewardsManager, ReentrancyGuard { @@ -41,16 +41,16 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /*****************/ /** - * @notice Maximum percentage of tokens burned that can be claimed as Ajna token lp nft rewards. + * @notice Maximum percentage of tokens burned that can be claimed as `Ajna` token `LP` `NFT` rewards. */ uint256 internal constant REWARD_CAP = 0.8 * 1e18; /** - * @notice Maximum percentage of tokens burned that can be claimed as Ajna token update rewards. + * @notice Maximum percentage of tokens burned that can be claimed as `Ajna` token update rewards. */ uint256 internal constant UPDATE_CAP = 0.1 * 1e18; /** * @notice Reward factor by which to scale the total rewards earned. - * @dev ensures that rewards issued to staked lenders in a given pool are less than the ajna tokens burned in that pool. + * @dev ensures that rewards issued to staked lenders in a given pool are less than the `Ajna` tokens burned in that pool. */ uint256 internal constant REWARD_FACTOR = 0.5 * 1e18; /** @@ -66,21 +66,27 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /*** State Variables ***/ /***********************/ - mapping(uint256 => mapping(uint256 => bool)) public override isEpochClaimed; // tokenID => epoch => bool has claimed - mapping(uint256 => uint256) public override rewardsClaimed; // epoch => tokens claimed - mapping(uint256 => uint256) public override updateRewardsClaimed; // epoch => tokens claimed + /// @dev `tokenID => epoch => bool has claimed` mapping. + mapping(uint256 => mapping(uint256 => bool)) public override isEpochClaimed; + /// @dev `epoch => rewards claimed` mapping. + mapping(uint256 => uint256) public override rewardsClaimed; + /// @dev `epoch => update bucket rate rewards claimed` mapping. + mapping(uint256 => uint256) public override updateRewardsClaimed; - // Mapping of per pool bucket exchange rates at a given burn event. - mapping(address => mapping(uint256 => mapping(uint256 => uint256))) internal bucketExchangeRates; // poolAddress => bucketIndex => epoch => bucket exchange rate + /// @dev Mapping of per pool bucket exchange rates at a given burn event `poolAddress => bucketIndex => epoch => bucket exchange rate`. + mapping(address => mapping(uint256 => mapping(uint256 => uint256))) internal bucketExchangeRates; - mapping(uint256 => StakeInfo) internal stakes; // tokenID => Stake info + /// @dev Mapping `tokenID => Stake info`. + mapping(uint256 => StakeInfo) internal stakes; /******************/ /*** Immutables ***/ /******************/ - address public immutable ajnaToken; // address of the AJNA token - IPositionManager public immutable positionManager; // The PositionManager contract + /// @dev Address of the `Ajna` token. + address public immutable ajnaToken; + /// @dev The `PositionManager` contract + IPositionManager public immutable positionManager; /*******************/ /*** Constructor ***/ @@ -99,11 +105,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @inheritdoc IRewardsManagerOwnerActions - * @dev revert on: - * - not owner NotOwnerOfDeposit() - * - already claimed AlreadyClaimed() - * @dev emit events: - * - ClaimRewards + * @dev === Revert on === + * @dev not owner `NotOwnerOfDeposit()` + * @dev already claimed `AlreadyClaimed()` + * @dev === Emit events === + * @dev - `ClaimRewards` */ function claimRewards( uint256 tokenId_, @@ -120,11 +126,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @inheritdoc IRewardsManagerOwnerActions - * @dev revert on: - * - not owner NotOwnerOfDeposit() - * - invalid index params MoveStakedLiquidityInvalid() - * @dev emit events: - * - MoveStakedLiquidity + * @dev === Revert on === + * @dev not owner `NotOwnerOfDeposit()` + * @dev invalid index params `MoveStakedLiquidityInvalid()` + * @dev === Emit events === + * @dev - `MoveStakedLiquidity` */ function moveStakedLiquidity( uint256 tokenId_, @@ -193,10 +199,10 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @inheritdoc IRewardsManagerOwnerActions - * @dev revert on: - * - not owner NotOwnerOfDeposit() - * @dev emit events: - * - Stake + * @dev === Revert on === + * @dev not owner `NotOwnerOfDeposit()` + * @dev === Emit events === + * @dev - `Stake` */ function stake( uint256 tokenId_ @@ -255,11 +261,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @inheritdoc IRewardsManagerOwnerActions - * @dev revert on: - * - not owner NotOwnerOfDeposit() - * @dev emit events: - * - ClaimRewards - * - Unstake + * @dev === Revert on === + * @dev not owner `NotOwnerOfDeposit()` + * @dev === Emit events === + * @dev - `ClaimRewards` + * @dev - `Unstake` */ function unstake( uint256 tokenId_ @@ -298,8 +304,8 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @inheritdoc IRewardsManagerOwnerActions - * @dev emit events: - * - UpdateExchangeRates + * @dev === Emit events === + * @dev - `UpdateExchangeRates` */ function updateBucketExchangeRatesAndClaim( address pool_, @@ -369,11 +375,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /**************************/ /** - * @notice Calculate the amount of rewards that have been accumulated by a staked NFT. + * @notice Calculate the amount of rewards that have been accumulated by a staked `NFT`. * @dev Rewards are calculated as the difference in exchange rates between the last interaction burn event and the current burn event. - * @param tokenId_ ID of the staked LP NFT. + * @param tokenId_ `ID` of the staked `LP` `NFT`. * @param epochToClaim_ The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch). - * @return rewards_ Amount of rewards earned by the NFT. + * @return rewards_ Amount of rewards earned by the `NFT`. */ function _calculateAndClaimRewards( uint256 tokenId_, @@ -408,13 +414,13 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } /** - * @notice Calculate the amount of rewards that have been accumulated by a staked NFT in next epoch. + * @notice Calculate the amount of rewards that have been accumulated by a staked `NFT` in next epoch. * @dev Rewards are calculated as the difference in exchange rates between the last interaction burn event and the current burn event. - * @param tokenId_ ID of the staked LP NFT. + * @param tokenId_ `ID` of the staked `LP` `NFT`. * @param epoch_ The current epoch. * @param stakingEpoch_ The epoch in which token was staked. * @param ajnaPool_ Address of the pool. - * @param positionIndexes_ Bucket ids associated with NFT staked. + * @param positionIndexes_ Bucket ids associated with `NFT` staked. * @return epochRewards_ Calculated rewards in epoch. */ function _calculateNextEpochRewards( @@ -470,11 +476,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } /** - * @notice Calculate the amount of interest that has accrued to a lender in a bucket based upon their LP. + * @notice Calculate the amount of interest that has accrued to a lender in a bucket based upon their `LP`. * @param pool_ Address of the pool whose exchange rates are being checked. * @param nextEventEpoch_ The next event epoch to check the exchange rate for. * @param bucketIndex_ Index of the bucket to check the exchange rate for. - * @param bucketLPs Amount of LP in bucket. + * @param bucketLP_ Amount of `LP` in bucket. * @param exchangeRate_ Exchange rate in current epoch. * @return interestEarned_ The amount of interest accrued. */ @@ -482,7 +488,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { address pool_, uint256 nextEventEpoch_, uint256 bucketIndex_, - uint256 bucketLPs, + uint256 bucketLP_, uint256 exchangeRate_ ) internal view returns (uint256 interestEarned_) { @@ -495,7 +501,7 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { // calculate the equivalent amount of quote tokens given the stakes lp balance, // and the exchange rate at the next and current burn events - interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLPs); + interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLP_); } } @@ -503,11 +509,12 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @notice Calculate new rewards between current and next epoch, based on earned interest. - * @param ajnaPool_ Address of the pool. - * @param interestEarned_ The amount of interest accrued to current epoch. - * @param nextEpoch_ The next burn event epoch to calculate new rewards. - * @param epoch_ The current burn event epoch to calculate new rewards. - * @return newRewards_ New rewards between current and next burn event epoch. + * @param ajnaPool_ Address of the pool. + * @param interestEarned_ The amount of interest accrued to current epoch. + * @param nextEpoch_ The next burn event epoch to calculate new rewards. + * @param epoch_ The current burn event epoch to calculate new rewards. + * @param rewardsClaimedInEpoch_ Rewards claimed in epoch. + * @return newRewards_ New rewards between current and next burn event epoch. */ function _calculateNewRewards( address ajnaPool_, @@ -544,12 +551,12 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } /** - * @notice Claim rewards that have been accumulated by a staked NFT. - * @param stakeInfo_ Details of stake to claim rewards for. - * @param tokenId_ ID of the staked LP NFT. + * @notice Claim rewards that have been accumulated by a staked `NFT`. + * @param stakeInfo_ `StakeInfo` struct containing details of stake to claim rewards for. + * @param tokenId_ `ID` of the staked `LP` `NFT`. * @param epochToClaim_ The burn epoch to claim rewards for (rewards calculation starts from the last claimed epoch) * @param validateEpoch_ True if the epoch is received as a parameter and needs to be validated (lower or equal with latest epoch). - * @param ajnaPool_ Address of ajna pool associated with the stake. + * @param ajnaPool_ Address of `Ajna` pool associated with the stake. */ function _claimRewards( StakeInfo storage stakeInfo_, @@ -619,11 +626,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @notice Retrieve the total ajna tokens burned and total interest earned by a pool since a given block. - * @param pool_ Address of the Ajna pool to retrieve accumulators of. + * @param pool_ Address of the `Ajna` pool to retrieve accumulators of. * @param currentBurnEventEpoch_ The latest burn event. * @param lastBurnEventEpoch_ The burn event to use as checkpoint since which values have accumulated. * @return Timestamp of the latest burn event. - * @return Total ajna tokens burned by the pool since the last burn event. + * @return Total `Ajna` tokens burned by the pool since the last burn event. * @return Total interest earned by the pool since the last burn event. */ function _getPoolAccumulators( @@ -655,10 +662,11 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { /** * @notice Update the exchange rate of a list of buckets. - * @dev Called as part of stakeToken, unstakeToken, and claimRewards, as well as updateBucketExchangeRatesAndClaim. - * @dev Caller can claim 5% of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated. - * @param pool_ Address of the pool whose exchange rates are being updated. - * @param indexes_ List of bucket indexes to be updated. + * @dev Called as part of `stake`, `unstake`, and `claimRewards`, as well as `updateBucketExchangeRatesAndClaim`. + * @dev Caller can claim `5%` of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated. + * @param pool_ Address of the pool whose exchange rates are being updated. + * @param indexes_ List of bucket indexes to be updated. + * @return updatedRewards_ Update exchange rate rewards. */ function _updateBucketExchangeRates( address pool_, @@ -753,8 +761,9 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { * @param pool_ Address of the pool whose exchange rates are being updated. * @param bucketIndex_ Bucket index to update exchange rate. * @param burnEpoch_ Current burn epoch of the pool. - * @param totalBurned_ Total Ajna tokens burned in pool. + * @param totalBurned_ Total `Ajna` tokens burned in pool. * @param interestEarned_ Total interest rate earned in pool. + * @return rewards_ Rewards for bucket exchange rate update. */ function _updateBucketExchangeRateAndCalculateRewards( address pool_, @@ -794,8 +803,8 @@ contract RewardsManager is IRewardsManager, ReentrancyGuard { } } - /** @notice Utility method to transfer Ajna rewards to the sender - * @dev This method is used to transfer rewards to the sender after a successful claim or update. + /** @notice Utility method to transfer `Ajna` rewards to the sender + * @dev This method is used to transfer rewards to the `msg.sender` after a successful claim or update. * @dev It is used to ensure that rewards claimers will be able to claim some portion of the remaining tokens if a claim would exceed the remaining contract balance. * @param rewardsEarned_ Amount of rewards earned by the caller. */ diff --git a/src/base/FlashloanablePool.sol b/src/base/FlashloanablePool.sol index 53a109a97..069784383 100644 --- a/src/base/FlashloanablePool.sol +++ b/src/base/FlashloanablePool.sol @@ -10,9 +10,9 @@ import { IERC3156FlashBorrower } from '../interfaces/pool/IERC3156FlashBorrower. /** * @title Flashloanable Pool Contract - * @notice Pool contract with IERC3156 flash loans capabilities. - * @notice No fee is charged for taking flash loans from pool. - * @notice Flash loans can be taking in ERC20 quote and ERC20 collateral tokens. + * @notice Pool contract with `IERC3156` flashloans capabilities. + * @notice No fee is charged for taking flashloans from pool. + * @notice Flashloans can be taking in `ERC20` quote and `ERC20` collateral tokens. */ abstract contract FlashloanablePool is Pool { using SafeERC20 for IERC20; @@ -20,10 +20,10 @@ abstract contract FlashloanablePool is Pool { /** * @notice Called by flashloan borrowers to borrow liquidity which must be repaid in the same transaction. * @param receiver_ Address of the contract which implements the appropriate interface to receive tokens. - * @param token_ Address of the ERC20 token caller wants to borrow. + * @param token_ Address of the `ERC20` token caller wants to borrow. * @param amount_ The denormalized amount (dependent upon token precision) of tokens to borrow. * @param data_ User-defined calldata passed to the receiver. - * @return success_ True if flashloan was successful. + * @return success_ `True` if flashloan was successful. */ function flashLoan( IERC3156FlashBorrower receiver_, @@ -59,7 +59,7 @@ abstract contract FlashloanablePool is Pool { } /** - * @notice Returns 0, as no fee is charged for flashloans. + * @notice Returns `0`, as no fee is charged for flashloans. */ function flashFee( address token_, @@ -71,7 +71,7 @@ abstract contract FlashloanablePool is Pool { /** * @notice Returns the amount of tokens available to be lent. - * @param token_ Address of the ERC20 token to be lent. + * @param token_ Address of the `ERC20` token to be lent. * @return maxLoan_ The amount of `token_` that can be lent. */ function maxFlashLoan( @@ -81,10 +81,10 @@ abstract contract FlashloanablePool is Pool { } /** - * @notice Returns true if pool allows flashloans for given token address, false otherwise. + * @notice Returns `true` if pool allows flashloans for given token address, `false` otherwise. * @dev Allows flashloans for quote token, overriden in pool implementation to allow flashloans for other tokens. - * @param token_ Address of the ERC20 token to be lent. - * @return True if token can be flashloaned, false otherwise. + * @param token_ Address of the `ERC20` token to be lent. + * @return `True` if token can be flashloaned, `false` otherwise. */ function _isFlashloanSupported( address token_ diff --git a/src/base/PermitERC721.sol b/src/base/PermitERC721.sol index 4d4c3acbe..a92acaf31 100644 --- a/src/base/PermitERC721.sol +++ b/src/base/PermitERC721.sol @@ -6,7 +6,14 @@ import { IERC1271 } from '@openzeppelin/contracts/interfaces/IERC1271.sol'; import { ERC721 } from '@openzeppelin/contracts/token/ERC721/ERC721.sol'; import { Address } from '@openzeppelin/contracts/utils/Address.sol'; + +/** + * @dev Interface for token permits for ERC-721 + */ interface IPermit { + /** + * @notice `EIP-4494` permit to approve by way of owner signature. + */ function permit( address spender_, uint256 tokenId_, uint256 deadline_, uint8 v_, bytes32 r_, bytes32 s_ ) external; @@ -14,7 +21,7 @@ interface IPermit { /** * @notice https://soliditydeveloper.com/erc721-permit - * @notice Functionality to enable EIP-4494 permit calls as part of interactions with Position NFTs + * @notice Functionality to enable `EIP-4494` permit calls as part of interactions with Position `NFT`s * @dev spender https://eips.ethereum.org/EIPS/eip-4494 */ abstract contract PermitERC721 is ERC721, IPermit { @@ -32,7 +39,7 @@ abstract contract PermitERC721 is ERC721, IPermit { bytes32 public constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; - /** @notice Computes the nameHash and versionHash based upon constructor input */ + /** @notice Computes the `nameHash` and `versionHash` based upon constructor input */ constructor( string memory name_, string memory symbol_, string memory version_ ) ERC721(name_, symbol_) { @@ -41,8 +48,8 @@ abstract contract PermitERC721 is ERC721, IPermit { } /** - * @notice Calculate the EIP-712 compliant DOMAIN_SEPERATOR for ledgible signature encoding - * @return The bytes32 domain separator of Position NFTs + * @notice Calculate the `EIP-712` compliant `DOMAIN_SEPERATOR` for ledgible signature encoding. + * @return The `bytes32` domain separator of Position `NFT`s. */ function DOMAIN_SEPARATOR() public view returns (bytes32) { return @@ -59,13 +66,13 @@ abstract contract PermitERC721 is ERC721, IPermit { } /** - * @notice Called by a NFT owner to enable a third party spender to interact with their NFT - * @param spender_ The address of the third party who will execute the transaction involving an owners NFT - * @param tokenId_ The id of the NFT being interacted with - * @param deadline_ The unix timestamp by which the permit must be called - * @param v_ Component of secp256k1 signature - * @param r_ Component of secp256k1 signature - * @param s_ Component of secp256k1 signature + * @notice Called by a `NFT` owner to enable a third party spender to interact with their `NFT`. + * @param spender_ The address of the third party who will execute the transaction involving an owners `NFT`. + * @param tokenId_ The id of the `NFT` being interacted with. + * @param deadline_ The unix timestamp by which the permit must be called. + * @param v_ Component of `secp256k1` signature. + * @param r_ Component of `secp256k1` signature. + * @param s_ Component of `secp256k1` signature. */ function permit( address spender_, uint256 tokenId_, uint256 deadline_, uint8 v_, bytes32 r_, bytes32 s_ @@ -106,8 +113,8 @@ abstract contract PermitERC721 is ERC721, IPermit { } /** - * @dev Gets the current chain ID - * @return chainId_ The current chain ID + * @dev Gets the current chain id + * @return chainId_ The current chain id */ function _chainId() internal view returns (uint256 chainId_) { assembly { diff --git a/src/base/Pool.sol b/src/base/Pool.sol index d07088c4e..60956e0e2 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -67,7 +67,7 @@ import { PoolCommons } from '../libraries/external/PoolCommons.sol'; /** * @title Pool Contract - * @dev Base contract and entrypoint for commong logic of both ERC20 and ERC721 pools. + * @dev Base contract and entrypoint for commong logic of both `ERC20` and `ERC721` pools. */ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { @@ -77,11 +77,15 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /*** Constants ***/ /*****************/ - // immutable args offset + /// @dev Immutable pool type arg offset. uint256 internal constant POOL_TYPE = 0; + /// @dev Immutable `Ajna` token address arg offset. uint256 internal constant AJNA_ADDRESS = 1; + /// @dev Immutable collateral token address arg offset. uint256 internal constant COLLATERAL_ADDRESS = 21; + /// @dev Immutable quote token address arg offset. uint256 internal constant QUOTE_ADDRESS = 41; + /// @dev Immutable quote token scale arg offset. uint256 internal constant QUOTE_SCALE = 61; /***********************/ @@ -97,13 +101,16 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { PoolBalancesState internal poolBalances; ReserveAuctionState internal reserveAuction; - mapping(uint256 => Bucket) internal buckets; // deposit index -> bucket + /// @dev deposit index -> bucket mapping + mapping(uint256 => Bucket) internal buckets; bool internal isPoolInitialized; - mapping(address => mapping(address => mapping(uint256 => uint256))) private _lpAllowances; // owner address -> new owner address -> deposit index -> allowed amount + /// @dev owner address -> new owner address -> deposit index -> allowed amount mapping + mapping(address => mapping(address => mapping(uint256 => uint256))) private _lpAllowances; - mapping(address => mapping(address => bool)) public override approvedTransferors; // owner address -> transferor address -> approved flag + /// @dev owner address -> transferor address -> approved flag mapping + mapping(address => mapping(address => bool)) public override approvedTransferors; /******************/ /*** Immutables ***/ @@ -140,23 +147,23 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /// @inheritdoc IPoolLenderActions function addQuoteToken( - uint256 quoteTokenAmountToAdd_, + uint256 amount_, uint256 index_, uint256 expiry_ - ) external override nonReentrant returns (uint256 bucketLPs_) { + ) external override nonReentrant returns (uint256 bucketLP_) { _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); // round to token precision - quoteTokenAmountToAdd_ = _roundToScale(quoteTokenAmountToAdd_, poolState.quoteDustLimit); + amount_ = _roundToScale(amount_, poolState.quoteDustLimit); uint256 newLup; - (bucketLPs_, newLup) = LenderActions.addQuoteToken( + (bucketLP_, newLup) = LenderActions.addQuoteToken( buckets, deposits, poolState, AddQuoteParams({ - amount: quoteTokenAmountToAdd_, + amount: amount_, index: index_ }) ); @@ -165,16 +172,16 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _updateInterestState(poolState, newLup); // move quote token amount from lender to pool - _transferQuoteTokenFrom(msg.sender, quoteTokenAmountToAdd_); + _transferQuoteTokenFrom(msg.sender, amount_); } /// @inheritdoc IPoolLenderActions function moveQuoteToken( - uint256 maxAmountToMove_, + uint256 maxAmount_, uint256 fromIndex_, uint256 toIndex_, uint256 expiry_ - ) external override nonReentrant returns (uint256 fromBucketLPs_, uint256 toBucketLPs_, uint256 movedAmount_) { + ) external override nonReentrant returns (uint256 fromBucketLP_, uint256 toBucketLP_, uint256 movedAmount_) { _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); @@ -182,8 +189,8 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { uint256 newLup; ( - fromBucketLPs_, - toBucketLPs_, + fromBucketLP_, + toBucketLP_, movedAmount_, newLup ) = LenderActions.moveQuoteToken( @@ -191,7 +198,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { deposits, poolState, MoveQuoteParams({ - maxAmountToMove: maxAmountToMove_, + maxAmountToMove: maxAmount_, fromIndex: fromIndex_, toIndex: toIndex_, thresholdPrice: Loans.getMax(loans).thresholdPrice @@ -206,7 +213,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { function removeQuoteToken( uint256 maxAmount_, uint256 index_ - ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLPs_) { + ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); @@ -216,7 +223,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { uint256 newLup; ( removedAmount_, - redeemedLPs_, + redeemedLP_, newLup ) = LenderActions.removeQuoteToken( buckets, @@ -266,12 +273,12 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @inheritdoc IPoolKickerActions - * @dev write state: - * - increment poolBalances.t0DebtInAuction and poolBalances.t0Debt accumulators + * @dev === Write state === + * @dev increment `poolBalances.t0DebtInAuction` and `poolBalances.t0Debt` accumulators */ function kick( - address borrowerAddress_, - uint256 limitIndex_ + address borrower_, + uint256 npLimitIndex_ ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); @@ -281,8 +288,8 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { deposits, loans, poolState, - borrowerAddress_, - limitIndex_ + borrower_, + npLimitIndex_ ); // update pool balances state @@ -302,17 +309,17 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { poolState.t0Debt = result.t0PoolDebt; _updateInterestState(poolState, result.lup); - if(result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); + if (result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); } /** * @inheritdoc IPoolKickerActions - * @dev write state: - * - increment poolBalances.t0DebtInAuction and poolBalances.t0Debt accumulators + * @dev === Write state === + * @dev increment `poolBalances.t0DebtInAuction` and `poolBalances.t0Debt` accumulators */ function kickWithDeposit( uint256 index_, - uint256 limitIndex_ + uint256 npLimitIndex_ ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); @@ -324,7 +331,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { loans, poolState, index_, - limitIndex_ + npLimitIndex_ ); // update pool balances state @@ -345,14 +352,14 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _updateInterestState(poolState, result.lup); // transfer from kicker to pool the difference to cover bond - if(result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); + if (result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); } /** * @inheritdoc IPoolKickerActions - * @dev write state: - * - decrease kicker's claimable accumulator - * - decrease auctions totalBondEscrowed accumulator + * @dev === Write state === + * @dev decrease kicker's `claimable` accumulator + * @dev decrease auctions `totalBondEscrowed` accumulator */ function withdrawBonds( address recipient_, @@ -382,13 +389,13 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @inheritdoc IPoolKickerActions - * @dev write state: - * - increment latestBurnEpoch counter - * - update reserveAuction.latestBurnEventEpoch and burn event timestamp state - * @dev reverts on: - * - 2 weeks not passed ReserveAuctionTooSoon() - * @dev emit events: - * - KickReserveAuction + * @dev === Write state === + * @dev increment `latestBurnEpoch` counter + * @dev update `reserveAuction.latestBurnEventEpoch` and burn event `timestamp` state + * @dev === Reverts on === + * @dev 2 weeks not passed `ReserveAuctionTooSoon()` + * @dev === Emit events === + * @dev - `KickReserveAuction` */ function kickReserveAuction() external override nonReentrant { // start a new claimable reserve auction, passing in relevant parameters such as the current pool size, debt, balance, and inflator value @@ -409,9 +416,9 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @inheritdoc IPoolTakerActions - * @dev write state: - * - increment reserveAuction.totalAjnaBurned accumulator - * - update burn event totalInterest and totalBurned accumulators + * @dev === Write state === + * @dev increment `reserveAuction.totalAjnaBurned` accumulator + * @dev update burn event `totalInterest` and `totalBurned` accumulators */ function takeReserves( uint256 maxAmount_ @@ -485,11 +492,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); } - /** - * @inheritdoc IPoolLPActions - * @dev write state: - * - approvedTransferors mapping - */ + /// @inheritdoc IPoolLPActions function revokeLPTransferors( address[] calldata transferors_ ) external override { @@ -521,13 +524,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @notice Accrues pool interest in current block and returns pool details. - * @dev external libraries call: - * - PoolCommons.accrueInterest - * @dev write state: - * - PoolCommons.accrueInterest: - * - Deposits.mult (scale Fenwick tree with new interest accrued): - * - update scaling array state - * - increment reserveAuction.totalInterestEarned accumulator + * @dev external libraries call: `PoolCommons.accrueInterest` + * @dev === Write state === + * @dev - `PoolCommons.accrueInterest` - `Deposits.mult` (scale `Fenwick` tree with new interest accrued): + * @dev update scaling array state + * @dev - increment `reserveAuction.totalInterestEarned` accumulator * @return poolState_ Struct containing pool details. */ function _accruePoolInterest() internal returns (PoolState memory poolState_) { @@ -569,10 +570,10 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } /** - * @notice Adjusts the t0 Debt 2 to collateral ratio, interestState.t0Debt2ToCollateral. - * @dev Anytime a borrower's debt or collateral changes, the interestState.t0Debt2ToCollateral must be updated. - * @dev write state: - * - update interestState.t0Debt2ToCollateral accumulator + * @notice Adjusts the `t0` debt 2 to collateral ratio, `interestState.t0Debt2ToCollateral`. + * @dev Anytime a borrower's debt or collateral changes, the `interestState.t0Debt2ToCollateral` must be updated. + * @dev === Write state === + * @dev update `interestState.t0Debt2ToCollateral` accumulator * @param debtPreAction_ Borrower's debt before the action * @param debtPostAction_ Borrower's debt after the action * @param colPreAction_ Borrower's collateral before the action @@ -598,18 +599,16 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { /** * @notice Update interest rate and inflator of the pool. - * @dev external libraries call: - * - PoolCommons.updateInterestState - * @dev write state: - * - PoolCommons.updateInterestState - * - interest debt and lup * collateral EMAs accumulators - * - interest rate accumulator and interestRateUpdate state - * - pool inflator and inflatorUpdate state - * @dev emit events: - * - PoolCommons.updateInterestState: - * - UpdateInterestRate + * @dev external libraries call: `PoolCommons.updateInterestState` + * @dev === Write state === + * @dev - `PoolCommons.updateInterestState` + * @dev `EMA`s accumulators + * @dev interest rate accumulator and `interestRateUpdate` state + * @dev pool inflator and `inflatorUpdate` state + * @dev === Emit events === + * @dev `PoolCommons.updateInterestState`: `UpdateInterestRate` * @param poolState_ Struct containing pool details. - * @param lup_ Current LUP in pool. + * @param lup_ Current `LUP` in pool. */ function _updateInterestState( PoolState memory poolState_, @@ -630,16 +629,26 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } } + /** + * @notice Helper function to transfer amount of quote tokens (in quote token precision) from sender to pool contract. + * @param from_ Sender address. + * @param amount_ Amount to transfer from sender. + */ function _transferQuoteTokenFrom(address from_, uint256 amount_) internal { IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransferFrom(from_, address(this), amount_ / _getArgUint256(QUOTE_SCALE)); } + /** + * @notice Helper function to transfer amount of quote tokens (in quote token precision) from pool contract. + * @param to_ Receiver address. + * @param amount_ Amount to transfer to receiver. + */ function _transferQuoteToken(address to_, uint256 amount_) internal { IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(QUOTE_SCALE)); } /** - * @dev returns the pool quote token balance normalized to WAD to be used for calculating pool reserves + * @notice Returns the pool quote token balance normalized to `WAD` to be used for calculating pool reserves. */ function _getNormalizedPoolQuoteTokenBalance() internal view returns (uint256) { return IERC20(_getArgAddress(QUOTE_ADDRESS)).balanceOf(address(this)) * _getArgUint256(QUOTE_SCALE); @@ -654,16 +663,16 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { address borrower_ ) external view override returns ( - address kicker, - uint256 bondFactor, - uint256 bondSize, - uint256 kickTime, - uint256 kickMomp, - uint256 neutralPrice, - address head, - address next, - address prev, - bool alreadyTaken + address kicker_, + uint256 bondFactor_, + uint256 bondSize_, + uint256 kickTime_, + uint256 kickMomp_, + uint256 neutralPrice_, + address head_, + address next_, + address prev_, + bool alreadyTaken_ ) { Liquidation memory liquidation = auctions.liquidations[borrower_]; return ( diff --git a/src/base/PoolDeployer.sol b/src/base/PoolDeployer.sol index 977c04f7b..60372b29a 100644 --- a/src/base/PoolDeployer.sol +++ b/src/base/PoolDeployer.sol @@ -10,16 +10,19 @@ import { IPoolFactory } from '../interfaces/pool/IPoolFactory.sol'; */ abstract contract PoolDeployer { + /// @dev Min interest rate value allowed for deploying the pool (1%) uint256 public constant MIN_RATE = 0.01 * 1e18; + /// @dev Max interest rate value allowed for deploying the pool (10% uint256 public constant MAX_RATE = 0.1 * 1e18; + /// @dev `Ajna` token address address public ajna; // Ajna token contract address on a network. /***********************/ /*** State Variables ***/ /***********************/ - /// @dev SubsetHash => CollateralAddress => QuoteAddress => Pool Address + /// @dev SubsetHash => CollateralAddress => QuoteAddress => Pool Address mapping // slither-disable-next-line uninitialized-state mapping(bytes32 => mapping(address => mapping(address => address))) public deployedPools; @@ -33,7 +36,7 @@ abstract contract PoolDeployer { /** * @notice Ensures that pools are deployed according to specifications. - * @dev Used by both ERC20, and ERC721 pool factory types. + * @dev Used by both `ERC20` and `ERC721` pool factories. */ modifier canDeploy(address collateral_, address quote_, uint256 interestRate_) { if (collateral_ == quote_) revert IPoolFactory.DeployQuoteCollateralSameToken(); @@ -50,7 +53,7 @@ abstract contract PoolDeployer { * @notice Returns the list of all deployed pools. * @dev This function is used by integrations to access deployed pools. * @dev Each factory implementation maintains its own list of deployed pools. - * @return address[] memory List of all deployed pools. + * @return List of all deployed pools. */ function getDeployedPoolsList() external view returns (address[] memory) { return deployedPoolsList; @@ -58,7 +61,7 @@ abstract contract PoolDeployer { /** * @notice Returns the number of deployed pools that have been deployed by a factory. - * @return uint256 length of deployedPoolsList array. + * @return Length of `deployedPoolsList` array. */ function getNumberOfDeployedPools() external view returns (uint256) { return deployedPoolsList.length; diff --git a/src/interfaces/pool/IERC3156FlashBorrower.sol b/src/interfaces/pool/IERC3156FlashBorrower.sol index 8e23435e7..8f009d100 100644 --- a/src/interfaces/pool/IERC3156FlashBorrower.sol +++ b/src/interfaces/pool/IERC3156FlashBorrower.sol @@ -11,7 +11,7 @@ interface IERC3156FlashBorrower { * @param amount The amount of tokens lent. * @param fee The additional amount of tokens to repay. * @param data Arbitrary data structure, intended to contain user-defined parameters. - * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" + * @return The `keccak256` hash of `ERC3156FlashBorrower.onFlashLoan` */ function onFlashLoan( address initiator, diff --git a/src/interfaces/pool/IERC3156FlashLender.sol b/src/interfaces/pool/IERC3156FlashLender.sol index 84f0b8ecd..a301305f9 100644 --- a/src/interfaces/pool/IERC3156FlashLender.sol +++ b/src/interfaces/pool/IERC3156FlashLender.sol @@ -32,6 +32,7 @@ interface IERC3156FlashLender { * @param token_ The loan currency. * @param amount_ The amount of tokens lent. * @param data_ Arbitrary data structure, intended to contain user-defined parameters. + * @return `True` when successful flashloan, `false` otherwise. */ function flashLoan( IERC3156FlashBorrower receiver_, diff --git a/src/interfaces/pool/IPool.sol b/src/interfaces/pool/IPool.sol index 63ad3b669..f3e77ea35 100644 --- a/src/interfaces/pool/IPool.sol +++ b/src/interfaces/pool/IPool.sol @@ -17,7 +17,7 @@ import { IPoolErrors } from './commons/IPoolErrors.sol'; import { IERC3156FlashLender } from './IERC3156FlashLender.sol'; /** - * @title Base Pool + * @title Base Pool Interface */ interface IPool is IPoolBorrowerActions, @@ -36,8 +36,10 @@ interface IPool is } +/// @dev Pool type enum - `ERC20` and `ERC721` enum PoolType { ERC20, ERC721 } +/// @dev `ERC20` token interface. interface IERC20Token { function balanceOf(address account) external view returns (uint256); function burn(uint256 amount) external; @@ -50,6 +52,7 @@ interface IERC20Token { ) external returns (bool); } +/// @dev `ERC721` token interface. interface IERC721Token { function transferFrom( address from, diff --git a/src/interfaces/pool/IPoolFactory.sol b/src/interfaces/pool/IPoolFactory.sol index bcf64a367..c6ad8cad7 100644 --- a/src/interfaces/pool/IPoolFactory.sol +++ b/src/interfaces/pool/IPoolFactory.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; /** - * @title Pool Factory Interface + * @title Pool Factory Interface. * @dev Used to deploy both funigible and non fungible pools. */ interface IPoolFactory { @@ -17,7 +17,7 @@ interface IPoolFactory { error DeployQuoteCollateralSameToken(); /** - * @notice Can't deploy with one of the args pointing to the 0x0 address. + * @notice Can't deploy with one of the args pointing to the `0x` address. */ error DeployWithZeroAddress(); diff --git a/src/interfaces/pool/commons/IPoolBorrowerActions.sol b/src/interfaces/pool/commons/IPoolBorrowerActions.sol index 141f085a9..35dec7bf0 100644 --- a/src/interfaces/pool/commons/IPoolBorrowerActions.sol +++ b/src/interfaces/pool/commons/IPoolBorrowerActions.sol @@ -8,9 +8,9 @@ pragma solidity 0.8.14; interface IPoolBorrowerActions { /** - * @notice Called by fully colalteralized borrowers to restamp the Neutral Price of the loan (only if loan is fully collateralized and not in auction). - * @notice The reason for stamping the neutral price on the loan is to provide some certainty to the borrower as to at what price they can expect to be liquidated. - * @notice This action can restamp only the loan of `msg.sender`. + * @notice Called by fully colalteralized borrowers to restamp the `Neutral Price` of the loan (only if loan is fully collateralized and not in auction). + * The reason for stamping the neutral price on the loan is to provide some certainty to the borrower as to at what price they can expect to be liquidated. + * This action can restamp only the loan of `msg.sender`. */ function stampLoan() external; diff --git a/src/interfaces/pool/commons/IPoolDerivedState.sol b/src/interfaces/pool/commons/IPoolDerivedState.sol index 07198be57..94294f83d 100644 --- a/src/interfaces/pool/commons/IPoolDerivedState.sol +++ b/src/interfaces/pool/commons/IPoolDerivedState.sol @@ -7,14 +7,19 @@ pragma solidity 0.8.14; */ interface IPoolDerivedState { + /** + * @notice Returns the exchange rate for a given bucket index. + * @param index_ The bucket index. + * @return exchangeRate_ Exchange rate of the bucket. + */ function bucketExchangeRate( uint256 index_ ) external view returns (uint256 exchangeRate_); /** - * @notice Returns the prefix sum of a given bucket - * @param index_ The target index - * @return Prefix sum + * @notice Returns the prefix sum of a given bucket. + * @param index_ The bucket index. + * @return The deposit up to given index. */ function depositUpToIndex( uint256 index_ diff --git a/src/interfaces/pool/commons/IPoolErrors.sol b/src/interfaces/pool/commons/IPoolErrors.sol index 3a9351f3b..440fb7c91 100644 --- a/src/interfaces/pool/commons/IPoolErrors.sol +++ b/src/interfaces/pool/commons/IPoolErrors.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.14; /** - * @title Pool Errors + * @title Pool Errors. */ interface IPoolErrors { /**************************/ @@ -11,7 +11,7 @@ interface IPoolErrors { /**************************/ /** - * @notice LP allowance is already set by the owner. + * @notice `LP` allowance is already set by the owner. */ error AllowanceAlreadySet(); @@ -46,7 +46,7 @@ interface IPoolErrors { error AmountLTMinDebt(); /** - * @notice Recipient of borrowed quote tokens doesn't match the caller of the drawDebt function. + * @notice Recipient of borrowed quote tokens doesn't match the caller of the `drawDebt` function. */ error BorrowerNotSender(); @@ -76,12 +76,12 @@ interface IPoolErrors { error DustAmountNotExceeded(); /** - * @notice Callback invoked by flashLoan function did not return the expected hash (see ERC-3156 spec). + * @notice Callback invoked by `flashLoan` function did not return the expected hash (see `ERC-3156` spec). */ error FlashloanCallbackFailed(); /** - * @notice Balance of pool contract before flash loan is different than the balance after flash loan. + * @notice Balance of pool contract before flashloan is different than the balance after flashloan. */ error FlashloanIncorrectBalance(); @@ -98,7 +98,7 @@ interface IPoolErrors { /** * @notice Lender is attempting to move or remove more collateral they have claim to in the bucket. * @notice Lender is attempting to remove more collateral they have claim to in the bucket. - * @notice Lender must have enough LP to claim the desired amount of quote from the bucket. + * @notice Lender must have enough `LP` to claim the desired amount of quote from the bucket. */ error InsufficientLP(); @@ -108,33 +108,33 @@ interface IPoolErrors { error InsufficientLiquidity(); /** - * @notice When increasing / decreasing LP allowances indexes and amounts arrays parameters should have same length. + * @notice When increasing / decreasing `LP` allowances indexes and amounts arrays parameters should have same length. */ error InvalidAllowancesInput(); /** - * @notice When transferring LP between indices, the new index must be a valid index. + * @notice When transferring `LP` between indices, the new index must be a valid index. */ error InvalidIndex(); /** - * @notice The amount used for performed action should be greater than 0. + * @notice The amount used for performed action should be greater than `0`. */ error InvalidAmount(); /** - * @notice Borrower is attempting to borrow more quote token than is available before the supplied limitIndex. + * @notice Borrower is attempting to borrow more quote token than is available before the supplied `limitIndex`. */ error LimitIndexExceeded(); /** - * @notice When moving quote token HTP must stay below LUP. - * @notice When removing quote token HTP must stay below LUP. + * @notice When moving quote token `HTP` must stay below `LUP`. + * @notice When removing quote token `HTP` must stay below `LUP`. */ error LUPBelowHTP(); /** - * @notice Liquidation must result in LUP below the borrowers threshold price. + * @notice Liquidation must result in `LUP` below the borrowers threshold price. */ error LUPGreaterThanTP(); @@ -144,7 +144,7 @@ interface IPoolErrors { error MoveToSameIndex(); /** - * @notice Owner of the LP must have approved the new owner prior to transfer. + * @notice Owner of the `LP` must have approved the new owner prior to transfer. */ error NoAllowance(); @@ -164,7 +164,7 @@ interface IPoolErrors { error NoReservesAuction(); /** - * @notice Lender must have non-zero LPB when attemptign to remove quote token from the pool. + * @notice Lender must have non-zero `LP` when attemptign to remove quote token from the pool. */ error NoClaim(); @@ -180,7 +180,7 @@ interface IPoolErrors { error PoolUnderCollateralized(); /** - * @notice Actor is attempting to remove using a bucket with price below the LUP. + * @notice Actor is attempting to remove using a bucket with price below the `LUP`. */ error PriceBelowLUP(); @@ -190,12 +190,12 @@ interface IPoolErrors { error RemoveDepositLockedByAuctionDebt(); /** - * @notice User attempted to kick off a new auction less than 2 weeks since the last auction completed. + * @notice User attempted to kick off a new auction less than `2` weeks since the last auction completed. */ error ReserveAuctionTooSoon(); /** - * @notice Take was called before 1 hour had passed from kick time. + * @notice Take was called before `1` hour had passed from kick time. */ error TakeNotPastCooldown(); @@ -205,12 +205,12 @@ interface IPoolErrors { error TransactionExpired(); /** - * @notice The address that transfer LP is not approved by the LP receiving address. + * @notice The address that transfer `LP` is not approved by the `LP` receiving address. */ error TransferorNotApproved(); /** - * @notice Owner of the LP attemps to transfer LP to same address. + * @notice Owner of the `LP` attemps to transfer `LP` to same address. */ error TransferToSameOwner(); diff --git a/src/interfaces/pool/commons/IPoolEvents.sol b/src/interfaces/pool/commons/IPoolEvents.sol index adcaa6321..e404fdd66 100644 --- a/src/interfaces/pool/commons/IPoolEvents.sol +++ b/src/interfaces/pool/commons/IPoolEvents.sol @@ -16,8 +16,8 @@ interface IPoolEvents { * @param lender Recipient that added quote tokens. * @param index Index at which quote tokens were added. * @param amount Amount of quote tokens added to the pool. - * @param lpAwarded Amount of LP awarded for the deposit. - * @param lup LUP calculated after deposit. + * @param lpAwarded Amount of `LP` awarded for the deposit. + * @param lup `LUP` calculated after deposit. */ event AddQuoteToken( address indexed lender, @@ -33,9 +33,9 @@ interface IPoolEvents { * @param from Price bucket from which quote tokens were moved. * @param to Price bucket where quote tokens were moved. * @param amount Amount of quote tokens moved. - * @param lpRedeemedFrom Amount of LP removed from the `from` bucket. - * @param lpAwardedTo Amount of LP credited to the `to` bucket. - * @param lup LUP calculated after removal. + * @param lpRedeemedFrom Amount of `LP` removed from the `from` bucket. + * @param lpAwardedTo Amount of `LP` credited to the `to` bucket. + * @param lup `LUP` calculated after removal. */ event MoveQuoteToken( address indexed lender, @@ -52,8 +52,8 @@ interface IPoolEvents { * @param lender Recipient that removed quote tokens. * @param index Index at which quote tokens were removed. * @param amount Amount of quote tokens removed from the pool. - * @param lpRedeemed Amount of LP exchanged for quote token. - * @param lup LUP calculated after removal. + * @param lpRedeemed Amount of `LP` exchanged for quote token. + * @param lup `LUP` calculated after removal. */ event RemoveQuoteToken( address indexed lender, @@ -67,8 +67,8 @@ interface IPoolEvents { * @notice Emitted when lender claims collateral from a bucket. * @param claimer Recipient that claimed collateral. * @param index Index at which collateral was claimed. - * @param amount The amount of collateral (or number of NFT tokens) transferred to the claimer. - * @param lpRedeemed Amount of LP exchanged for quote token. + * @param amount The amount of collateral (or number of `NFT` tokens) transferred to the claimer. + * @param lpRedeemed Amount of `LP` exchanged for quote token. */ event RemoveCollateral( address indexed claimer, @@ -82,11 +82,11 @@ interface IPoolEvents { /***********************/ /** - * @notice Emitted when borrower repays quote tokens to the pool, and/or pulls collateral from the pool. + * @notice Emitted when borrower repays quote tokens to the pool and/or pulls collateral from the pool. * @param borrower `msg.sender` or on behalf of sender. * @param quoteRepaid Amount of quote tokens repaid to the pool. - * @param collateralPulled The amount of collateral (or number of NFT tokens) transferred to the claimer. - * @param lup LUP after repay. + * @param collateralPulled The amount of collateral (or number of `NFT` tokens) transferred to the claimer. + * @param lup `LUP` after repay. */ event RepayDebt( address indexed borrower, @@ -128,11 +128,11 @@ interface IPoolEvents { /** * @notice Emitted when an actor uses quote token to arb higher-priced deposit off the book. * @param borrower Identifies the loan being liquidated. - * @param index The index of the Highest Price Bucket used for this take. + * @param index The index of the `Highest Price Bucket` used for this take. * @param amount Amount of quote token used to purchase collateral. * @param collateral Amount of collateral purchased with quote token. * @param bondChange Impact of this take to the liquidation bond. - * @param isReward True if kicker was rewarded with `bondChange` amount, false if kicker was penalized. + * @param isReward `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized. * @dev amount / collateral implies the auction price. */ event BucketTake( @@ -145,11 +145,11 @@ interface IPoolEvents { ); /** - * @notice Emitted when LP are awarded to a taker or kicker in a bucket take. + * @notice Emitted when `LP` are awarded to a taker or kicker in a bucket take. * @param taker Actor who invoked the bucket take. * @param kicker Actor who started the auction. - * @param lpAwardedTaker Amount of LP awarded to the taker. - * @param lpAwardedKicker Amount of LP awarded to the actor who started the auction. + * @param lpAwardedTaker Amount of `LP` awarded to the taker. + * @param lpAwardedKicker Amount of `LP` awarded to the actor who started the auction. */ event BucketTakeLPAwarded( address indexed taker, @@ -162,9 +162,9 @@ interface IPoolEvents { * @notice Emitted when an actor uses quote token outside of the book to purchase collateral under liquidation. * @param borrower Identifies the loan being liquidated. * @param amount Amount of quote token used to purchase collateral. - * @param collateral Amount of collateral purchased with quote token (ERC20 pool) or number of NFTs purchased (ERC721 pool). + * @param collateral Amount of collateral purchased with quote token (`ERC20` pool) or number of `NFT`s purchased (`ERC721` pool). * @param bondChange Impact of this take to the liquidation bond. - * @param isReward True if kicker was rewarded with `bondChange` amount, false if kicker was penalized. + * @param isReward `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized. * @dev amount / collateral implies the auction price. */ event Take( @@ -179,7 +179,7 @@ interface IPoolEvents { * @notice Emitted when an actor settles debt in a completed liquidation * @param borrower Identifies the loan under liquidation. * @param settledDebt Amount of pool debt settled in this transaction. - * @dev When amountRemaining_ == 0, the auction has been completed cleared and removed from the queue. + * @dev When `amountRemaining_ == 0`, the auction has been completed cleared and removed from the queue. */ event Settle( address indexed borrower, @@ -197,23 +197,23 @@ interface IPoolEvents { ); /** - * @notice Emitted when NFT auction is completed. + * @notice Emitted when `NFT` auction is completed. * @param borrower Address of borrower that exits auction. * @param collateral Borrower's remaining collateral when auction completed. - * @param lps Amount of LP given to the borrower to compensate fractional collateral (if any). - * @param index Index of the bucket with LP to compensate fractional collateral. + * @param lp Amount of `LP` given to the borrower to compensate fractional collateral (if any). + * @param index Index of the bucket with `LP` to compensate fractional collateral. */ event AuctionNFTSettle( address indexed borrower, uint256 collateral, - uint256 lps, + uint256 lp, uint256 index ); /** - * @notice Emitted when a Claimaible Reserve Auction is started. + * @notice Emitted when a `Claimaible Reserve Auction` is started. * @return claimableReservesRemaining Amount of claimable reserves which has not yet been taken. - * @return auctionPrice Current price at which 1 quote token may be purchased, denominated in Ajna. + * @return auctionPrice Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @return currentBurnEpoch Current burn epoch. */ event KickReserveAuction( @@ -223,9 +223,9 @@ interface IPoolEvents { ); /** - * @notice Emitted when a Claimaible Reserve Auction is taken. + * @notice Emitted when a `Claimaible Reserve Auction` is taken. * @return claimableReservesRemaining Amount of claimable reserves which has not yet been taken. - * @return auctionPrice Current price at which 1 quote token may be purchased, denominated in Ajna. + * @return auctionPrice Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @return currentBurnEpoch Current burn epoch. */ event ReserveAuction( @@ -239,11 +239,11 @@ interface IPoolEvents { /**************************/ /** - * @notice Emitted when owner increase the LP allowance of a spender at specified indexes with specified amounts. - * @param owner LP owner. - * @param spender Address approved to transfer LP. - * @param indexes Bucket indexes of LP approved. - * @param amounts LP amounts added (ordered by indexes). + * @notice Emitted when owner increase the `LP` allowance of a spender at specified indexes with specified amounts. + * @param owner `LP` owner. + * @param spender Address approved to transfer `LP`. + * @param indexes Bucket indexes of `LP` approved. + * @param amounts `LP` amounts added (ordered by indexes). */ event IncreaseLPAllowance( address indexed owner, @@ -253,11 +253,11 @@ interface IPoolEvents { ); /** - * @notice Emitted when owner decrease the LP allowance of a spender at specified indexes with specified amounts. - * @param owner LP owner. - * @param spender Address approved to transfer LP. - * @param indexes Bucket indexes of LP approved. - * @param amounts LP amounts removed (ordered by indexes). + * @notice Emitted when owner decrease the `LP` allowance of a spender at specified indexes with specified amounts. + * @param owner `LP` owner. + * @param spender Address approved to transfer `LP`. + * @param indexes Bucket indexes of `LP` approved. + * @param amounts `LP` amounts removed (ordered by indexes). */ event DecreaseLPAllowance( address indexed owner, @@ -267,8 +267,8 @@ interface IPoolEvents { ); /** - * @notice Emitted when lender removes the allowance of a spender for their LP. - * @param owner LP owner. + * @notice Emitted when lender removes the allowance of a spender for their `LP`. + * @param owner `LP` owner. * @param spender Address that is having it's allowance revoked. * @param indexes List of bucket index to remove the allowance from. */ @@ -279,9 +279,9 @@ interface IPoolEvents { ); /** - * @notice Emitted when lender whitelists addresses to accept LP from. - * @param lender Recipient that approves new owner for LP. - * @param transferors List of addresses that can transfer LP to lender. + * @notice Emitted when lender whitelists addresses to accept `LP` from. + * @param lender Recipient that approves new owner for `LP`. + * @param transferors List of addresses that can transfer `LP` to lender. */ event ApproveLPTransferors( address indexed lender, @@ -289,9 +289,9 @@ interface IPoolEvents { ); /** - * @notice Emitted when lender removes addresses from the LP transferors whitelist. - * @param lender Recipient that approves new owner for LP. - * @param transferors List of addresses that won't be able to transfer LP to lender anymore. + * @notice Emitted when lender removes addresses from the `LP` transferors whitelist. + * @param lender Recipient that approves new owner for `LP`. + * @param transferors List of addresses that won't be able to transfer `LP` to lender anymore. */ event RevokeLPTransferors( address indexed lender, @@ -299,18 +299,18 @@ interface IPoolEvents { ); /** - * @notice Emitted when a lender transfers their LP to a different address. - * @dev Used by PositionManager.memorializePositions(). + * @notice Emitted when a lender transfers their `LP` to a different address. + * @dev Used by `PositionManager.memorializePositions()`. * @param owner The original owner address of the position. * @param newOwner The new owner address of the position. - * @param indexes Array of price bucket indexes at which LP were transferred. - * @param lps Amount of LP transferred. + * @param indexes Array of price bucket indexes at which `LP` were transferred. + * @param lp Amount of `LP` transferred. */ event TransferLP( address owner, address newOwner, uint256[] indexes, - uint256 lps + uint256 lp ); /**************************/ @@ -318,9 +318,9 @@ interface IPoolEvents { /**************************/ /** - * @notice Emitted when LP are forfeited as a result of the bucket losing all assets. + * @notice Emitted when `LP` are forfeited as a result of the bucket losing all assets. * @param index The index of the bucket. - * @param lpForfeited Amount of LP forfeited by lenders. + * @param lpForfeited Amount of `LP` forfeited by lenders. */ event BucketBankruptcy( uint256 indexed index, @@ -340,15 +340,15 @@ interface IPoolEvents { ); /** - * @notice Emitted when a loan Neutral Price is restamped. - * @param borrower Identifies the loan to update the Neutral Price. + * @notice Emitted when a loan `Neutral Price` is restamped. + * @param borrower Identifies the loan to update the `Neutral Price`. */ event LoanStamped( address indexed borrower ); /** - * @notice Emitted when pool interest rate is reset. This happens when interest rate > 10% and debtEma < 5% of depositEma + * @notice Emitted when pool interest rate is reset. This happens when `interest rate > 10%` and `debtEma < 5%` of `depositEma` * @param oldRate Old pool interest rate. * @param newRate New pool interest rate. */ diff --git a/src/interfaces/pool/commons/IPoolImmutables.sol b/src/interfaces/pool/commons/IPoolImmutables.sol index addf29a6b..96e0c915e 100644 --- a/src/interfaces/pool/commons/IPoolImmutables.sol +++ b/src/interfaces/pool/commons/IPoolImmutables.sol @@ -8,23 +8,23 @@ pragma solidity 0.8.14; interface IPoolImmutables { /** - * @notice Returns the type of the pool (0 for ERC20, 1 for ERC721) + * @notice Returns the type of the pool (`0` for `ERC20`, `1` for `ERC721`). */ function poolType() external pure returns (uint8); /** - * @notice Returns the address of the pool's collateral token + * @notice Returns the address of the pool's collateral token. */ function collateralAddress() external pure returns (address); /** - * @notice Returns the address of the pools quote token + * @notice Returns the address of the pool's quote token. */ function quoteTokenAddress() external pure returns (address); /** * @notice Returns the `quoteTokenScale` state variable. - * @return The precision of the quote ERC-20 token based on decimals. + * @return The precision of the quote `ERC20` token based on decimals. */ function quoteTokenScale() external pure returns (uint256); diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 002ca9f2e..8a82801db 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -10,6 +10,7 @@ pragma solidity 0.8.14; /*** Auction Param Structs ***/ /*****************************/ +/// @dev Struct used to return result of `TakerAction.bucketTake` action. struct BucketTakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP @@ -26,6 +27,7 @@ struct BucketTakeResult { uint256 collateralPostAction; // [WAD] The amount of borrower collateral after take } +/// @dev Struct used to return result of `KickerAction.kick` action. struct KickResult { uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered uint256 t0PoolDebt; // [WAD] t0 debt in pool after kick @@ -35,12 +37,14 @@ struct KickResult { uint256 collateralPreAction; // [WAD] The amount of borrower collateral before kick, same as the one after kick } +/// @dev Struct used to hold parameters for `SettlerAction.settlePoolDebt` action. struct SettleParams { address borrower; // borrower address to settle uint256 bucketDepth; // number of buckets to use when settle debt uint256 poolBalance; // current pool quote token balance } +/// @dev Struct used to return result of `SettlerAction.settlePoolDebt` action. struct SettleResult { uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before settle uint256 debtPostAction; // [WAD] The amount of borrower t0 debt remaining after settle @@ -50,6 +54,7 @@ struct SettleResult { uint256 t0DebtSettled; // [WAD] The amount of t0 debt settled } +/// @dev Struct used to return result of `TakerAction.take` action. struct TakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP @@ -68,6 +73,7 @@ struct TakeResult { uint256 collateralPostAction; // [WAD] The amount of borrower collateral after take } +/// @dev Struct used to hold parameters for `KickerAction.kickReserveAuction` action. struct KickReserveAuctionParams { uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) uint256 t0PoolDebt; // [WAD] current t0 pool debt @@ -79,11 +85,13 @@ struct KickReserveAuctionParams { /*** Liquidity Management Param Structs ***/ /******************************************/ +/// @dev Struct used to hold parameters for `LenderAction.addQuoteToken` action. struct AddQuoteParams { uint256 amount; // [WAD] amount to be added uint256 index; // the index in which to deposit } +/// @dev Struct used to hold parameters for `LenderAction.moveQuoteToken` action. struct MoveQuoteParams { uint256 fromIndex; // the deposit index from where amount is moved uint256 maxAmountToMove; // [WAD] max amount to move between deposits @@ -91,6 +99,7 @@ struct MoveQuoteParams { uint256 thresholdPrice; // [WAD] max threshold price in pool } +/// @dev Struct used to hold parameters for `LenderAction.removeQuoteToken` action. struct RemoveQuoteParams { uint256 index; // the deposit index from where amount is removed uint256 maxAmount; // [WAD] max amount to be removed @@ -101,6 +110,7 @@ struct RemoveQuoteParams { /*** Loan Management Param Structs ***/ /*************************************/ +/// @dev Struct used to return result of `BorrowerActions.drawDebt` action. struct DrawDebtResult { uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pledge collateral @@ -115,6 +125,7 @@ struct DrawDebtResult { uint256 collateralPostAction; // [WAD] The amount of borrower collateral after draw debt } +/// @dev Struct used to return result of `BorrowerActions.repayDebt` action. struct RepayDebtResult { uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pull collateral diff --git a/src/interfaces/pool/commons/IPoolKickerActions.sol b/src/interfaces/pool/commons/IPoolKickerActions.sol index 2b3e117a5..b55e27d39 100644 --- a/src/interfaces/pool/commons/IPoolKickerActions.sol +++ b/src/interfaces/pool/commons/IPoolKickerActions.sol @@ -13,32 +13,32 @@ interface IPoolKickerActions { /** * @notice Called by actors to initiate a liquidation. - * @param borrower Identifies the loan to liquidate. - * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. + * @param borrower_ Identifies the loan to liquidate. + * @param npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. */ function kick( - address borrower, - uint256 npLimitIndex + address borrower_, + uint256 npLimitIndex_ ) external; /** * @notice Called by lenders to liquidate the top loan using their deposits. - * @param index The deposit index to use for kicking the top loan. - * @param npLimitIndex Index of the lower bound of NP tolerated when kicking the auction. + * @param index_ The deposit index to use for kicking the top loan. + * @param npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. */ function kickWithDeposit( - uint256 index, - uint256 npLimitIndex + uint256 index_, + uint256 npLimitIndex_ ) external; /** * @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions). - * @param recipient Address to receive claimed bonds amount. - * @param maxAmount The max amount to withdraw from auction bonds. Constrained by claimable amounts and liquidity + * @param recipient_ Address to receive claimed bonds amount. + * @param maxAmount_ The max amount to withdraw from auction bonds. Constrained by claimable amounts and liquidity */ function withdrawBonds( - address recipient, - uint256 maxAmount + address recipient_, + uint256 maxAmount_ ) external; /***********************/ @@ -46,7 +46,7 @@ interface IPoolKickerActions { /***********************/ /** - * @notice Called by actor to start a Claimable Reserve Auction (CRA). + * @notice Called by actor to start a `Claimable Reserve Auction` (`CRA`). */ function kickReserveAuction() external; } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolLPActions.sol b/src/interfaces/pool/commons/IPoolLPActions.sol index a50f5d8e1..370ee5f40 100644 --- a/src/interfaces/pool/commons/IPoolLPActions.sol +++ b/src/interfaces/pool/commons/IPoolLPActions.sol @@ -3,74 +3,74 @@ pragma solidity 0.8.14; /** - * @title Pool LP Actions + * @title Pool `LP` Actions */ interface IPoolLPActions { /** - * @notice Called by LP owners to approve transfer of an amount of LP to a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LP. - * @param indexes Bucket indexes from where LP are transferred. - * @param amounts The amounts of LP approved to transfer. + * @notice Called by `LP` owners to approve transfer of an amount of `LP` to a new owner. + * @dev Intended for use by the `PositionManager` contract. + * @param spender_ The new owner of the `LP`. + * @param indexes_ Bucket indexes from where `LP` are transferred. + * @param amounts_ The amounts of `LP` approved to transfer. */ function increaseLPAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ ) external; /** - * @notice Called by LP owners to decrease the amount of LP that can be spend by a new owner. - * @dev Intended for use by the PositionManager contract. - * @param spender The new owner of the LP. - * @param indexes Bucket indexes from where LP are transferred. - * @param amounts The amounts of LP disapproved to transfer. + * @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner. + * @dev Intended for use by the `PositionManager` contract. + * @param spender_ The new owner of the `LP`. + * @param indexes_ Bucket indexes from where `LP` are transferred. + * @param amounts_ The amounts of `LP` disapproved to transfer. */ function decreaseLPAllowance( - address spender, - uint256[] calldata indexes, - uint256[] calldata amounts + address spender_, + uint256[] calldata indexes_, + uint256[] calldata amounts_ ) external; /** - * @notice Called by LP owners to decrease the amount of LP that can be spend by a new owner. - * @param spender Address that is having it's allowance revoked. - * @param indexes List of bucket index to remove the allowance from. + * @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner. + * @param spender_ Address that is having it's allowance revoked. + * @param indexes_ List of bucket index to remove the allowance from. */ function revokeLPAllowance( - address spender, - uint256[] calldata indexes + address spender_, + uint256[] calldata indexes_ ) external; /** - * @notice Called by LP owners to allow addresses that can transfer LP. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are allowed to transfer LP to new owner. + * @notice Called by `LP` owners to allow addresses that can transfer LP. + * @dev Intended for use by the `PositionManager` contract. + * @param transferors_ Addresses that are allowed to transfer `LP` to new owner. */ function approveLPTransferors( - address[] calldata transferors + address[] calldata transferors_ ) external; /** - * @notice Called by LP owners to revoke addresses that can transfer LP. - * @dev Intended for use by the PositionManager contract. - * @param transferors Addresses that are revoked to transfer LP to new owner. + * @notice Called by `LP` owners to revoke addresses that can transfer `LP`. + * @dev Intended for use by the `PositionManager` contract. + * @param transferors_ Addresses that are revoked to transfer `LP` to new owner. */ function revokeLPTransferors( - address[] calldata transferors + address[] calldata transferors_ ) external; /** - * @notice Called by LP owners to transfers their LP to a different address. approveLpOwnership needs to be run first - * @dev Used by PositionManager.memorializePositions(). - * @param owner The original owner address of the position. - * @param newOwner The new owner address of the position. - * @param indexes Array of price buckets index at which LP were moved. + * @notice Called by `LP` owners to transfers their `LP` to a different address. `approveLpOwnership` needs to be run first. + * @dev Used by `PositionManager.memorializePositions()`. + * @param owner_ The original owner address of the position. + * @param newOwner_ The new owner address of the position. + * @param indexes_ Array of price buckets index at which `LP` were moved. */ function transferLP( - address owner, - address newOwner, - uint256[] calldata indexes + address owner_, + address newOwner_, + uint256[] calldata indexes_ ) external; } diff --git a/src/interfaces/pool/commons/IPoolLenderActions.sol b/src/interfaces/pool/commons/IPoolLenderActions.sol index 33fd237f8..8ea6e23a3 100644 --- a/src/interfaces/pool/commons/IPoolLenderActions.sol +++ b/src/interfaces/pool/commons/IPoolLenderActions.sol @@ -13,64 +13,64 @@ interface IPoolLenderActions { /** * @notice Called by lenders to add an amount of credit at a specified price bucket. - * @param amount The amount of quote token to be added by a lender. - * @param index The index of the bucket to which the quote tokens will be added. - * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LP changed for the added quote tokens. + * @param amount_ The amount of quote token to be added by a lender. + * @param index_ The index of the bucket to which the quote tokens will be added. + * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. + * @return bucketLP_ The amount of `LP` changed for the added quote tokens. */ function addQuoteToken( - uint256 amount, - uint256 index, - uint256 expiry - ) external returns (uint256 lpbChange); + uint256 amount_, + uint256 index_, + uint256 expiry_ + ) external returns (uint256 bucketLP_); /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket. - * @param maxAmount The maximum amount of quote token to be moved by a lender. - * @param fromIndex The bucket index from which the quote tokens will be removed. - * @param toIndex The bucket index to which the quote tokens will be added. - * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbAmountFrom The amount of LP moved out from bucket. - * @return lpbAmountTo The amount of LP moved to destination bucket. - * @return quoteTokenAmount The amount of quote token moved. + * @param maxAmount_ The maximum amount of quote token to be moved by a lender. + * @param fromIndex_ The bucket index from which the quote tokens will be removed. + * @param toIndex_ The bucket index to which the quote tokens will be added. + * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. + * @return fromBucketLP_ The amount of `LP` moved out from bucket. + * @return toBucketLP_ The amount of `LP` moved to destination bucket. + * @return movedAmount_ The amount of quote token moved. */ function moveQuoteToken( - uint256 maxAmount, - uint256 fromIndex, - uint256 toIndex, - uint256 expiry - ) external returns (uint256 lpbAmountFrom, uint256 lpbAmountTo, uint256 quoteTokenAmount); + uint256 maxAmount_, + uint256 fromIndex_, + uint256 toIndex_, + uint256 expiry_ + ) external returns (uint256 fromBucketLP_, uint256 toBucketLP_, uint256 movedAmount_); /** * @notice Called by lenders to claim collateral from a price bucket. - * @param maxAmount The amount of collateral (or the number of NFT tokens) to claim. - * @param index The bucket index from which collateral will be removed. - * @return collateralAmount The amount of collateral removed. - * @return lpAmount The amount of LP used for removing collateral amount. + * @param maxAmount_ The amount of collateral (or the number of `NFT` tokens) to claim. + * @param index_ The bucket index from which collateral will be removed. + * @return removedAmount_ The amount of collateral removed. + * @return redeemedLP_ The amount of `LP` used for removing collateral amount. */ function removeCollateral( - uint256 maxAmount, - uint256 index - ) external returns (uint256 collateralAmount, uint256 lpAmount); + uint256 maxAmount_, + uint256 index_ + ) external returns (uint256 removedAmount_, uint256 redeemedLP_); /** * @notice Called by lenders to remove an amount of credit at a specified price bucket. - * @param maxAmount The max amount of quote token to be removed by a lender. - * @param index The bucket index from which quote tokens will be removed. - * @return quoteTokenAmount The amount of quote token removed. - * @return lpAmount The amount of LP used for removing quote tokens amount. + * @param maxAmount_ The max amount of quote token to be removed by a lender. + * @param index_ The bucket index from which quote tokens will be removed. + * @return removedAmount_ The amount of quote token removed. + * @return redeemedLP_ The amount of `LP` used for removing quote tokens amount. */ function removeQuoteToken( - uint256 maxAmount, - uint256 index - ) external returns (uint256 quoteTokenAmount, uint256 lpAmount); + uint256 maxAmount_, + uint256 index_ + ) external returns (uint256 removedAmount_, uint256 redeemedLP_); /********************************/ /*** Interest update function ***/ /********************************/ /** - * @notice Called by actors to update pool interest rate (can be updated only once in a 12 hours period of time). + * @notice Called by actors to update pool interest rate (can be updated only once in a `12` hours period of time). */ function updateInterest() external; diff --git a/src/interfaces/pool/commons/IPoolSettlerActions.sol b/src/interfaces/pool/commons/IPoolSettlerActions.sol index 180ecad7a..7973545e2 100644 --- a/src/interfaces/pool/commons/IPoolSettlerActions.sol +++ b/src/interfaces/pool/commons/IPoolSettlerActions.sol @@ -9,13 +9,13 @@ interface IPoolSettlerActions { /** * @notice Called by actors to settle an amount of debt in a completed liquidation. - * @param borrowerAddress Address of the auctioned borrower. - * @param maxDepth Measured from HPB, maximum number of buckets deep to settle debt. - * @dev maxDepth is used to prevent unbounded iteration clearing large liquidations. + * @param borrowerAddress_ Address of the auctioned borrower. + * @param maxDepth_ Measured from `HPB`, maximum number of buckets deep to settle debt. + * @dev `maxDepth_` is used to prevent unbounded iteration clearing large liquidations. */ function settle( - address borrowerAddress, - uint256 maxDepth + address borrowerAddress_, + uint256 maxDepth_ ) external; } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index 04ebdabfb..46356542c 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -9,233 +9,241 @@ interface IPoolState { /** * @notice Returns details of an auction for a given borrower address. - * @param borrower Address of the borrower that is liquidated. - * @return kicker Address of the kicker that is kicking the auction. - * @return bondFactor The factor used for calculating bond size. - * @return bondSize The bond amount in quote token terms. - * @return kickTime Time the liquidation was initiated. - * @return kickMomp Price where the average loan utilizes deposit, at the time when the loan is liquidated (kicked). - * @return neutralPrice Neutral Price of auction. - * @return head Address of the head auction. - * @return next Address of the next auction in queue. - * @return prev Address of the prev auction in queue. - * @return alreadyTaken True if take has been called on auction + * @param borrower_ Address of the borrower that is liquidated. + * @return kicker_ Address of the kicker that is kicking the auction. + * @return bondFactor_ The factor used for calculating bond size. + * @return bondSize_ The bond amount in quote token terms. + * @return kickTime_ Time the liquidation was initiated. + * @return kickMomp_ Price where the average loan utilizes deposit, at the time when the loan is liquidated (kicked). + * @return neutralPrice_ `Neutral Price` of auction. + * @return head_ Address of the head auction. + * @return next_ Address of the next auction in queue. + * @return prev_ Address of the prev auction in queue. + * @return alreadyTaken_ True if take has been called on auction */ - function auctionInfo(address borrower) + function auctionInfo(address borrower_) external view returns ( - address kicker, - uint256 bondFactor, - uint256 bondSize, - uint256 kickTime, - uint256 kickMomp, - uint256 neutralPrice, - address head, - address next, - address prev, - bool alreadyTaken + address kicker_, + uint256 bondFactor_, + uint256 bondSize_, + uint256 kickTime_, + uint256 kickMomp_, + uint256 neutralPrice_, + address head_, + address next_, + address prev_, + bool alreadyTaken_ ); /** * @notice Returns pool related debt values. - * @return debt_ Current amount of debt owed by borrowers in pool. - * @return accruedDebt_ Debt owed by borrowers based on last inflator snapshot. + * @return debt_ Current amount of debt owed by borrowers in pool. + * @return accruedDebt_ Debt owed by borrowers based on last inflator snapshot. * @return debtInAuction_ Total amount of debt in auction. * @return t0Debt2ToCollateral_ t0debt accross all borrowers divided by their collateral, used in determining a collateralization weighted debt. */ - function debtInfo() external view returns (uint256 debt_, uint256 accruedDebt_, uint256 debtInAuction_, uint256 t0Debt2ToCollateral_); + function debtInfo() + external + view + returns ( + uint256 debt_, + uint256 accruedDebt_, + uint256 debtInAuction_, + uint256 t0Debt2ToCollateral_ + ); /** - * @notice Mapping of borrower addresses to {Borrower} structs. + * @notice Mapping of borrower addresses to `Borrower` structs. * @dev NOTE: Cannot use appended underscore syntax for return params since struct is used. - * @param borrower Address of the borrower. - * @return t0Debt Amount of debt borrower would have had if their loan was the first debt drawn from the pool - * @return collateral Amount of collateral that the borrower has deposited, in collateral token. - * @return t0Np t0 Neutral Price + * @param borrower_ Address of the borrower. + * @return t0Debt_ Amount of debt borrower would have had if their loan was the first debt drawn from the pool. + * @return collateral_ Amount of collateral that the borrower has deposited, in collateral token. + * @return t0Np_ t0 `Neutral Price` */ - function borrowerInfo(address borrower) + function borrowerInfo(address borrower_) external view returns ( - uint256 t0Debt, - uint256 collateral, - uint256 t0Np + uint256 t0Debt_, + uint256 collateral_, + uint256 t0Np_ ); /** - * @notice Mapping of buckets indexes to {Bucket} structs. + * @notice Mapping of buckets indexes to `Bucket` structs. * @dev NOTE: Cannot use appended underscore syntax for return params since struct is used. - * @param index Bucket index. - * @return lpAccumulator Amount of LP accumulated in current bucket. - * @return availableCollateral Amount of collateral available in current bucket. - * @return bankruptcyTime Timestamp when bucket become insolvent, 0 if healthy. - * @return bucketDeposit Amount of quote tokens in bucket. - * @return bucketScale Bucket multiplier. + * @param index_ Bucket index. + * @return lpAccumulator_ Amount of `LP` accumulated in current bucket. + * @return availableCollateral_ Amount of collateral available in current bucket. + * @return bankruptcyTime_ Timestamp when bucket become insolvent, `0` if healthy. + * @return bucketDeposit_ Amount of quote tokens in bucket. + * @return bucketScale_ Bucket multiplier. */ - function bucketInfo(uint256 index) + function bucketInfo(uint256 index_) external view returns ( - uint256 lpAccumulator, - uint256 availableCollateral, - uint256 bankruptcyTime, - uint256 bucketDeposit, - uint256 bucketScale + uint256 lpAccumulator_, + uint256 availableCollateral_, + uint256 bankruptcyTime_, + uint256 bucketDeposit_, + uint256 bucketScale_ ); /** - * @notice Mapping of burnEventEpoch to {BurnEvent} structs. + * @notice Mapping of burnEventEpoch to `BurnEvent` structs. * @dev Reserve auctions correspond to burn events. * @param burnEventEpoch_ Id of the current reserve auction. - * @return burnBlock Block in which a reserve auction started. - * @return totalInterest Total interest as of the reserve auction. - * @return totalBurned Total ajna tokens burned as of the reserve auction. + * @return burnBlock_ Block in which a reserve auction started. + * @return totalInterest_ Total interest as of the reserve auction. + * @return totalBurned_ Total ajna tokens burned as of the reserve auction. */ function burnInfo(uint256 burnEventEpoch_) external view returns (uint256, uint256, uint256); /** - * @notice Returns the latest burnEventEpoch of reserve auctions. + * @notice Returns the latest `burnEventEpoch` of reserve auctions. * @dev If a reserve auction is active, it refers to the current reserve auction. If no reserve auction is active, it refers to the last reserve auction. - * @return burnEventEpoch Current burnEventEpoch. + * @return Current `burnEventEpoch`. */ function currentBurnEpoch() external view returns (uint256); /** - * @notice Returns information about the pool EMA (Exponential Moving Average) variables. - * @return debtColEma Debt squared to collateral Exponential, numerator to TU calculation - * @return lupt0DebtEma Exponential of LUP * t0 debt, denominator to TU calculation - * @return debtEma Exponential debt moving average. - * @return depositEma sample of meaningful deposit Exponential, denominator to MAU calculation. + * @notice Returns information about the pool `EMA (Exponential Moving Average)` variables. + * @return debtColEma_ Debt squared to collateral Exponential, numerator to `TU` calculation. + * @return lupt0DebtEma_ Exponential of `LUP * t0 debt`, denominator to `TU` calculation + * @return debtEma_ Exponential debt moving average. + * @return depositEma_ sample of meaningful deposit Exponential, denominator to `MAU` calculation. */ function emasInfo() external view returns ( - uint256 debtColEma, - uint256 lupt0DebtEma, - uint256 debtEma, - uint256 depositEma + uint256 debtColEma_, + uint256 lupt0DebtEma_, + uint256 debtEma_, + uint256 depositEma_ ); /** * @notice Returns information about pool inflator. - * @return inflator Pool inflator value. - * @return lastUpdate The timestamp of the last `inflator` update. + * @return inflator_ Pool inflator value. + * @return lastUpdate_ The timestamp of the last `inflator` update. */ function inflatorInfo() external view returns ( - uint256 inflator, - uint256 lastUpdate + uint256 inflator_, + uint256 lastUpdate_ ); /** * @notice Returns information about pool interest rate. - * @return interestRate Current interest rate in pool. - * @return interestRateUpdate The timestamp of the last interest rate update. + * @return interestRate_ Current interest rate in pool. + * @return interestRateUpdate_ The timestamp of the last interest rate update. */ function interestRateInfo() external view returns ( - uint256 interestRate, - uint256 interestRateUpdate + uint256 interestRate_, + uint256 interestRateUpdate_ ); /** * @notice Returns details about kicker balances. - * @param kicker The address of the kicker to retrieved info for. - * @return claimable Amount of quote token kicker can claim / withdraw from pool at any time. - * @return locked Amount of quote token kicker locked in auctions (as bonds). + * @param kicker_ The address of the kicker to retrieved info for. + * @return claimable_ Amount of quote token kicker can claim / withdraw from pool at any time. + * @return locked_ Amount of quote token kicker locked in auctions (as bonds). */ - function kickerInfo(address kicker) + function kickerInfo(address kicker_) external view returns ( - uint256 claimable, - uint256 locked + uint256 claimable_, + uint256 locked_ ); /** - * @notice Mapping of buckets indexes and owner addresses to {Lender} structs. - * @param index Bucket index. - * @param lp Address of the liquidity provider. - * @return lpBalance Amount of LP owner has in current bucket. - * @return lastQuoteDeposit Time the user last deposited quote token. + * @notice Mapping of buckets indexes and owner addresses to `Lender` structs. + * @param index_ Bucket index. + * @param lender_ Address of the liquidity provider. + * @return lpBalance_ Amount of `LP` owner has in current bucket. + * @return depositTime_ Time the user last deposited quote token. */ function lenderInfo( - uint256 index, - address lp + uint256 index_, + address lender_ ) external view returns ( - uint256 lpBalance, - uint256 lastQuoteDeposit + uint256 lpBalance_, + uint256 depositTime_ ); /** - * @notice Return the LPB allowance a LP owner provided to a spender. - * @param index Bucket index. - * @param spender Address of the LPB spender. - * @param owner The initial owner of the LP. - * @return allowance_ Amount of LP spender can utilize. + * @notice Return the `LP` allowance a `LP` owner provided to a spender. + * @param index_ Bucket index. + * @param spender_ Address of the `LP` spender. + * @param owner_ The initial owner of the `LP`. + * @return allowance_ Amount of `LP` spender can utilize. */ function lpAllowance( - uint256 index, - address spender, - address owner + uint256 index_, + address spender_, + address owner_ ) external view returns (uint256 allowance_); /** * @notice Returns information about a loan in the pool. - * @param loanId Loan's id within loan heap. Max loan is position 1. - * @return borrower Borrower address at the given position. - * @return thresholdPrice Borrower threshold price in pool. + * @param loanId_ Loan's id within loan heap. Max loan is position `1`. + * @return borrower_ Borrower address at the given position. + * @return thresholdPrice_ Borrower threshold price in pool. */ function loanInfo( - uint256 loanId + uint256 loanId_ ) external view returns ( - address borrower, - uint256 thresholdPrice + address borrower_, + uint256 thresholdPrice_ ); /** * @notice Returns information about pool loans. - * @return maxBorrower Borrower address with highest threshold price. - * @return maxThresholdPrice Highest threshold price in pool. - * @return noOfLoans Total number of loans. + * @return maxBorrower_ Borrower address with highest threshold price. + * @return maxThresholdPrice_ Highest threshold price in pool. + * @return noOfLoans_ Total number of loans. */ function loansInfo() external view returns ( - address maxBorrower, - uint256 maxThresholdPrice, - uint256 noOfLoans + address maxBorrower_, + uint256 maxThresholdPrice_, + uint256 noOfLoans_ ); /** * @notice Returns information about pool reserves. - * @return liquidationBondEscrowed Amount of liquidation bond across all liquidators. - * @return reserveAuctionUnclaimed Amount of claimable reserves which has not been taken in the Claimable Reserve Auction. - * @return reserveAuctionKicked Time a Claimable Reserve Auction was last kicked. - * @return totalInterestEarned Total interest earned by all lenders in the pool + * @return liquidationBondEscrowed_ Amount of liquidation bond across all liquidators. + * @return reserveAuctionUnclaimed_ Amount of claimable reserves which has not been taken in the `Claimable Reserve Auction`. + * @return reserveAuctionKicked_ Time a `Claimable Reserve Auction` was last kicked. + * @return totalInterestEarned_ Total interest earned by all lenders in the pool */ function reservesInfo() external view returns ( - uint256 liquidationBondEscrowed, - uint256 reserveAuctionUnclaimed, - uint256 reserveAuctionKicked, - uint256 totalInterestEarned + uint256 liquidationBondEscrowed_, + uint256 reserveAuctionUnclaimed_, + uint256 reserveAuctionKicked_, + uint256 totalInterestEarned_ ); /** @@ -245,34 +253,34 @@ interface IPoolState { function pledgedCollateral() external view returns (uint256); /** - * @notice Returns the total number of active auctions in pool - * @return totalAuctions_ number of active auctions. + * @notice Returns the total number of active auctions in pool. + * @return totalAuctions_ Number of active auctions. */ function totalAuctionsInPool() external view returns (uint256); /** * @notice Returns the `t0Debt` state variable. * @dev This value should be multiplied by inflator in order to calculate current debt of the pool. - * @return The total t0Debt in the system, in WAD units. + * @return The total `t0Debt` in the system, in `WAD` units. */ function totalT0Debt() external view returns (uint256); /** * @notice Returns the `t0DebtInAuction` state variable. * @dev This value should be multiplied by inflator in order to calculate current debt in auction of the pool. - * @return The total t0DebtInAuction in the system, in WAD units. + * @return The total `t0DebtInAuction` in the system, in `WAD` units. */ function totalT0DebtInAuction() external view returns (uint256); /** - * @notice Mapping of addresses that can transfer LP to a given lender. - * @param lender Lender that receives LP. - * @param transferor Transferor that transfers LP. + * @notice Mapping of addresses that can transfer `LP` to a given lender. + * @param lender_ Lender that receives `LP`. + * @param transferor_ Transferor that transfers `LP`. * @return True if the transferor is approved by lender. */ function approvedTransferors( - address lender, - address transferor + address lender_, + address transferor_ ) external view returns (bool); } @@ -281,13 +289,17 @@ interface IPoolState { /*** State Structs ***/ /*********************/ +/******************/ /*** Pool State ***/ +/******************/ +/// @dev Struct holding inflator state. struct InflatorState { uint208 inflator; // [WAD] pool's inflator uint48 inflatorUpdate; // [SEC] last time pool's inflator was updated } +/// @dev Struct holding pool interest state. struct InterestState { uint208 interestRate; // [WAD] pool's interest rate uint48 interestRateUpdate; // [SEC] last time pool's interest rate was updated (not before 12 hours passed) @@ -298,6 +310,7 @@ struct InterestState { uint256 lupt0Debt; // [WAD] previous LUP * t0 debt } +/// @dev Struct holding pool EMAs state. struct EmaState { uint256 debtEma; // [WAD] sample of debt EMA, numerator to MAU calculation uint256 depositEma; // [WAD] sample of meaningful deposit EMA, denominator to MAU calculation @@ -306,12 +319,14 @@ struct EmaState { uint256 emaUpdate; // [SEC] last time pool's EMAs were updated } +/// @dev Struct holding pool balances state. struct PoolBalancesState { uint256 pledgedCollateral; // [WAD] total collateral pledged in pool uint256 t0DebtInAuction; // [WAD] Total debt in auction used to restrict LPB holder from withdrawing uint256 t0Debt; // [WAD] Pool debt as if the whole amount was incurred upon the first loan } +/// @dev Struct holding pool params (in memory only). struct PoolState { uint8 poolType; // pool type, can be ERC20 or ERC721 uint256 t0Debt; // [WAD] t0 debt in pool @@ -323,13 +338,17 @@ struct PoolState { uint256 quoteDustLimit; // [WAD] quote token dust limit of the pool } +/*********************/ /*** Buckets State ***/ +/*********************/ +/// @dev Struct holding lender state. struct Lender { uint256 lps; // [WAD] Lender LP accumulator uint256 depositTime; // timestamp of last deposit } +/// @dev Struct holding bucket state. struct Bucket { uint256 lps; // [WAD] Bucket LP accumulator uint256 collateral; // [WAD] Available collateral tokens deposited in the bucket @@ -337,34 +356,45 @@ struct Bucket { mapping(address => Lender) lenders; // lender address to Lender struct mapping } +/**********************/ /*** Deposits State ***/ +/**********************/ +/// @dev Struct holding deposits (Fenwick) values and scaling. struct DepositsState { uint256[8193] values; // Array of values in the FenwickTree. uint256[8193] scaling; // Array of values which scale (multiply) the FenwickTree accross indexes. } +/*******************/ /*** Loans State ***/ +/*******************/ +/// @dev Struct holding loans state. struct LoansState { Loan[] loans; mapping (address => uint) indices; // borrower address => loan index mapping mapping (address => Borrower) borrowers; // borrower address => Borrower struct mapping } +/// @dev Struct holding loan state. struct Loan { address borrower; // borrower address uint96 thresholdPrice; // [WAD] Loan's threshold price. } +/// @dev Struct holding borrower state. struct Borrower { uint256 t0Debt; // [WAD] Borrower debt time-adjusted as if it was incurred upon first loan of pool. uint256 collateral; // [WAD] Collateral deposited by borrower. uint256 t0Np; // [WAD] Neutral Price time-adjusted as if it was incurred upon first loan of pool. } +/**********************/ /*** Auctions State ***/ +/**********************/ +/// @dev Struct holding pool auctions state. struct AuctionsState { uint96 noOfAuctions; // total number of auctions in pool address head; // first address in auction queue @@ -374,6 +404,7 @@ struct AuctionsState { mapping(address => Kicker) kickers; // mapping of kicker address and kicker balances } +/// @dev Struct holding liquidation state. struct Liquidation { address kicker; // address that initiated liquidation uint96 bondFactor; // [WAD] bond factor used to start liquidation @@ -386,13 +417,17 @@ struct Liquidation { bool alreadyTaken; // true if take has been called on auction } +/// @dev Struct holding kicker state. struct Kicker { uint256 claimable; // [WAD] kicker's claimable balance uint256 locked; // [WAD] kicker's balance of tokens locked in auction bonds } -/*** Reserve Auction State ***/ +/******************************/ +/*** Reserve Auctions State ***/ +/******************************/ +/// @dev Struct holding reserve auction state. struct ReserveAuctionState { uint256 kicked; // Time a Claimable Reserve Auction was last kicked. uint256 unclaimed; // [WAD] Amount of claimable reserves which has not been taken in the Claimable Reserve Auction. @@ -402,6 +437,7 @@ struct ReserveAuctionState { mapping (uint256 => BurnEvent) burnEvents; // Mapping burnEventEpoch => BurnEvent. } +/// @dev Struct holding burn event state. struct BurnEvent { uint256 timestamp; // time at which the burn event occured uint256 totalInterest; // [WAD] current pool interest accumulator `PoolCommons.accrueInterest().newInterest` diff --git a/src/interfaces/pool/commons/IPoolTakerActions.sol b/src/interfaces/pool/commons/IPoolTakerActions.sol index 627cacabf..543412e61 100644 --- a/src/interfaces/pool/commons/IPoolTakerActions.sol +++ b/src/interfaces/pool/commons/IPoolTakerActions.sol @@ -9,30 +9,30 @@ interface IPoolTakerActions { /** * @notice Called by actors to use quote token to arb higher-priced deposit off the book. - * @param borrower Identifies the loan to liquidate. - * @param depositTake If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. - * @param index Index of a bucket, likely the HPB, in which collateral will be deposited. + * @param borrowerAddress_ Address of the borower take is being called upon. + * @param depositTake_ If `true` then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. + * @param index_ Index of a bucket, likely the `HPB`, in which collateral will be deposited. */ function bucketTake( - address borrower, - bool depositTake, - uint256 index + address borrowerAddress_, + bool depositTake_, + uint256 index_ ) external; /** * @notice Called by actors to purchase collateral from the auction in exchange for quote token. - * @param borrower Address of the borower take is being called upon. - * @param maxAmount Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). - * @param callee Identifies where collateral should be sent and where quote token should be obtained. - * @param data If provided, take will assume the callee implements IERC*Taker. Take will send collateral to - * callee before passing this data to IERC*Taker.atomicSwapCallback. If not provided, - * the callback function will not be invoked. + * @param borrowerAddress_ Address of the borower take is being called upon. + * @param maxAmount_ Max amount of collateral that will be taken from the auction (max number of `NFT`s in case of `ERC721` pool). + * @param callee_ Identifies where collateral should be sent and where quote token should be obtained. + * @param data_ If provided, take will assume the callee implements `IERC*Taker`. Take will send collateral to + * callee before passing this data to `IERC*Taker.atomicSwapCallback`. If not provided, + * the callback function will not be invoked. */ function take( - address borrower, - uint256 maxAmount, - address callee, - bytes calldata data + address borrowerAddress_, + uint256 maxAmount_, + address callee_, + bytes calldata data_ ) external; /***********************/ @@ -40,12 +40,12 @@ interface IPoolTakerActions { /***********************/ /** - * @notice Purchases claimable reserves during a CRA using Ajna token. - * @param maxAmount Maximum amount of quote token to purchase at the current auction price. - * @return amount Actual amount of reserves taken. + * @notice Purchases claimable reserves during a `CRA` using `Ajna` token. + * @param maxAmount_ Maximum amount of quote token to purchase at the current auction price. + * @return amount_ Actual amount of reserves taken. */ function takeReserves( - uint256 maxAmount - ) external returns (uint256 amount); + uint256 maxAmount_ + ) external returns (uint256 amount_); } \ No newline at end of file diff --git a/src/interfaces/pool/erc20/IERC20Pool.sol b/src/interfaces/pool/erc20/IERC20Pool.sol index 76b945e54..b3ddba077 100644 --- a/src/interfaces/pool/erc20/IERC20Pool.sol +++ b/src/interfaces/pool/erc20/IERC20Pool.sol @@ -21,17 +21,17 @@ interface IERC20Pool is /** * @notice Initializes a new pool, setting initial state variables. - * @param rate Initial interest rate of the pool. + * @param rate_ Initial interest rate of the pool (min accepted value 1%, max accepted value 10%). */ - function initialize(uint256 rate) external; + function initialize(uint256 rate_) external; /** * @notice Returns the minimum amount of collateral an actor may have in a bucket. - * @param bucketIndex The bucket index for which the dust limit is desired, or 0 for pledged collateral. - * @return The dust limit for `bucketIndex`. + * @param bucketIndex_ The bucket index for which the dust limit is desired, or `0` for pledged collateral. + * @return The dust limit for `bucketIndex_`. */ function bucketCollateralDust( - uint256 bucketIndex + uint256 bucketIndex_ ) external pure returns (uint256); } diff --git a/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol b/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol index d92a95712..2d5252102 100644 --- a/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol +++ b/src/interfaces/pool/erc20/IERC20PoolBorrowerActions.sol @@ -9,11 +9,10 @@ interface IERC20PoolBorrowerActions { /** * @notice Called by borrowers to add collateral to the pool and/or borrow quote from the pool. - * @dev Can be called by borrowers with either 0 amountToBorrow_ or 0 collateralToPledge_, if borrower only wants to take a single action. - * Call with 0 amountToBorrow_, and non-0 limitIndex_ to restamp loan's neutral price. + * @dev Can be called by borrowers with either `0` `amountToBorrow_` or `0` `collateralToPledge_`, if borrower only wants to take a single action. * @param borrowerAddress_ The borrower to whom collateral was pledged, and/or debt was drawn for. * @param amountToBorrow_ The amount of quote tokens to borrow. - * @param limitIndex_ Lower bound of LUP change (if any) that the borrower will tolerate from a creating or modifying position. + * @param limitIndex_ Lower bound of `LUP` change (if any) that the borrower will tolerate from a creating or modifying position. * @param collateralToPledge_ The amount of collateral to be added to the pool. */ function drawDebt( @@ -25,12 +24,12 @@ interface IERC20PoolBorrowerActions { /** * @notice Called by borrowers to repay borrowed quote to the pool, and/or pull collateral form the pool. - * @dev Can be called by borrowers with either 0 maxQuoteTokenAmountToRepay_ or 0 collateralAmountToPull_, if borrower only wants to take a single action. + * @dev Can be called by borrowers with either `0` `maxQuoteTokenAmountToRepay_` or `0` `collateralAmountToPull_`, if borrower only wants to take a single action. * @param borrowerAddress_ The borrower whose loan is being interacted with. - * @param maxQuoteTokenAmountToRepay_ The amount of quote tokens to repay. - * @param collateralAmountToPull_ The amount of collateral to be puled from the pool. + * @param maxQuoteTokenAmountToRepay_ The max amount of quote tokens to repay. + * @param collateralAmountToPull_ The max amount of collateral to be puled from the pool. * @param recipient_ The address to receive amount of pulled collateral. - * @param limitIndex_ Ensures LUP has not moved far from state when borrower pulls collateral. + * @param limitIndex_ Ensures `LUP` has not moved far from state when borrower pulls collateral. */ function repayDebt( address borrowerAddress_, diff --git a/src/interfaces/pool/erc20/IERC20PoolEvents.sol b/src/interfaces/pool/erc20/IERC20PoolEvents.sol index bcd448d63..2df85d23d 100644 --- a/src/interfaces/pool/erc20/IERC20PoolEvents.sol +++ b/src/interfaces/pool/erc20/IERC20PoolEvents.sol @@ -12,7 +12,7 @@ interface IERC20PoolEvents { * @param actor Recipient that added collateral. * @param index Index at which collateral were added. * @param amount Amount of collateral added to the pool. - * @param lpAwarded Amount of LP awarded for the deposit. + * @param lpAwarded Amount of `LP` awarded for the deposit. */ event AddCollateral( address indexed actor, @@ -26,7 +26,7 @@ interface IERC20PoolEvents { * @param borrower The borrower to whom collateral was pledged, and/or debt was drawn for. * @param amountBorrowed Amount of quote tokens borrowed from the pool. * @param collateralPledged Amount of collateral locked in the pool. - * @param lup LUP after borrow. + * @param lup `LUP` after borrow. */ event DrawDebt( address indexed borrower, diff --git a/src/interfaces/pool/erc20/IERC20PoolFactory.sol b/src/interfaces/pool/erc20/IERC20PoolFactory.sol index de3b49fea..9d2c8c802 100644 --- a/src/interfaces/pool/erc20/IERC20PoolFactory.sol +++ b/src/interfaces/pool/erc20/IERC20PoolFactory.sol @@ -6,7 +6,7 @@ import { IPoolFactory } from '../IPoolFactory.sol'; /** * @title ERC20 Pool Factory - * @dev Used to deploy ERC20 pools. + * @dev Used to deploy `ERC20` pools. */ interface IERC20PoolFactory is IPoolFactory { @@ -16,15 +16,15 @@ interface IERC20PoolFactory is IPoolFactory { /** * @notice Deploys a cloned pool for the given collateral and quote token. - * @dev Pool must not already exist, and must use WETH instead of ETH. - * @param collateral Address of ERC20 collateral token. - * @param quote Address of ERC20 quote token. - * @param interestRate Initial interest rate of the pool. - * @return pool Address of the newly created pool. + * @dev Pool must not already exist, and must use `WETH` instead of `ETH`. + * @param collateral_ Address of `ERC20` collateral token. + * @param quote_ Address of `ERC20` quote token. + * @param interestRate_ Initial interest rate of the pool. + * @return pool_ Address of the newly created pool. */ function deployPool( - address collateral, - address quote, - uint256 interestRate - ) external returns (address pool); + address collateral_, + address quote_, + uint256 interestRate_ + ) external returns (address pool_); } diff --git a/src/interfaces/pool/erc20/IERC20PoolImmutables.sol b/src/interfaces/pool/erc20/IERC20PoolImmutables.sol index d7aa20964..533cee50d 100644 --- a/src/interfaces/pool/erc20/IERC20PoolImmutables.sol +++ b/src/interfaces/pool/erc20/IERC20PoolImmutables.sol @@ -9,7 +9,7 @@ interface IERC20PoolImmutables { /** * @notice Returns the `collateralScale` immutable. - * @return The precision of the collateral ERC-20 token based on decimals. + * @return The precision of the collateral `ERC20` token based on decimals. */ function collateralScale() external view returns (uint256); diff --git a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol index 3d43ea4e0..87f8a3ab6 100644 --- a/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol +++ b/src/interfaces/pool/erc20/IERC20PoolLenderActions.sol @@ -9,14 +9,14 @@ interface IERC20PoolLenderActions { /** * @notice Deposit claimable collateral into a specified bucket. - * @param amount Amount of collateral to deposit. - * @param index The bucket index to which collateral will be deposited. - * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LP changed for the added collateral. + * @param amountToAdd_ Amount of collateral to deposit. + * @param index_ The bucket index to which collateral will be deposited. + * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. + * @return bucketLP_ The amount of `LP` awarded for the added collateral. */ function addCollateral( - uint256 amount, - uint256 index, - uint256 expiry - ) external returns (uint256 lpbChange); + uint256 amountToAdd_, + uint256 index_, + uint256 expiry_ + ) external returns (uint256 bucketLP_); } \ No newline at end of file diff --git a/src/interfaces/pool/erc20/IERC20Taker.sol b/src/interfaces/pool/erc20/IERC20Taker.sol index fa60b2cb7..15826c0ff 100644 --- a/src/interfaces/pool/erc20/IERC20Taker.sol +++ b/src/interfaces/pool/erc20/IERC20Taker.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.14; interface IERC20Taker { /** - * @notice Called by Pool.take allowing a taker to externally swap collateral for quote token. + * @notice Called by `Pool.take` allowing a taker to externally swap collateral for quote token. * @param collateralAmount The denormalized amount of collateral being taken. - * @param quoteAmountDue Denormalized amount of quote token required to purchase collateralAmount at the + * @param quoteAmountDue Denormalized amount of quote token required to purchase `collateralAmount` at the * current auction price. * @param data Taker-provided calldata passed from taker's invocation to their callback. */ diff --git a/src/interfaces/pool/erc721/IERC721Pool.sol b/src/interfaces/pool/erc721/IERC721Pool.sol index edc8721d6..dd4f9e7c4 100644 --- a/src/interfaces/pool/erc721/IERC721Pool.sol +++ b/src/interfaces/pool/erc721/IERC721Pool.sol @@ -26,12 +26,12 @@ interface IERC721Pool is /** * @notice Initializes a new pool, setting initial state variables. - * @param tokenIds Enumerates tokenIds to be allowed in the pool. - * @param rate Initial interest rate of the pool. + * @param tokenIds_ Enumerates `tokenIds_` to be allowed in the pool. + * @param rate_ Initial interest rate of the pool. */ function initialize( - uint256[] memory tokenIds, - uint256 rate + uint256[] memory tokenIds_, + uint256 rate_ ) external; } diff --git a/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol b/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol index 9206c9b5e..9d4e1ac1d 100644 --- a/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol +++ b/src/interfaces/pool/erc721/IERC721PoolBorrowerActions.sol @@ -9,12 +9,11 @@ interface IERC721PoolBorrowerActions { /** * @notice Called by borrowers to add collateral to the pool and/or borrow quote from the pool. - * @dev Can be called by borrowers with either 0 amountToBorrow_ or 0 collateralToPledge_, if borrower only wants to take a single action. - * Call with 0 amountToBorrow_, and non-0 limitIndex_ to restamp loan's neutral price. + * @dev Can be called by borrowers with either `0` `amountToBorrow_` or `0` `collateralToPledge`_, if borrower only wants to take a single action. * @param borrower_ The address of borrower to drawDebt for. * @param amountToBorrow_ The amount of quote tokens to borrow. - * @param limitIndex_ Lower bound of LUP change (if any) that the borrower will tolerate from a creating or modifying position. - * @param tokenIdsToPledge_ Array of tokenIds to be pledged to the pool. + * @param limitIndex_ Lower bound of `LUP` change (if any) that the borrower will tolerate from a creating or modifying position. + * @param tokenIdsToPledge_ Array of token ids to be pledged to the pool. */ function drawDebt( address borrower_, @@ -25,12 +24,12 @@ interface IERC721PoolBorrowerActions { /** * @notice Called by borrowers to repay borrowed quote to the pool, and/or pull collateral form the pool. - * @dev Can be called by borrowers with either 0 maxQuoteTokenAmountToRepay_ or 0 collateralAmountToPull_, if borrower only wants to take a single action. + * @dev Can be called by borrowers with either `0` `maxQuoteTokenAmountToRepay_` or `0` `collateralAmountToPull_`, if borrower only wants to take a single action. * @param borrowerAddress_ The borrower whose loan is being interacted with. - * @param maxQuoteTokenAmountToRepay_ The amount of quote tokens to repay. - * @param noOfNFTsToPull_ The integer number of NFT collateral to be puled from the pool. + * @param maxQuoteTokenAmountToRepay_ The max amount of quote tokens to repay. + * @param noOfNFTsToPull_ The integer number of `NFT` collateral to be puled from the pool. * @param recipient_ The address to receive amount of pulled collateral. - * @param limitIndex_ Ensures LUP has not moved far from state when borrower pulls collateral. + * @param limitIndex_ Ensures `LUP` has not moved far from state when borrower pulls collateral. */ function repayDebt( address borrowerAddress_, diff --git a/src/interfaces/pool/erc721/IERC721PoolErrors.sol b/src/interfaces/pool/erc721/IERC721PoolErrors.sol index 5f9db4792..63ac82dd4 100644 --- a/src/interfaces/pool/erc721/IERC721PoolErrors.sol +++ b/src/interfaces/pool/erc721/IERC721PoolErrors.sol @@ -8,7 +8,7 @@ pragma solidity 0.8.14; interface IERC721PoolErrors { /** - * @notice User attempted to add an NFT to the pool with a tokenId outsde of the allowed subset. + * @notice User attempted to add an `NFT` to the pool with a `tokenId` outside of the allowed subset. */ error OnlySubset(); } \ No newline at end of file diff --git a/src/interfaces/pool/erc721/IERC721PoolEvents.sol b/src/interfaces/pool/erc721/IERC721PoolEvents.sol index 123e86a3c..f963650f4 100644 --- a/src/interfaces/pool/erc721/IERC721PoolEvents.sol +++ b/src/interfaces/pool/erc721/IERC721PoolEvents.sol @@ -34,7 +34,7 @@ interface IERC721PoolEvents { ); /** - * @notice Emitted when borrower draws debt from the pool, or adds collateral to the pool. + * @notice Emitted when borrower draws debt from the pool or adds collateral to the pool. * @param borrower `msg.sender`. * @param amountBorrowed Amount of quote tokens borrowed from the pool. * @param tokenIdsPledged Array of tokenIds to be added to the pool. diff --git a/src/interfaces/pool/erc721/IERC721PoolFactory.sol b/src/interfaces/pool/erc721/IERC721PoolFactory.sol index 30d842cbf..605ef9b85 100644 --- a/src/interfaces/pool/erc721/IERC721PoolFactory.sol +++ b/src/interfaces/pool/erc721/IERC721PoolFactory.sol @@ -15,7 +15,7 @@ interface IERC721PoolFactory is IPoolFactory { /**************/ /** - * @notice User tried to deploy a pool with an array of tokenIds that weren't sorted, or contained duplicates. + * @notice User tried to deploy a pool with an array of `tokenIds` that weren't sorted, or contained duplicates. */ error TokenIdSubsetInvalid(); @@ -25,22 +25,22 @@ interface IERC721PoolFactory is IPoolFactory { /** * @notice Deploys a cloned pool for the given collateral and quote token. - * @dev Pool must not already exist, and must use WETH instead of ETH. - * @param collateral Address of NFT collateral token. - * @param quote Address of NFT quote token. - * @param tokenIds Ids of subset NFT tokens. - * @param interestRate Initial interest rate of the pool. - * @return pool Address of the newly created pool. + * @dev Pool must not already exist, and must use `WETH` instead of `ETH`. + * @param collateral_ Address of `NFT` collateral token. + * @param quote_ Address of `NFT` quote token. + * @param tokenIds_ Ids of subset `NFT` tokens. + * @param interestRate_ Initial interest rate of the pool. + * @return pool_ Address of the newly created pool. */ function deployPool( - address collateral, - address quote, - uint256[] memory tokenIds, - uint256 interestRate - ) external returns (address pool); + address collateral_, + address quote_, + uint256[] memory tokenIds_, + uint256 interestRate_ + ) external returns (address pool_); /** - * @notice User attempted to make pool with non supported NFT contract as collateral. + * @notice User attempted to make pool with non supported `NFT` contract as collateral. */ error NFTNotSupported(); } diff --git a/src/interfaces/pool/erc721/IERC721PoolImmutables.sol b/src/interfaces/pool/erc721/IERC721PoolImmutables.sol index 53b74ae89..df91b9907 100644 --- a/src/interfaces/pool/erc721/IERC721PoolImmutables.sol +++ b/src/interfaces/pool/erc721/IERC721PoolImmutables.sol @@ -8,8 +8,8 @@ pragma solidity 0.8.14; interface IERC721PoolImmutables{ /** - * @notice Returns the type of NFT pool. - * @return True if NTF pool is a subset pool. + * @notice Returns the type of `NFT` pool. + * @return `True` if `NTF` pool is a subset pool. */ function isSubset() external view returns (bool); diff --git a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol index c9f5119aa..260d3c680 100644 --- a/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol +++ b/src/interfaces/pool/erc721/IERC721PoolLenderActions.sol @@ -9,28 +9,28 @@ interface IERC721PoolLenderActions { /** * @notice Deposit claimable collateral into a specified bucket. - * @param tokenIds Array of collateral to deposit. - * @param index The bucket index to which collateral will be deposited. - * @param expiry Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. - * @return lpbChange The amount of LP changed for the added collateral. + * @param tokenIds_ Array of token ids to deposit. + * @param index_ The bucket index to which collateral will be deposited. + * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. + * @return bucketLP_ The amount of `LP `changed for the added collateral. */ function addCollateral( - uint256[] calldata tokenIds, - uint256 index, - uint256 expiry - ) external returns (uint256); + uint256[] calldata tokenIds_, + uint256 index_, + uint256 expiry_ + ) external returns (uint256 bucketLP_); /** - * @notice Merge collateral accross a number of buckets, removeAmountAtIndex_to reconstitute an NFT - * @param removeAmountAtIndex_ Array of bucket indexes to remove all collateral that the caller has ownership over. - * @param toIndex_ The bucket index to which merge collateral into. - * @param noOfNFTsToRemove_ Intergral number of NFTs to remove if collateral amount is met noOfNFTsToRemove_, else merge at bucket index, toIndex_. - * @return collateralMerged_ Amount of collateral merged into toIndex. - * @return bucketLPs_ If non-zero, amount of LP in toIndex when collateral is merged into bucket. If 0, no collateral is merged. + * @notice Merge collateral accross a number of buckets, `removalIndexes_` reconstitute an `NFT`. + * @param removalIndexes_ Array of bucket indexes to remove all collateral that the caller has ownership over. + * @param noOfNFTsToRemove_ Intergral number of `NFT`s to remove if collateral amount is met `noOfNFTsToRemove_`, else merge at bucket index, `toIndex_`. + * @param toIndex_ The bucket index to which merge collateral into. + * @return collateralMerged_ Amount of collateral merged into `toIndex_`. + * @return bucketLP_ If non-zero, amount of `LP` in `toIndex_` when collateral is merged into bucket. If `0`, no collateral is merged. */ function mergeOrRemoveCollateral( - uint256[] calldata removeAmountAtIndex_, + uint256[] calldata removalIndexes_, uint256 noOfNFTsToRemove_, uint256 toIndex_ - ) external returns (uint256 collateralMerged_, uint256 bucketLPs_); + ) external returns (uint256 collateralMerged_, uint256 bucketLP_); } \ No newline at end of file diff --git a/src/interfaces/pool/erc721/IERC721PoolState.sol b/src/interfaces/pool/erc721/IERC721PoolState.sol index 4bc028a82..fd174fff5 100644 --- a/src/interfaces/pool/erc721/IERC721PoolState.sol +++ b/src/interfaces/pool/erc721/IERC721PoolState.sol @@ -10,17 +10,17 @@ interface IERC721PoolState { /** * @notice Check if a token id is allowed as collateral in pool. * @param tokenId The token id to check. - * @return allowed True if token id is allowed in pool + * @return allowed `True` if token id is allowed in pool. */ function tokenIdsAllowed( uint256 tokenId ) external view returns (bool allowed); /** - * @notice Returns the token id of an NFT pledged by a borrower with a given index. - * @param borrower The address of borrower that pledged the NFT. - * @param nftIndex NFT index in borrower's pledged token ids array. - * @return tokenId Token id of the NFT. + * @notice Returns the token id of an `NFT` pledged by a borrower with a given index. + * @param borrower The address of borrower that pledged the `NFT`. + * @param nftIndex `NFT` index in borrower's pledged token ids array. + * @return tokenId Token id of the `NFT`. */ function borrowerTokenIds( address borrower, @@ -28,26 +28,26 @@ interface IERC721PoolState { ) external view returns (uint256 tokenId); /** - * @notice Returns the token id of an NFT added in pool bucket. - * @param nftIndex NFT index in bucket's token ids array. - * @return tokenId Token id of the NFT. + * @notice Returns the token id of an `NFT`added in pool bucket (claimable from pool). + * @param nftIndex `NFT` index in bucket's token ids array. + * @return tokenId Token id of the `NFT`. */ function bucketTokenIds( uint256 nftIndex ) external view returns (uint256 tokenId); /** - * @notice Returns the total NFT pledged by a borrower. - * @param borrower The address of borrower that pledged the NFT. - * @return tokens Total borrower NFTs. + * @notice Returns the total `NFT` pledged by a borrower. + * @param borrower_ The address of borrower that pledged the `NFT`. + * @return Total number of `NFT`s pledged by borrower. */ function totalBorrowerTokens( - address borrower - ) external view returns (uint256 tokens); + address borrower_ + ) external view returns (uint256); /** - * @notice Returns the total NFT added in pool bucket. - * @return tokens Total NFT in bucket. + * @notice Returns the total `NFT` added in pool bucket. + * @return Total number of `NFT`s in buckets (claimable from pool). */ - function totalBucketTokens() external view returns (uint256 tokens); + function totalBucketTokens() external view returns (uint256); } \ No newline at end of file diff --git a/src/interfaces/pool/erc721/IERC721Taker.sol b/src/interfaces/pool/erc721/IERC721Taker.sol index 486684116..9e1a68090 100644 --- a/src/interfaces/pool/erc721/IERC721Taker.sol +++ b/src/interfaces/pool/erc721/IERC721Taker.sol @@ -4,9 +4,9 @@ pragma solidity 0.8.14; interface IERC721Taker { /** - * @notice Called by Pool.take allowing a taker to externally swap collateral for quote token. - * @param tokenIds Identifies the NFTs being taken. - * @param quoteAmountDue Denormalized amount of quote token required to purchase collateralAmount at the + * @notice Called by `Pool.take` allowing a taker to externally swap collateral for quote token. + * @param tokenIds Identifies the `NFT`s being taken. + * @param quoteAmountDue Denormalized amount of quote token required to purchase `collateralAmount` at the * current auction price. * @param data Taker-provided calldata passed from taker's invocation to their callback. */ diff --git a/src/interfaces/position/IPositionManagerDerivedState.sol b/src/interfaces/position/IPositionManagerDerivedState.sol index 7cc95761a..7119646f7 100644 --- a/src/interfaces/position/IPositionManagerDerivedState.sol +++ b/src/interfaces/position/IPositionManagerDerivedState.sol @@ -8,42 +8,42 @@ pragma solidity 0.8.14; interface IPositionManagerDerivedState { /** - * @notice Returns the LP accrued to a given tokenId, bucket pairing. + * @notice Returns the `LP` accrued to a given `tokenId`, bucket pairing. * @dev Nested mappings aren't returned normally as part of the default getter for a mapping. - * @param tokenId Unique ID of token. - * @param index Index of bucket to check LP balance of. - * @return lps Balance of lps in the bucket for this position. + * @param tokenId_ Unique `ID` of token. + * @param index_ Index of bucket to check `LP` balance of. + * @return lp_ Balance of `LP` in the bucket for this position. */ function getLP( - uint256 tokenId, - uint256 index - ) external view returns (uint256 lps); + uint256 tokenId_, + uint256 index_ + ) external view returns (uint256 lp_); /** - * @notice Returns an array of bucket indexes in which an NFT has liquidity. + * @notice Returns an array of bucket indexes in which an `NFT` has liquidity. * @dev Potentially includes buckets that have been bankrupted. - * @param tokenId Unique ID of token. + * @param tokenId_ Unique `ID` of token. * @return Array of bucket indexes. */ function getPositionIndexes( - uint256 tokenId + uint256 tokenId_ ) external view returns (uint256[] memory); /** - * @notice Returns an array of bucket indexes in which an NFT has liquidity, with bankrupt buckets removed. - * @param tokenId Unique ID of token. + * @notice Returns an array of bucket indexes in which an `NFT` has liquidity, with bankrupt buckets removed. + * @param tokenId_ Unique `ID` of token. * @return Array of bucket indexes filtered for active liquidity. */ function getPositionIndexesFiltered( - uint256 tokenId + uint256 tokenId_ ) external view returns (uint256[] memory); /** - * @notice Returns information about a given NFT. - * @param tokenId_ Unique ID of token. + * @notice Returns information about a given `NFT`. + * @param tokenId_ Unique `ID` of token. * @param index_ Bucket index to check for position information. - * @return lps in that bucket. - * @return position's deposit time. + * @return `LP` in bucket. + * @return Position's deposit time. */ function getPositionInfo( uint256 tokenId_, @@ -52,15 +52,15 @@ interface IPositionManagerDerivedState { /** - * @notice Checks if a given tokenId has a given position bucket - * @param tokenId Unique ID of token. - * @param index Index of bucket to check if in position buckets. - * @return bucketInPosition True if tokenId has the position bucket. + * @notice Checks if a given `tokenId` has a given position bucket + * @param tokenId_ Unique `ID` of token. + * @param index_ Index of bucket to check if in position buckets. + * @return bucketInPosition_ `True` if tokenId has the position bucket. */ function isIndexInPosition( - uint256 tokenId, - uint256 index - ) external view returns (bool bucketInPosition); + uint256 tokenId_, + uint256 index_ + ) external view returns (bool bucketInPosition_); /** * @notice Checks if a tokenId has a position in a bucket that was bankrupted. diff --git a/src/interfaces/position/IPositionManagerErrors.sol b/src/interfaces/position/IPositionManagerErrors.sol index adc5077ef..dcfa90877 100644 --- a/src/interfaces/position/IPositionManagerErrors.sol +++ b/src/interfaces/position/IPositionManagerErrors.sol @@ -8,32 +8,32 @@ pragma solidity 0.8.14; interface IPositionManagerErrors { /** - * @notice User attempting to utilize LPB from a bankrupt bucket. + * @notice User attempting to utilize `LP` from a bankrupt bucket. */ error BucketBankrupt(); /** - * @notice User attempting to burn a LPB NFT before removing liquidity. + * @notice User attempting to burn a `LP` `NFT` before removing liquidity. */ error LiquidityNotRemoved(); /** - * @notice User not authorized to interact with the specified NFT. + * @notice User not authorized to interact with the specified `NFT`. */ error NoAuth(); /** - * @notice User attempted to mint an NFT pointing to a pool that wasn't deployed by an Ajna factory. + * @notice User attempted to mint an `NFT` pointing to a pool that wasn't deployed by an `Ajna` factory. */ error NotAjnaPool(); /** - * @notice User failed to remove position from their NFT. + * @notice User failed to remove position from their `NFT`. */ error RemovePositionFailed(); /** - * @notice User attempting to interact with a pool that doesn't match the pool associated with the tokenId. + * @notice User attempting to interact with a pool that doesn't match the pool associated with the `tokenId`. */ error WrongPool(); } \ No newline at end of file diff --git a/src/interfaces/position/IPositionManagerEvents.sol b/src/interfaces/position/IPositionManagerEvents.sol index f8ebe89e7..e74458ec2 100644 --- a/src/interfaces/position/IPositionManagerEvents.sol +++ b/src/interfaces/position/IPositionManagerEvents.sol @@ -8,9 +8,9 @@ pragma solidity 0.8.14; interface IPositionManagerEvents { /** - * @notice Emitted when an existing NFT was burned. + * @notice Emitted when an existing `NFT` was burned. * @param lender Lender address. - * @param tokenId The token id of the NFT that was burned. + * @param tokenId The token id of the `NFT` that was burned. */ event Burn( address indexed lender, @@ -18,8 +18,8 @@ interface IPositionManagerEvents { ); /** - * @notice Emitted when existing positions were memorialized for a given NFT. - * @param tokenId The tokenId of the NFT. + * @notice Emitted when existing positions were memorialized for a given `NFT`. + * @param tokenId The `tokenId` of the `NFT`. * @param indexes Bucket indexes of memorialized positions. */ event MemorializePosition( @@ -29,10 +29,10 @@ interface IPositionManagerEvents { ); /** - * @notice Emitted when representative NFT minted. + * @notice Emitted when representative `NFT` minted. * @param lender Lender address. * @param pool Pool address. - * @param tokenId The tokenId of the newly minted NFT. + * @param tokenId The `tokenId` of the newly minted `NFT`. */ event Mint( address indexed lender, @@ -43,11 +43,11 @@ interface IPositionManagerEvents { /** * @notice Emitted when a position's liquidity is moved between buckets. * @param lender Lender address. - * @param tokenId The tokenId of the newly minted NFT. + * @param tokenId The `tokenId` of the newly minted `NFT`. * @param fromIndex Index of bucket from where liquidity is moved. * @param toIndex Index of bucket where liquidity is moved. - * @param lpRedeemedFrom Amount of LP removed from the `from` bucket. - * @param lpAwardedTo Amount of LP credited to the `to` bucket. + * @param lpRedeemedFrom Amount of `LP` removed from the `from` bucket. + * @param lpAwardedTo Amount of `LP` credited to the `to` bucket. */ event MoveLiquidity( address indexed lender, @@ -59,8 +59,8 @@ interface IPositionManagerEvents { ); /** - * @notice Emitted when existing positions were redeemed for a given NFT. - * @param tokenId The tokenId of the NFT. + * @notice Emitted when existing positions were redeemed for a given `NFT`. + * @param tokenId The `tokenId` of the `NFT`. * @param indexes Bucket indexes of redeemed positions. */ event RedeemPosition( diff --git a/src/interfaces/position/IPositionManagerOwnerActions.sol b/src/interfaces/position/IPositionManagerOwnerActions.sol index 7138f811b..ebeeb305f 100644 --- a/src/interfaces/position/IPositionManagerOwnerActions.sol +++ b/src/interfaces/position/IPositionManagerOwnerActions.sol @@ -8,12 +8,12 @@ pragma solidity 0.8.14; interface IPositionManagerOwnerActions { /** - * @notice Called by owners to burn an existing NFT. - * @dev Requires that all lps have been removed from the NFT prior to calling. - * @param params Calldata struct supplying inputs required to update the underlying assets owed to an NFT. + * @notice Called by owners to burn an existing `NFT`. + * @dev Requires that all `LP` have been removed from the `NFT `prior to calling. + * @param params_ Calldata struct supplying inputs required to update the underlying assets owed to an `NFT`. */ function burn( - BurnParams calldata params + BurnParams calldata params_ ) external; /** @@ -22,40 +22,40 @@ interface IPositionManagerOwnerActions { * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. * @dev An additional call is made to the pool to transfer the LP from their previous owner, to the Position Manager. * @dev `Pool.increaseLPAllowance` must be called prior to calling this method in order to allow Position manager contract to transfer LP to be memorialized. - * @param params Calldata struct supplying inputs required to conduct the memorialization. + * @param params_ Calldata struct supplying inputs required to conduct the memorialization. */ function memorializePositions( - MemorializePositionsParams calldata params + MemorializePositionsParams calldata params_ ) external; /** - * @notice Called by owners to mint and receive an Ajna Position NFT. - * @dev PositionNFTs can only be minited with an association to pools that have been deployed by the Ajna ERC20PoolFactory or ERC721PoolFactory. - * @param params Calldata struct supplying inputs required to mint a positions NFT. - * @return tokenId The tokenId of the newly minted NFT. + * @notice Called by owners to mint and receive an `Ajna` Position `NFT`. + * @dev Position `NFT`s can only be minited with an association to pools that have been deployed by the `Ajna` `ERC20PoolFactory` or `ERC721PoolFactory`. + * @param params_ Calldata struct supplying inputs required to mint a positions `NFT`. + * @return tokenId_ The `tokenId` of the newly minted `NFT`. */ function mint( - MintParams calldata params - ) external returns (uint256 tokenId); + MintParams calldata params_ + ) external returns (uint256 tokenId_); /** * @notice Called by owners to move liquidity between two buckets. - * @param params Calldata struct supplying inputs required to move liquidity tokens. + * @param params_ Calldata struct supplying inputs required to move liquidity tokens. */ function moveLiquidity( - MoveLiquidityParams calldata params + MoveLiquidityParams calldata params_ ) external; /** - * @notice Called to reedem existing positions with a given NFT. + * @notice Called to reedem existing positions with a given `NFT`. * @dev The array of buckets is expected to be constructed off chain by scanning events for that lender. - * @dev The NFT must have already been created, and the number of buckets to be memorialized at a time determined by function caller. - * @dev An additional call is made to the pool to transfer the LP Position Manager to owner. - * @dev `Pool.approveLPTransferors` must be called prior to calling this method in order to allow Position manager contract to transfer redeemed LP. - * @param params Calldata struct supplying inputs required to conduct the redeem. + * @dev The `NFT` must have already been created, and the number of buckets to be memorialized at a time determined by function caller. + * @dev An additional call is made to the pool to transfer the `LP` Position Manager to owner. + * @dev `Pool.approveLPTransferors` must be called prior to calling this method in order to allow `Position manager` contract to transfer redeemed `LP`. + * @param params_ Calldata struct supplying inputs required to conduct the redeem. */ function reedemPositions( - RedeemPositionsParams calldata params + RedeemPositionsParams calldata params_ ) external; /*********************/ @@ -63,7 +63,7 @@ interface IPositionManagerOwnerActions { /*********************/ /** - * @notice Struct holding parameters for burning an NFT. + * @notice Struct holding parameters for burning an `NFT`. */ struct BurnParams { uint256 tokenId; // The tokenId of the positions NFT to burn diff --git a/src/interfaces/position/IPositionManagerState.sol b/src/interfaces/position/IPositionManagerState.sol index 364e00bbb..26e669a4e 100644 --- a/src/interfaces/position/IPositionManagerState.sol +++ b/src/interfaces/position/IPositionManagerState.sol @@ -8,15 +8,16 @@ pragma solidity 0.8.14; interface IPositionManagerState { /** - * @notice Returns the pool address associated with a positions NFT. - * @param tokenId The token id of the positions NFT. - * @return Pool address associated with the NFT. + * @notice Returns the pool address associated with a positions `NFT`. + * @param tokenId_ The token id of the positions `NFT`. + * @return Pool address associated with the `NFT`. */ function poolKey( - uint256 tokenId + uint256 tokenId_ ) external view returns (address); } +/// @dev Struct holding Position `LP` state. struct Position { uint256 lps; // [WAD] position LP uint256 depositTime; // deposit time for position diff --git a/src/interfaces/rewards/IRewardsManagerDerivedState.sol b/src/interfaces/rewards/IRewardsManagerDerivedState.sol index 2e1a53e8a..ab360f44f 100644 --- a/src/interfaces/rewards/IRewardsManagerDerivedState.sol +++ b/src/interfaces/rewards/IRewardsManagerDerivedState.sol @@ -8,14 +8,14 @@ pragma solidity 0.8.14; interface IRewardsManagerDerivedState { /** - * @notice Calculate the amount of rewards that have been accumulated by a staked NFT. - * @param tokenId ID of the staked LP NFT. - * @param claimEpoch The end burn epoch to calculate rewards for (rewards calculation starts from the last claimed epoch). - * @return rewards_ The amount of rewards earned by the NFT. + * @notice Calculate the amount of rewards that have been accumulated by a staked `NFT`. + * @param tokenId_ `ID` of the staked `LP` `NFT`. + * @param epochToClaim_ The end burn epoch to calculate rewards for (rewards calculation starts from the last claimed epoch). + * @return The amount of rewards earned by the staked `NFT`. */ function calculateRewards( - uint256 tokenId, - uint256 claimEpoch + uint256 tokenId_, + uint256 epochToClaim_ ) external view returns (uint256); } diff --git a/src/interfaces/rewards/IRewardsManagerErrors.sol b/src/interfaces/rewards/IRewardsManagerErrors.sol index dbfa58320..591af6c5f 100644 --- a/src/interfaces/rewards/IRewardsManagerErrors.sol +++ b/src/interfaces/rewards/IRewardsManagerErrors.sol @@ -22,12 +22,12 @@ interface IRewardsManagerErrors { error MoveStakedLiquidityInvalid(); /** - * @notice User attempted to interact with an NFT they aren't the owner of. + * @notice User attempted to interact with an `NFT` they aren't the owner of. */ error NotOwnerOfDeposit(); /** - * @notice Can't deploy with Ajna token address 0x0 address. + * @notice Can't deploy with `Ajna` token address `0x` address. */ error DeployWithZeroAddress(); } \ No newline at end of file diff --git a/src/interfaces/rewards/IRewardsManagerEvents.sol b/src/interfaces/rewards/IRewardsManagerEvents.sol index f1607eb4b..04dc2e403 100644 --- a/src/interfaces/rewards/IRewardsManagerEvents.sol +++ b/src/interfaces/rewards/IRewardsManagerEvents.sol @@ -8,12 +8,12 @@ pragma solidity 0.8.14; interface IRewardsManagerEvents { /** - * @notice Emitted when lender claims rewards that have accrued to their staked NFT. - * @param owner Owner of the staked NFT. - * @param ajnaPool Address of the Ajna pool the NFT corresponds to. - * @param tokenId ID of the staked NFT. + * @notice Emitted when lender claims rewards that have accrued to their staked `NFT`. + * @param owner Owner of the staked `NFT`. + * @param ajnaPool Address of the `Ajna` pool the `NFT` corresponds to. + * @param tokenId `ID` of the staked `NFT`. * @param epochsClaimed Array of burn epochs claimed. - * @param amount The amount of AJNA tokens claimed by the staker. + * @param amount The amount of `Ajna` tokens claimed by the staker. */ event ClaimRewards( address indexed owner, @@ -24,8 +24,8 @@ interface IRewardsManagerEvents { ); /** - * @notice Emitted when moves liquidity in a staked NFT between buckets. - * @param tokenId ID of the staked NFT. + * @notice Emitted when moves liquidity in a staked `NFT` between buckets. + * @param tokenId `ID` of the staked `NFT`. * @param fromIndexes Array of indexes from which liquidity was moved. * @param toIndexes Array of indexes to which liquidity was moved. */ @@ -36,10 +36,10 @@ interface IRewardsManagerEvents { ); /** - * @notice Emitted when lender stakes their LP NFT in the rewards contract. - * @param owner Owner of the staked NFT. - * @param ajnaPool Address of the Ajna pool the NFT corresponds to. - * @param tokenId ID of the staked NFT. + * @notice Emitted when lender stakes their `LP` `NFT` in the rewards contract. + * @param owner Owner of the staked `NFT`. + * @param ajnaPool Address of the `Ajna` pool the `NFT` corresponds to. + * @param tokenId `ID` of the staked `NFT`. */ event Stake( address indexed owner, @@ -50,9 +50,9 @@ interface IRewardsManagerEvents { /** * @notice Emitted when someone records the latest exchange rate for a bucket in a pool, and claims the associated reward. * @param caller Address of the recorder. The address which will receive an update reward, if applicable. - * @param ajnaPool Address of the Ajna pool whose exchange rates are being updated. + * @param ajnaPool Address of the `Ajna` pool whose exchange rates are being updated. * @param indexesUpdated Array of bucket indexes whose exchange rates are being updated. - * @param rewardsClaimed Amount of ajna tokens claimed by the recorder as a reward for updating each bucket index. + * @param rewardsClaimed Amount of `Ajna` tokens claimed by the recorder as a reward for updating each bucket index. */ event UpdateExchangeRates( address indexed caller, @@ -62,10 +62,10 @@ interface IRewardsManagerEvents { ); /** - * @notice Emitted when lender withdraws their LP NFT from the rewards contract. - * @param owner Owner of the staked NFT. - * @param ajnaPool Address of the Ajna pool the NFT corresponds to. - * @param tokenId ID of the staked NFT. + * @notice Emitted when lender withdraws their `LP` `NFT` from the rewards contract. + * @param owner Owner of the staked `NFT`. + * @param ajnaPool Address of the `Ajna` pool the `NFT` corresponds to. + * @param tokenId `ID` of the staked `NFT`. */ event Unstake( address indexed owner, diff --git a/src/interfaces/rewards/IRewardsManagerOwnerActions.sol b/src/interfaces/rewards/IRewardsManagerOwnerActions.sol index fd378b0a5..00cbda95c 100644 --- a/src/interfaces/rewards/IRewardsManagerOwnerActions.sol +++ b/src/interfaces/rewards/IRewardsManagerOwnerActions.sol @@ -8,25 +8,25 @@ pragma solidity 0.8.14; interface IRewardsManagerOwnerActions { /** - * @notice Claim ajna token rewards that have accrued to a staked LP NFT. - * @dev Updates exchange rates for each bucket the NFT is associated with. - * @param tokenId ID of the staked LP NFT. - * @param claimEpoch The burn epoch to claim rewards for. + * @notice Claim `Ajna` token rewards that have accrued to a staked `LP` `NFT`. + * @dev Updates exchange rates for each bucket the `NFT` is associated with. + * @param tokenId_ `ID` of the staked `LP` `NFT`. + * @param epochToClaim_ The burn epoch to claim rewards for. */ function claimRewards( - uint256 tokenId, - uint256 claimEpoch + uint256 tokenId_, + uint256 epochToClaim_ ) external; /** - * @notice Moves liquidity in a staked NFT between buckets. - * @dev Calls out to PositionManager.moveLiquidity(). - * @dev Automatically claims any available rewards in all existing buckets. Updates exchange rates for each new bucket the NFT is associated with. - * @dev fromBuckets and toBuckets must be the same array length. Liquidity is moved from the fromBuckets to the toBuckets in the same index. - * @param tokenId_ ID of the staked LP NFT. + * @notice Moves liquidity in a staked `NFT` between buckets. + * @dev Calls out to `PositionManager.moveLiquidity()`. + * @dev Automatically claims any available rewards in all existing buckets. Updates exchange rates for each new bucket the `NFT` is associated with. + * @dev `fromBuckets_` and `toBuckets_` must be the same array length. Liquidity is moved from the `fromBuckets_` to the `toBuckets_` in the same index. + * @param tokenId_ `ID` of the staked `LP` `NFT`. * @param fromBuckets_ The list of bucket indexes to move liquidity from. * @param toBuckets_ The list of bucket indexes to move liquidity to. - * @param expiry_ Timestamp after which this TX will revert, preventing inclusion in a block with unfavorable price. + * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. */ function moveStakedLiquidity( uint256 tokenId_, @@ -36,33 +36,33 @@ interface IRewardsManagerOwnerActions { ) external; /** - * @notice Stake a LP NFT into the rewards contract. - * @dev Updates exchange rates for each bucket the NFT is associated with. - * @param tokenId ID of the LP NFT to stake in the AjnaRewards contract. + * @notice Stake a `LP` `NFT` into the rewards contract. + * @dev Updates exchange rates for each bucket the `NFT` is associated with. + * @param tokenId_ `ID` of the `LP` `NFT` to stake in the `Rewards contract. */ function stake( - uint256 tokenId + uint256 tokenId_ ) external; /** - * @notice Withdraw a staked LP NFT from the rewards contract. + * @notice Withdraw a staked `LP` `NFT` from the rewards contract. * @notice If rewards are available, claim all available rewards before withdrawal. - * @param tokenId ID of the staked LP NFT. + * @param tokenId_ `ID` of the staked `LP` `NFT`. */ function unstake( - uint256 tokenId + uint256 tokenId_ ) external; /** * @notice Update the exchange rate of a list of buckets. - * @dev Caller can claim 5% of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated. - * @param pool Address of the pool whose exchange rates are being updated. - * @param indexes List of bucket indexes to be updated. + * @dev Caller can claim `5%` of the rewards that have accumulated to each bucket since the last burn event, if it hasn't already been updated. + * @param pool_ Address of the pool whose exchange rates are being updated. + * @param indexes_ List of bucket indexes to be updated. * @return Returns reward amount for updating bucket exchange rates. */ function updateBucketExchangeRatesAndClaim( - address pool, - uint256[] calldata indexes + address pool_, + uint256[] calldata indexes_ ) external returns (uint256); } \ No newline at end of file diff --git a/src/interfaces/rewards/IRewardsManagerState.sol b/src/interfaces/rewards/IRewardsManagerState.sol index 061b8cd64..a933f5304 100644 --- a/src/interfaces/rewards/IRewardsManagerState.sol +++ b/src/interfaces/rewards/IRewardsManagerState.sol @@ -9,54 +9,54 @@ interface IRewardsManagerState { /** * @notice Track whether a depositor has claimed rewards for a given burn event epoch. - * @param tokenId ID of the staked LP NFT. - * @param epoch The burn epoch to track if rewards were claimed. - * @return True if rewards were claimed for the given epoch, else false. + * @param tokenId_ ID of the staked `LP` `NFT`. + * @param epoch_ The burn epoch to track if rewards were claimed. + * @return `True` if rewards were claimed for the given epoch, else false. */ function isEpochClaimed( - uint256 tokenId, - uint256 epoch + uint256 tokenId_, + uint256 epoch_ ) external view returns (bool); /** * @notice Track the total amount of rewards that have been claimed for a given epoch. - * @param epoch The burn epoch to track if rewards were claimed. + * @param epoch_ The burn epoch to track if rewards were claimed. * @return The amount of rewards claimed in given epoch. */ function rewardsClaimed( - uint256 epoch + uint256 epoch_ ) external view returns (uint256); /** * @notice Track the total amount of rewards that have been claimed for a given burn event's bucket updates. - * @param epoch The burn epoch to track if rewards were claimed. + * @param epoch_ The burn epoch to track if rewards were claimed. * @return The amount of update rewards claimed in given epoch. */ function updateRewardsClaimed( - uint256 epoch + uint256 epoch_ ) external view returns (uint256); /** * @notice Retrieve information about a given stake. - * @param tokenId ID of the NFT staked in the rewards contract to retrieve information about. - * @return The owner of a given NFT stake. - * @return The Pool the NFT represents positions in. - * @return The last burn epoch in which the owner of the NFT claimed rewards. + * @param tokenId_ `ID` of the `NFT` staked in the rewards contract to retrieve information about. + * @return owner_ The owner of a given `NFT` stake. + * @return pool_ The `Pool` the `NFT` represents positions in. + * @return lastClaimedEpoch_ The last burn epoch in which the owner of the `NFT` claimed rewards. */ function getStakeInfo( - uint256 tokenId - ) external view returns (address, address, uint256); + uint256 tokenId_ + ) external view returns (address owner_, address pool_, uint256 lastClaimedEpoch_); /** - * @notice Retrieve information about recorded LP and rate values for a given bucket and a given stake, at stake time. - * @param tokenId ID of the NFT staked in the rewards contract to retrieve information about. - * @param bucketId ID of the bucket to retrieve recorded information at stake time. - * @return [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking. - * @return [WAD] current bucket exchange rate at the time of staking. + * @notice Retrieve information about recorded `LP` and rate values for a given bucket and a given stake, at stake time. + * @param tokenId_ `ID` of the `NFT` staked in the rewards contract to retrieve information about. + * @param bucketId_ `ID` of the bucket to retrieve recorded information at stake time. + * @return `LP` amount (in `WAD`) the `NFT` owner is entitled in current bucket at the time of staking. + * @return Current bucket exchange rate (`WAD`) at the time of staking. */ function getBucketStateStakeInfo( - uint256 tokenId, - uint256 bucketId + uint256 tokenId_, + uint256 bucketId_ ) external view returns (uint256, uint256); } @@ -65,6 +65,7 @@ interface IRewardsManagerState { /*** State Structs ***/ /*********************/ +/// @dev Struct holding stake info state. struct StakeInfo { address ajnaPool; // address of the Ajna pool the NFT corresponds to uint96 lastClaimedEpoch; // last epoch the stake claimed rewards @@ -73,6 +74,7 @@ struct StakeInfo { mapping(uint256 => BucketState) snapshot; // the LP NFT's balances and exchange rates in each bucket at the time of staking } +/// @dev Struct holding bucket state at stake time. struct BucketState { uint128 lpsAtStakeTime; // [WAD] LP amount the NFT owner is entitled in current bucket at the time of staking uint128 rateAtStakeTime; // [WAD] current bucket exchange rate at the time of staking diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index 9c5c584ee..e0afd63c0 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -35,7 +35,7 @@ import { SettlerActions } from './SettlerActions.sol'; /** @title BorrowerActions library @notice External library containing logic for for pool actors: - - Borrowers: pledge collateral and draw debt; repay debt and pull collateral + - `Borrowers`: pledge collateral and draw debt; repay debt and pull collateral */ library BorrowerActions { @@ -43,6 +43,7 @@ library BorrowerActions { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `drawDebt` function local vars. struct DrawDebtLocalVars { bool borrow; // true if borrow action uint256 borrowerDebt; // [WAD] borrower's accrued debt @@ -53,6 +54,8 @@ library BorrowerActions { bool pledge; // true if pledge action bool stampT0Np; // true if loan's t0 neutral price should be restamped (when drawing debt or pledge settles auction) } + + /// @dev Struct used for `repayDebt` function local vars. struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP (NFTs only) @@ -90,27 +93,23 @@ library BorrowerActions { /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions - * @dev write state: - * - SettlerActions._settleAuction: - * - _removeAuction: - * - decrement kicker locked accumulator, increment kicker claimable accumumlator - * - decrement auctions count accumulator - * - decrement auctions.totalBondEscrowed accumulator - * - update auction queue state - * - Loans.update: - * - _upsert: - * - insert or update loan in loans array - * - remove: - * - remove loan from loans array - * - update borrower in address => borrower mapping - * @dev reverts on: - * - borrower not sender BorrowerNotSender() - * - borrower debt less than pool min debt AmountLTMinDebt() - * - limit price reached LimitIndexExceeded() - * - borrower cannot draw more debt BorrowerUnderCollateralized() - * @dev emit events: - * - SettlerActions._settleAuction: - * - AuctionNFTSettle or AuctionSettle + * @dev === Write state === + * @dev - `SettlerActions._settleAuction` (`_removeAuction`): + * @dev decrement kicker locked accumulator, increment kicker claimable accumumlator + * @dev decrement auctions count accumulator + * @dev decrement `auctions.totalBondEscrowed` accumulator + * @dev update auction queue state + * @dev - `Loans.update` (`_upsert`): + * @dev insert or update loan in loans array + * @dev remove loan from loans array + * @dev update borrower in `address => borrower` mapping + * @dev === Reverts on === + * @dev borrower not sender `BorrowerNotSender()` + * @dev borrower debt less than pool min debt `AmountLTMinDebt()` + * @dev limit price reached `LimitIndexExceeded()` + * @dev borrower cannot draw more debt `BorrowerUnderCollateralized()` + * @dev === Emit events === + * @dev - `SettlerActions._settleAuction`: `AuctionNFTSettle` or `AuctionSettle` */ function drawDebt( AuctionsState storage auctions_, @@ -249,28 +248,24 @@ library BorrowerActions { /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions - * @dev write state: - * - SettlerActions._settleAuction: - * - _removeAuction: - * - decrement kicker locked accumulator, increment kicker claimable accumumlator - * - decrement auctions count accumulator - * - decrement auctions.totalBondEscrowed accumulator - * - update auction queue state - * - Loans.update: - * - _upsert: - * - insert or update loan in loans array - * - remove: - * - remove loan from loans array - * - update borrower in address => borrower mapping - * @dev reverts on: - * - no debt to repay NoDebt() - * - borrower debt less than pool min debt AmountLTMinDebt() - * - borrower not sender BorrowerNotSender() - * - not enough collateral to pull InsufficientCollateral() - * - limit price reached LimitIndexExceeded() - * @dev emit events: - * - SettlerActions._settleAuction: - * - AuctionNFTSettle or AuctionSettle + * @dev === Write state === + * @dev - `SettlerActions._settleAuction` (`_removeAuction`): + * @dev decrement kicker locked accumulator, increment kicker claimable accumumlator + * @dev decrement auctions count accumulator + * @dev decrement `auctions.totalBondEscrowed` accumulator + * @dev update auction queue state + * @dev - `Loans.update` (`_upsert`): + * @dev insert or update loan in loans array + * @dev remove loan from loans array + * @dev update borrower in `address => borrower` mapping + * @dev === Reverts on === + * @dev no debt to repay `NoDebt()` + * @dev borrower debt less than pool min debt `AmountLTMinDebt()` + * @dev borrower not sender `BorrowerNotSender()` + * @dev not enough collateral to pull `InsufficientCollateral()` + * @dev limit price reached `LimitIndexExceeded()` + * @dev === Emit events === + * @dev - `SettlerActions._settleAuction`: `AuctionNFTSettle` or `AuctionSettle` */ function repayDebt( AuctionsState storage auctions_, @@ -415,18 +410,16 @@ library BorrowerActions { /** * @notice See `IPoolBorrowerActions` for descriptions - * @dev write state: - * - Loans.update: - * - _upsert: - * - insert or update loan in loans array - * - remove: - * - remove loan from loans array - * - update borrower in address => borrower mapping - * @dev reverts on: - * - auction active AuctionActive() - * - loan not fully collateralized BorrowerUnderCollateralized() - * @dev emit events: - * - LoanStamped + * @dev === Write state === + * @dev - `Loans.update` (`_upsert`): + * @dev insert or update loan in loans array + * @dev remove loan from loans array + * @dev update borrower in `address => borrower` mapping + * @dev === Reverts on === + * @dev auction active `AuctionActive()` + * @dev loan not fully collateralized `BorrowerUnderCollateralized()` + * @dev === Emit events === + * @dev - `LoanStamped` */ function stampLoan( AuctionsState storage auctions_, @@ -475,10 +468,11 @@ library BorrowerActions { /**********************/ /** - * @notice Returns true if borrower is in auction. - * @dev Used to accuratley increment and decrement t0DebtInAuction. + * @notice Returns `true` if borrower is in auction. + * @dev Used to accuratley increment and decrement `t0DebtInAuction` accumulator. + * @param auctions_ Struct for pool auctions state. * @param borrower_ Borrower address to check auction status for. - * @return active_ Boolean, based on if borrower is in auction. + * @return `True` if borrower is in auction. */ function _inAuction( AuctionsState storage auctions_, diff --git a/src/libraries/external/KickerActions.sol b/src/libraries/external/KickerActions.sol index 9a3708e8a..ed516d6cc 100644 --- a/src/libraries/external/KickerActions.sol +++ b/src/libraries/external/KickerActions.sol @@ -50,6 +50,7 @@ library KickerActions { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `kick` function local vars. struct KickLocalVars { uint256 borrowerDebt; // [WAD] the accrued debt of kicked borrower uint256 borrowerCollateral; // [WAD] amount of kicked borrower collateral @@ -61,17 +62,19 @@ library KickerActions { uint256 t0KickPenalty; // [WAD] t0 debt added as kick penalty uint256 kickPenalty; // [WAD] current debt added as kick penalty } + + /// @dev Struct used for `kickWithDeposit` function local vars. struct KickWithDepositLocalVars { uint256 amountToDebitFromDeposit; // [WAD] the amount of quote tokens used to kick and debited from lender deposit uint256 bucketCollateral; // [WAD] amount of collateral in bucket uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket - uint256 bucketLPs; // [WAD] LP of the bucket + uint256 bucketLP; // [WAD] LP of the bucket uint256 bucketPrice; // [WAD] bucket price uint256 bucketRate; // [WAD] bucket exchange rate uint256 bucketScale; // [WAD] bucket scales uint256 bucketUnscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket - uint256 lenderLPs; // [WAD] LP of lender in bucket - uint256 redeemedLPs; // [WAD] LP used by kick action + uint256 lenderLP; // [WAD] LP of lender in bucket + uint256 redeemedLP; // [WAD] LP used by kick action } /**************/ @@ -100,11 +103,8 @@ library KickerActions { /***************************/ /** - * @notice Called to start borrower liquidation and to update the auctions queue. - * @param poolState_ Current state of the pool. - * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @return kickResult_ The result of the kick action. + * @notice See `IPoolKickerActions` for descriptions. + * @return The `KickResult` struct result of the kick action. */ function kick( AuctionsState storage auctions_, @@ -128,18 +128,14 @@ library KickerActions { } /** - * @notice Called by lenders to kick loans using their deposits. - * @dev write state: - * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): - * - update values array state - * - decrement lender.lps accumulator - * - decrement bucket.lps accumulator - * @dev emit events: - * - RemoveQuoteToken - * @param poolState_ Current state of the pool. - * @param index_ The deposit index from where lender removes liquidity. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @return kickResult_ The result of the kick action. + * @notice See `IPoolKickerActions` for descriptions. + * @dev === Write state === + * @dev - `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): update `values` array state + * @dev - decrement `lender.lps` accumulator + * @dev - decrement `bucket.lps` accumulator + * @dev === Emit events === + * @dev - `RemoveQuoteToken` + * @return kickResult_ The `KickResult` struct result of the kick action. */ function kickWithDeposit( AuctionsState storage auctions_, @@ -157,9 +153,9 @@ library KickerActions { KickWithDepositLocalVars memory vars; - if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLPs = lender.lps; + if (bucket.bankruptcyTime < lender.depositTime) vars.lenderLP = lender.lps; - vars.bucketLPs = bucket.lps; + vars.bucketLP = bucket.lps; vars.bucketCollateral = bucket.collateral; vars.bucketPrice = _priceAt(index_); vars.bucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, index_); @@ -169,12 +165,12 @@ library KickerActions { // calculate max amount that can be removed (constrained by lender LP in bucket, bucket deposit and the amount lender wants to remove) vars.bucketRate = Buckets.getExchangeRate( vars.bucketCollateral, - vars.bucketLPs, + vars.bucketLP, vars.bucketDeposit, vars.bucketPrice ); - vars.amountToDebitFromDeposit = Maths.wmul(vars.lenderLPs, vars.bucketRate); // calculate amount to remove based on lender LP in bucket + vars.amountToDebitFromDeposit = Maths.wmul(vars.lenderLP, vars.bucketRate); // calculate amount to remove based on lender LP in bucket if (vars.amountToDebitFromDeposit > vars.bucketDeposit) vars.amountToDebitFromDeposit = vars.bucketDeposit; // cap the amount to remove at bucket deposit @@ -207,13 +203,13 @@ library KickerActions { // remove amount from deposits if (vars.amountToDebitFromDeposit == vars.bucketDeposit && vars.bucketCollateral == 0) { - // In this case we are redeeming the entire bucket exactly, and need to ensure bucket LPs are set to 0 - vars.redeemedLPs = vars.bucketLPs; + // In this case we are redeeming the entire bucket exactly, and need to ensure bucket LP are set to 0 + vars.redeemedLP = vars.bucketLP; Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); } else { - vars.redeemedLPs = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); + vars.redeemedLP = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); Deposits.unscaledRemove( deposits_, @@ -223,14 +219,14 @@ library KickerActions { } // remove bucket LP coresponding to the amount removed from deposits - lender.lps -= vars.redeemedLPs; - bucket.lps -= vars.redeemedLPs; + lender.lps -= vars.redeemedLP; + bucket.lps -= vars.redeemedLP; emit RemoveQuoteToken( msg.sender, index_, vars.amountToDebitFromDeposit, - vars.redeemedLPs, + vars.redeemedLP, kickResult_.lup ); } @@ -240,14 +236,15 @@ library KickerActions { /*************************/ /** - * @notice See `IPoolReserveAuctionActions` for descriptions. - * @dev write state: - * - update reserveAuction.unclaimed accumulator - * - update reserveAuction.kicked timestamp state - * @dev reverts on: - * - no reserves to claim NoReserves() - * @dev emit events: - * - KickReserveAuction + * @notice See `IPoolKickerActions` for descriptions. + * @dev === Write state === + * @dev update `reserveAuction.unclaimed` accumulator + * @dev update `reserveAuction.kicked` timestamp state + * @dev === Reverts on === + * @dev no reserves to claim `NoReserves()` + * @dev === Emit events === + * @dev - `KickReserveAuction` + * @return kickerAward_ The `LP`s awarded to reserve auction kicker. */ function kickReserveAuction( AuctionsState storage auctions_, @@ -301,24 +298,27 @@ library KickerActions { /** * @notice Called to start borrower liquidation and to update the auctions queue. - * @dev write state: - * - _recordAuction: - * - borrower -> liquidation mapping update - * - increment auctions count accumulator - * - increment auctions.totalBondEscrowed accumulator - * - updates auction queue state - * - _updateKicker: - * - update locked and claimable kicker accumulators - * - Loans.remove: - * - delete borrower from indices => borrower address mapping - * - remove loan from loans array - * @dev emit events: - * - Kick + * @dev === Write state === + * @dev - `_recordAuction`: + * @dev `borrower -> liquidation` mapping update + * @dev increment `auctions count` accumulator + * @dev increment `auctions.totalBondEscrowed` accumulator + * @dev updates auction queue state + * @dev - `_updateKicker`: + * @dev update `locked` and `claimable` kicker accumulators + * @dev - `Loans.remove`: + * @dev delete borrower from `indices => borrower` address mapping + * @dev remove loan from loans array + * @dev === Emit events === + * @dev - `Kick` + * @param auctions_ Struct for pool auctions state. + * @param deposits_ Struct for pool deposits state. + * @param loans_ Struct for pool loans state. * @param poolState_ Current state of the pool. * @param borrowerAddress_ Address of the borrower to kick. - * @param limitIndex_ Index of the lower bound of NP tolerated when kicking the auction. - * @param additionalDebt_ Additional debt to be used when calculating proposed LUP. - * @return kickResult_ The result of the kick action. + * @param limitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. + * @param additionalDebt_ Additional debt to be used when calculating proposed `LUP`. + * @return kickResult_ The `KickResult` struct result of the kick action. */ function _kick( AuctionsState storage auctions_, @@ -405,8 +405,9 @@ library KickerActions { /** * @notice Updates kicker balances. - * @dev write state: - * - update locked and claimable kicker accumulators + * @dev === Write state === + * @dev update `locked` and `claimable` kicker accumulators + * @param auctions_ Struct for pool auctions state. * @param bondSize_ Bond size to cover newly kicked auction. * @return bondDifference_ The amount that kicker should send to pool to cover auction bond. */ @@ -435,16 +436,17 @@ library KickerActions { } /** - * @notice Saves a new liquidation that was kicked. - * @dev write state: - * - borrower -> liquidation mapping update - * - increment auctions count accumulator - * - updates auction queue state + * @notice Saves in storage a new liquidation that was kicked. + * @dev === Write state === + * @dev `borrower -> liquidation` mapping update + * @dev increment auctions count accumulator + * @dev updates auction queue state + * @param auctions_ Struct for pool auctions state. * @param borrowerAddress_ Address of the borrower that is kicked. * @param bondSize_ Bond size to cover newly kicked auction. * @param bondFactor_ Bond factor of the newly kicked auction. - * @param momp_ Current pool MOMP. - * @param neutralPrice_ Current pool Neutral Price. + * @param momp_ Current pool `MOMP`. + * @param neutralPrice_ Current pool `Neutral Price`. */ function _recordAuction( AuctionsState storage auctions_, diff --git a/src/libraries/external/LPActions.sol b/src/libraries/external/LPActions.sol index 951ece024..204691dc0 100644 --- a/src/libraries/external/LPActions.sol +++ b/src/libraries/external/LPActions.sol @@ -10,8 +10,8 @@ import { Maths } from '../internal/Maths.sol'; /** @title LPActions library - @notice External library containing logic for LP owners to: - - increase/decrease/revoke LP allowance; approve/revoke LP transferors; transfer LP + @notice External library containing logic for `LP` owners to: + - `increase`/`decrease`/`revoke` `LP` allowance; `approve`/`revoke` `LP` transferors; `transfer` `LP` */ library LPActions { @@ -25,7 +25,7 @@ library LPActions { event IncreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); event DecreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); event RevokeLPAllowance(address indexed owner, address indexed spender, uint256[] indexes); - event TransferLP(address owner, address newOwner, uint256[] indexes, uint256 lps); + event TransferLP(address owner, address newOwner, uint256[] indexes, uint256 lp); /**************/ /*** Errors ***/ @@ -45,12 +45,12 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - increment LP allowances - * @dev reverts on: - * - invalid indexes and amounts input InvalidAllowancesInput() - * @dev emit events: - * - IncreaseLPAllowance + * @dev === Write state === + * @dev increment `LP` allowances + * @dev === Reverts on === + * @dev invalid indexes and amounts input `InvalidAllowancesInput()` + * @dev === Emit events === + * @dev - `IncreaseLPAllowance` */ function increaseLPAllowance( mapping(uint256 => uint256) storage allowances_, @@ -81,12 +81,12 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - decrement LP allowances - * @dev reverts on: - * - invalid indexes and amounts input InvalidAllowancesInput() - * @dev emit events: - * - DecreaseLPAllowance + * @dev === Write state === + * @dev decrement `LP` allowances + * @dev === Reverts on === + * @dev invalid indexes and amounts input `InvalidAllowancesInput()` + * @dev === Emit events === + * @dev - `DecreaseLPAllowance` */ function decreaseLPAllowance( mapping(uint256 => uint256) storage allowances_, @@ -118,10 +118,10 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - decrement LP allowances - * @dev emit events: - * - RevokeLPAllowance + * @dev === Write state === + * @dev decrement `LP` allowances + * @dev === Emit events === + * @dev - `RevokeLPAllowance` */ function revokeLPAllowance( mapping(uint256 => uint256) storage allowances_, @@ -148,10 +148,10 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - approvedTransferors mapping - * @dev emit events: - * - ApproveLPTransferors + * @dev === Write state === + * @dev `approvedTransferors` mapping + * @dev === Emit events === + * @dev - `ApproveLPTransferors` */ function approveLPTransferors( mapping(address => bool) storage allowances_, @@ -172,10 +172,10 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - approvedTransferors mapping - * @dev emit events: - * - RevokeLPTransferors + * @dev === Write state === + * @dev `approvedTransferors` mapping + * @dev === Emit events === + * @dev - `RevokeLPTransferors` */ function revokeLPTransferors( mapping(address => bool) storage allowances_, @@ -196,15 +196,15 @@ library LPActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - delete allowance mapping - * - increment new lender.lps accumulator and lender.depositTime state - * - delete old lender from bucket -> lender mapping - * @dev reverts on: - * - invalid index InvalidIndex() - * - no allowance NoAllowance() - * @dev emit events: - * - TransferLP + * @dev === Write state === + * @dev delete allowance mapping + * @dev increment new `lender.lps` accumulator and `lender.depositTime` state + * @dev delete old lender from `bucket -> lender` mapping + * @dev === Reverts on === + * @dev invalid index `InvalidIndex()` + * @dev no allowance `NoAllowance()` + * @dev === Emit events === + * @dev - `TransferLP` */ function transferLP( mapping(uint256 => Bucket) storage buckets_, @@ -222,7 +222,7 @@ library LPActions { uint256 indexesLength = indexes_.length; uint256 index; - uint256 lpsTransferred; + uint256 lpTransferred; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; @@ -243,7 +243,7 @@ library LPActions { // transfer allowed amount or entire LP balance allowedAmount = Maths.min(allowedAmount, ownerLpBalance); - // move owner lps (if any) to the new owner + // move owner LP (if any) to the new owner if (allowedAmount != 0) { Lender storage newOwner = bucket.lenders[newOwnerAddress_]; @@ -257,8 +257,8 @@ library LPActions { newOwner.lps = allowedAmount; } - owner.lps -= allowedAmount; // remove amount of LP from old owner - lpsTransferred += allowedAmount; // add amount of LP to total LP transferred + owner.lps -= allowedAmount; // remove amount of LP from old owner + lpTransferred += allowedAmount; // add amount of LP to total LP transferred // set the deposit time as the max of transferred deposit and current deposit time newOwner.depositTime = Maths.max(ownerDepositTime, newOwnerDepositTime); @@ -274,7 +274,7 @@ library LPActions { ownerAddress_, newOwnerAddress_, indexes_, - lpsTransferred + lpTransferred ); } } diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index 06c0780f9..c23ec6d56 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -23,8 +23,8 @@ import { Maths } from '../internal/Maths.sol'; /** @title LenderActions library @notice External library containing logic for lender actors: - - Lenders: add, remove and move quote tokens; - - Traders: add, remove and move quote tokens; add and remove collateral + - `Lenders`: add, remove and move quote tokens; + - `Traders`: add, remove and move quote tokens; add and remove collateral */ library LenderActions { @@ -32,13 +32,14 @@ library LenderActions { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `moveQuoteToken` function local vars. struct MoveQuoteLocalVars { uint256 fromBucketPrice; // [WAD] Price of the bucket to move amount from. uint256 fromBucketCollateral; // [WAD] Total amount of collateral in from bucket. - uint256 fromBucketLPs; // [WAD] Total amount of LP in from bucket. - uint256 fromBucketLenderLPs; // [WAD] Amount of LP owned by lender in from bucket. + uint256 fromBucketLP; // [WAD] Total amount of LP in from bucket. + uint256 fromBucketLenderLP; // [WAD] Amount of LP owned by lender in from bucket. uint256 fromBucketDepositTime; // Time of lender deposit in the bucket to move amount from. - uint256 fromBucketRemainingLPs; // Amount of LP remaining in from bucket after move. + uint256 fromBucketRemainingLP; // Amount of LP remaining in from bucket after move. uint256 fromBucketRemainingDeposit; // Amount of scaled deposit remaining in from bucket after move. uint256 toBucketPrice; // [WAD] Price of the bucket to move amount to. uint256 toBucketBankruptcyTime; // Time the bucket to move amount to was marked as insolvent. @@ -49,10 +50,12 @@ library LenderActions { uint256 ptp; // [WAD] Pool Threshold Price. uint256 htp; // [WAD] Highest Threshold Price. } + + /// @dev Struct used for `removeQuoteToken` function local vars. struct RemoveDepositParams { uint256 depositConstraint; // [WAD] Constraint on deposit in quote token. uint256 lpConstraint; // [WAD] Constraint in LPB terms. - uint256 bucketLPs; // [WAD] Total LPB in the bucket. + uint256 bucketLP; // [WAD] Total LPB in the bucket. uint256 bucketCollateral; // [WAD] Claimable collateral in the bucket. uint256 price; // [WAD] Price of bucket. uint256 index; // Bucket index. @@ -92,20 +95,19 @@ library LenderActions { /** * @notice See `IERC20PoolLenderActions` and `IERC721PoolLenderActions` for descriptions - * @dev write state: - * - Buckets.addCollateral: - * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLP: - * - increment lender.lps accumulator and lender.depositTime state - * @dev reverts on: - * - invalid bucket index InvalidIndex() + * @dev === Write state === + * @dev - `Buckets.addCollateral`: + * @dev increment `bucket.collateral` and `bucket.lps` accumulator + * @dev `addLenderLP`: increment `lender.lps` accumulator and `lender.depositTime `state + * @dev === Reverts on === + * @dev invalid bucket index `InvalidIndex()` */ function addCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256 collateralAmountToAdd_, uint256 index_ - ) external returns (uint256 bucketLPs_) { + ) external returns (uint256 bucketLP_) { // revert if no amount to be added if (collateralAmountToAdd_ == 0) revert InvalidAmount(); // revert if adding at invalid index @@ -114,7 +116,7 @@ library LenderActions { uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); uint256 bucketPrice = _priceAt(index_); - bucketLPs_ = Buckets.addCollateral( + bucketLP_ = Buckets.addCollateral( buckets_[index_], msg.sender, bucketDeposit, @@ -125,23 +127,22 @@ library LenderActions { /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - Deposits.unscaledAdd (add new amount in Fenwick tree): - * - update values array state - * - increment bucket.lps accumulator - * - increment lender.lps accumulator and lender.depositTime state - * @dev reverts on: - * - invalid bucket index InvalidIndex() - * - same block when bucket becomes insolvent BucketBankruptcyBlock() - * @dev emit events: - * - AddQuoteToken + * @dev === Write state === + * @dev - `Deposits.unscaledAdd` (add new amount in `Fenwick` tree): update `values` array state + * @dev - increment `bucket.lps` accumulator + * @dev - increment `lender.lps` accumulator and `lender.depositTime` state + * @dev === Reverts on === + * @dev invalid bucket index `InvalidIndex()` + * @dev same block when bucket becomes insolvent `BucketBankruptcyBlock()` + * @dev === Emit events === + * @dev - `AddQuoteToken` */ function addQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, AddQuoteParams calldata params_ - ) external returns (uint256 bucketLPs_, uint256 lup_) { + ) external returns (uint256 bucketLP_, uint256 lup_) { // revert if no amount to be added if (params_.amount == 0) revert InvalidAmount(); // revert if adding to an invalid index @@ -167,7 +168,7 @@ library LenderActions { addedAmount = Maths.wmul(addedAmount, Maths.WAD - _depositFeeRate(poolState_.rate)); } - bucketLPs_ = Buckets.quoteTokensToLP( + bucketLP_ = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, bucketDeposit, @@ -178,10 +179,10 @@ library LenderActions { Deposits.unscaledAdd(deposits_, params_.index, Maths.wdiv(addedAmount, bucketScale)); // update lender LP - Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, bucketLPs_); + Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, bucketLP_); // update bucket LP - bucket.lps += bucketLPs_; + bucket.lps += bucketLP_; // only need to recalculate LUP if the deposit was above it if (!depositBelowLup) { @@ -193,37 +194,35 @@ library LenderActions { msg.sender, params_.index, addedAmount, - bucketLPs_, + bucketLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - _removeMaxDeposit: - * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): - * - update values array state - * - Deposits.unscaledAdd (add amount in Fenwick tree, to index): - * - update values array state - * - decrement lender.lps accumulator for from bucket - * - increment lender.lps accumulator and lender.depositTime state for to bucket - * - decrement bucket.lps accumulator for from bucket - * - increment bucket.lps accumulator for to bucket - * @dev reverts on: - * - same index MoveToSameIndex() - * - dust amount DustAmountNotExceeded() - * - invalid index InvalidIndex() - * @dev emit events: - * - BucketBankruptcy - * - MoveQuoteToken + * @dev === Write state === + * @dev - `_removeMaxDeposit`: + * @dev `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): update `values` array state + * @dev - `Deposits.unscaledAdd` (add amount in `Fenwick` tree, to index): update `values` array state + * @dev - decrement `lender.lps` accumulator for from bucket + * @dev - increment `lender.lps` accumulator and `lender.depositTime` state for to bucket + * @dev - decrement `bucket.lps` accumulator for from bucket + * @dev - increment `bucket.lps` accumulator for to bucket + * @dev === Reverts on === + * @dev same index `MoveToSameIndex()` + * @dev dust amount `DustAmountNotExceeded()` + * @dev invalid index `InvalidIndex()` + * @dev === Emit events === + * @dev - `BucketBankruptcy` + * @dev - `MoveQuoteToken` */ function moveQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, MoveQuoteParams calldata params_ - ) external returns (uint256 fromBucketRedeemedLPs_, uint256 toBucketLPs_, uint256 movedAmount_, uint256 lup_) { + ) external returns (uint256 fromBucketRedeemedLP_, uint256 toBucketLP_, uint256 movedAmount_, uint256 lup_) { if (params_.maxAmountToMove == 0) revert InvalidAmount(); if (params_.fromIndex == params_.toIndex) @@ -246,19 +245,19 @@ library LenderActions { vars.fromBucketPrice = _priceAt(params_.fromIndex); vars.fromBucketCollateral = fromBucket.collateral; - vars.fromBucketLPs = fromBucket.lps; + vars.fromBucketLP = fromBucket.lps; vars.fromBucketDepositTime = fromBucketLender.depositTime; vars.toBucketPrice = _priceAt(params_.toIndex); - if (fromBucket.bankruptcyTime < vars.fromBucketDepositTime) vars.fromBucketLenderLPs = fromBucketLender.lps; + if (fromBucket.bankruptcyTime < vars.fromBucketDepositTime) vars.fromBucketLenderLP = fromBucketLender.lps; - (movedAmount_, fromBucketRedeemedLPs_, vars.fromBucketRemainingDeposit) = _removeMaxDeposit( + (movedAmount_, fromBucketRedeemedLP_, vars.fromBucketRemainingDeposit) = _removeMaxDeposit( deposits_, RemoveDepositParams({ depositConstraint: params_.maxAmountToMove, - lpConstraint: vars.fromBucketLenderLPs, - bucketLPs: vars.fromBucketLPs, + lpConstraint: vars.fromBucketLenderLP, + bucketLP: vars.fromBucketLP, bucketCollateral: vars.fromBucketCollateral, price: vars.fromBucketPrice, index: params_.fromIndex, @@ -276,7 +275,7 @@ library LenderActions { vars.toBucketScale = Deposits.scale(deposits_, params_.toIndex); vars.toBucketDeposit = Maths.wmul(vars.toBucketUnscaledDeposit, vars.toBucketScale); - toBucketLPs_ = Buckets.quoteTokensToLP( + toBucketLP_ = Buckets.quoteTokensToLP( toBucket.collateral, toBucket.lps, vars.toBucketDeposit, @@ -292,22 +291,22 @@ library LenderActions { if (params_.fromIndex < params_.toIndex && vars.htp > lup_) revert LUPBelowHTP(); // update lender and bucket LP balance in from bucket - vars.fromBucketRemainingLPs = vars.fromBucketLPs - fromBucketRedeemedLPs_; + vars.fromBucketRemainingLP = vars.fromBucketLP - fromBucketRedeemedLP_; // check if from bucket healthy after move quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP - if (vars.fromBucketCollateral == 0 && vars.fromBucketRemainingDeposit == 0 && vars.fromBucketRemainingLPs != 0) { + if (vars.fromBucketCollateral == 0 && vars.fromBucketRemainingDeposit == 0 && vars.fromBucketRemainingLP != 0) { fromBucket.lps = 0; fromBucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( params_.fromIndex, - vars.fromBucketRemainingLPs + vars.fromBucketRemainingLP ); } else { // update lender and bucket LP balance - fromBucketLender.lps -= fromBucketRedeemedLPs_; + fromBucketLender.lps -= fromBucketRedeemedLP_; - fromBucket.lps = vars.fromBucketRemainingLPs; + fromBucket.lps = vars.fromBucketRemainingLP; } // update lender and bucket LP balance in target bucket @@ -316,52 +315,51 @@ library LenderActions { vars.toBucketDepositTime = toBucketLender.depositTime; if (vars.toBucketBankruptcyTime >= vars.toBucketDepositTime) { // bucket is bankrupt and deposit was done before bankruptcy time, reset lender lp amount - toBucketLender.lps = toBucketLPs_; + toBucketLender.lps = toBucketLP_; // set deposit time of the lender's to bucket as bucket's last bankruptcy timestamp + 1 so deposit won't get invalidated vars.toBucketDepositTime = vars.toBucketBankruptcyTime + 1; } else { - toBucketLender.lps += toBucketLPs_; + toBucketLender.lps += toBucketLP_; } // set deposit time to the greater of the lender's from bucket and the target bucket toBucketLender.depositTime = Maths.max(vars.fromBucketDepositTime, vars.toBucketDepositTime); // update bucket LP balance - toBucket.lps += toBucketLPs_; + toBucket.lps += toBucketLP_; emit MoveQuoteToken( msg.sender, params_.fromIndex, params_.toIndex, movedAmount_, - fromBucketRedeemedLPs_, - toBucketLPs_, + fromBucketRedeemedLP_, + toBucketLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - _removeMaxDeposit: - * - Deposits.unscaledRemove (remove amount in Fenwick tree): - * - update values array state - * - decrement lender.lps accumulator - * - decrement bucket.lps accumulator - * @dev reverts on: - * - no LP NoClaim() - * - LUP lower than HTP LUPBelowHTP() - * @dev emit events: - * - RemoveQuoteToken - * - BucketBankruptcy + * @dev === Write state === + * @dev - `_removeMaxDeposit`: + * @dev `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): update `values` array state + * @dev - decrement `lender.lps` accumulator + * @dev - decrement `bucket.lps` accumulator + * @dev === Reverts on === + * @dev no `LP` `NoClaim()`; + * @dev `LUP` lower than `HTP` `LUPBelowHTP()` + * @dev === Emit events === + * @dev - `RemoveQuoteToken` + * @dev - `BucketBankruptcy` */ function removeQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, RemoveQuoteParams calldata params_ - ) external returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 lup_) { + ) external returns (uint256 removedAmount_, uint256 redeemedLP_, uint256 lup_) { // revert if no amount to be removed if (params_.maxAmount == 0) revert InvalidAmount(); @@ -378,13 +376,13 @@ library LenderActions { removeParams.depositConstraint = params_.maxAmount; removeParams.price = _priceAt(params_.index); - removeParams.bucketLPs = bucket.lps; + removeParams.bucketLP = bucket.lps; removeParams.bucketCollateral = bucket.collateral; removeParams.index = params_.index; removeParams.dustLimit = poolState_.quoteDustLimit; uint256 unscaledRemaining; - (removedAmount_, redeemedLPs_, unscaledRemaining) = _removeMaxDeposit( + (removedAmount_, redeemedLP_, unscaledRemaining) = _removeMaxDeposit( deposits_, removeParams ); @@ -402,43 +400,43 @@ library LenderActions { (poolState_.debt != 0 && poolState_.debt > Deposits.treeSum(deposits_)) ) revert LUPBelowHTP(); - uint256 lpsRemaining = removeParams.bucketLPs - redeemedLPs_; + uint256 lpRemaining = removeParams.bucketLP - redeemedLP_; // check if bucket healthy after remove quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP - if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpsRemaining != 0) { + if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpRemaining != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( params_.index, - lpsRemaining + lpRemaining ); } else { // update lender and bucket LP balances - lender.lps -= redeemedLPs_; + lender.lps -= redeemedLP_; - bucket.lps = lpsRemaining; + bucket.lps = lpRemaining; } emit RemoveQuoteToken( msg.sender, params_.index, removedAmount_, - redeemedLPs_, + redeemedLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions - * @dev write state: - * - decrement lender.lps accumulator - * - decrement bucket.collateral and bucket.lps accumulator - * @dev reverts on: - * - not enough collateral InsufficientCollateral() - * - insufficient LP InsufficientLP() - * @dev emit events: - * - BucketBankruptcy + * @dev === Write state === + * @dev decrement `lender.lps` accumulator + * @dev decrement `bucket.collateral` and `bucket.lps` accumulator + * @dev === Reverts on === + * @dev not enough collateral `InsufficientCollateral()` + * @dev insufficient `LP` `InsufficientLP()` + * @dev === Emit events === + * @dev - `BucketBankruptcy` */ function removeCollateral( mapping(uint256 => Bucket) storage buckets_, @@ -456,12 +454,12 @@ library LenderActions { if (amount_ > bucketCollateral) revert InsufficientCollateral(); uint256 bucketPrice = _priceAt(index_); - uint256 bucketLPs = bucket.lps; + uint256 bucketLP = bucket.lps; uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); lpAmount_ = Buckets.collateralToLP( bucketCollateral, - bucketLPs, + bucketLP, bucketDeposit, amount_, bucketPrice @@ -474,10 +472,10 @@ library LenderActions { if (lenderLpBalance == 0 || lpAmount_ > lenderLpBalance) revert InsufficientLP(); // update bucket LP and collateral balance - bucketLPs -= lpAmount_; + bucketLP -= lpAmount_; // If clearing out the bucket collateral, ensure it's zeroed out - if (bucketLPs == 0 && bucketDeposit == 0) { + if (bucketLP == 0 && bucketDeposit == 0) { amount_ = bucketCollateral; } @@ -485,31 +483,31 @@ library LenderActions { bucket.collateral = bucketCollateral; // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP - if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { + if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLP != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( index_, - bucketLPs + bucketLP ); } else { // update lender LP balance lender.lps -= lpAmount_; - bucket.lps = bucketLPs; + bucket.lps = bucketLP; } } /** * @notice Removes max collateral amount from a given bucket index. - * @dev write state: - * - _removeMaxCollateral: - * - decrement lender.lps accumulator - * - decrement bucket.collateral and bucket.lps accumulator - * @dev reverts on: - * - not enough collateral InsufficientCollateral() - * - no claim NoClaim() + * @dev === Write state === + * @dev - `_removeMaxCollateral`: + * @dev decrement `lender.lps` accumulator + * @dev decrement `bucket.collateral` and `bucket.lps` accumulator + * @dev === Reverts on === + * @dev not enough collateral `InsufficientCollateral()` + * @dev no claim `NoClaim()` * @return Amount of collateral that was removed. * @return Amount of LP redeemed for removed collateral amount. */ @@ -532,13 +530,12 @@ library LenderActions { /** * @notice See `IERC721PoolLenderActions` for descriptions - * @dev write state: - * - Buckets.addCollateral: - * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLP: - * - increment lender.lps accumulator and lender.depositTime state - * @dev reverts on: - * - invalid merge index CannotMergeToHigherPrice() + * @dev === Write state === + * @dev - `Buckets.addCollateral`: + * @dev increment `bucket.collateral` and `bucket.lps` accumulator + * @dev increment `lender.lps` accumulator and `lender.depositTime` state + * @dev === Reverts on === + * @dev invalid merge index `CannotMergeToHigherPrice()` */ function mergeOrRemoveCollateral( mapping(uint256 => Bucket) storage buckets_, @@ -546,7 +543,7 @@ library LenderActions { uint256[] calldata removalIndexes_, uint256 collateralAmount_, uint256 toIndex_ - ) external returns (uint256 collateralToMerge_, uint256 bucketLPs_) { + ) external returns (uint256 collateralToMerge_, uint256 bucketLP_) { uint256 i; uint256 fromIndex; uint256 collateralRemoved; @@ -578,7 +575,7 @@ library LenderActions { uint256 toBucketDeposit = Deposits.valueAt(deposits_, toIndex_); uint256 toBucketPrice = _priceAt(toIndex_); - bucketLPs_ = Buckets.addCollateral( + bucketLP_ = Buckets.addCollateral( buckets_[toIndex_], msg.sender, toBucketDeposit, @@ -594,16 +591,16 @@ library LenderActions { /** * @notice Removes max collateral amount from a given bucket index. - * @dev write state: - * - decrement lender.lps accumulator - * - decrement bucket.collateral and bucket.lps accumulator - * @dev reverts on: - * - not enough collateral InsufficientCollateral() - * - no claim NoClaim() - * @dev emit events: - * - BucketBankruptcy + * @dev === Write state === + * @dev decrement `lender.lps` accumulator + * @dev decrement `bucket.collateral` and `bucket.lps` accumulator + * @dev === Reverts on === + * @dev not enough collateral `InsufficientCollateral()` + * @dev no claim `NoClaim()` + * @dev === Emit events === + * @dev - `BucketBankruptcy` * @return collateralAmount_ Amount of collateral that was removed. - * @return lpAmount_ Amount of LPs redeemed for removed collateral amount. + * @return lpAmount_ Amount of `LP` redeemed for removed collateral amount. */ function _removeMaxCollateral( mapping(uint256 => Bucket) storage buckets_, @@ -624,37 +621,37 @@ library LenderActions { if (lenderLpBalance == 0) revert NoClaim(); // revert if no LP to redeem uint256 bucketPrice = _priceAt(index_); - uint256 bucketLPs = bucket.lps; + uint256 bucketLP = bucket.lps; uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); // limit amount by what is available in the bucket collateralAmount_ = Maths.min(maxAmount_, bucketCollateral); // determine how much LP would be required to remove the requested amount - uint256 requiredLPs = Buckets.collateralToLP( + uint256 requiredLP = Buckets.collateralToLP( bucketCollateral, - bucketLPs, + bucketLP, bucketDeposit, collateralAmount_, bucketPrice ); // limit withdrawal by the lender's LPB - if (requiredLPs <= lenderLpBalance) { + if (requiredLP <= lenderLpBalance) { // withdraw collateralAmount_ as is - lpAmount_ = requiredLPs; + lpAmount_ = requiredLP; } else { lpAmount_ = lenderLpBalance; - collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLpBalance, collateralAmount_), requiredLPs); + collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLpBalance, collateralAmount_), requiredLP); if (collateralAmount_ == 0) revert InsufficientLP(); } - // update bucket LPs and collateral balance - bucketLPs -= Maths.min(bucketLPs, lpAmount_); + // update bucket LP and collateral balance + bucketLP -= Maths.min(bucketLP, lpAmount_); // If clearing out the bucket collateral, ensure it's zeroed out - if (bucketLPs == 0 && bucketDeposit == 0) { + if (bucketLP == 0 && bucketDeposit == 0) { collateralAmount_ = bucketCollateral; } @@ -662,35 +659,35 @@ library LenderActions { bucket.collateral = bucketCollateral; // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP - if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLPs != 0) { + if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLP != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( index_, - bucketLPs + bucketLP ); } else { // update lender LP balance lender.lps -= lpAmount_; - bucket.lps = bucketLPs; + bucket.lps = bucketLP; } } /** * @notice Removes the amount of quote tokens calculated for the given amount of LP. - * @dev write state: - * - Deposits.unscaledRemove (remove amount in Fenwick tree, from index): - * - update values array state + * @dev === Write state === + * @dev - `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): + * @dev update `values` array state * @return removedAmount_ Amount of scaled deposit removed. - * @return redeemedLPs_ Amount of bucket LP corresponding for calculated scaled deposit amount. + * @return redeemedLP_ Amount of bucket `LP` corresponding for calculated scaled deposit amount. * @return unscaledRemaining_ Amount of unscaled deposit remaining. */ function _removeMaxDeposit( DepositsState storage deposits_, RemoveDepositParams memory params_ - ) internal returns (uint256 removedAmount_, uint256 redeemedLPs_, uint256 unscaledRemaining_) { + ) internal returns (uint256 removedAmount_, uint256 redeemedLP_, uint256 unscaledRemaining_) { uint256 unscaledDepositAvailable = Deposits.unscaledValueAt(deposits_, params_.index); if (unscaledDepositAvailable == 0) revert InsufficientLiquidity(); // revert if there's no liquidity available to remove @@ -701,7 +698,7 @@ library LenderActions { uint256 exchangeRate = Buckets.getExchangeRate( params_.bucketCollateral, - params_.bucketLPs, + params_.bucketLP, scaledDepositAvailable, params_.price ); @@ -709,8 +706,8 @@ library LenderActions { // Below is pseudocode explaining the logic behind finding the constrained amount of deposit and LPB // scaledRemovedAmount is constrained by the scaled maxAmount(in QT), the scaledDeposit constraint, and // the lender LPB exchange rate in scaled deposit-to-LPB for the bucket: - // scaledRemovedAmount = min ( maxAmount_, scaledDeposit, lenderLPsBalance*exchangeRate) - // redeemedLPs_ = min ( maxAmount_/scaledExchangeRate, scaledDeposit/exchangeRate, lenderLPsBalance) + // scaledRemovedAmount = min ( maxAmount_, scaledDeposit, lenderLPBalance*exchangeRate) + // redeemedLP_ = min ( maxAmount_/scaledExchangeRate, scaledDeposit/exchangeRate, lenderLPBalance) uint256 scaledLpConstraint = Maths.wmul(params_.lpConstraint, exchangeRate); if ( @@ -719,19 +716,19 @@ library LenderActions { ) { // depositConstraint is binding constraint removedAmount_ = params_.depositConstraint; - redeemedLPs_ = Maths.wdiv(removedAmount_, exchangeRate); + redeemedLP_ = Maths.wdiv(removedAmount_, exchangeRate); } else if (scaledDepositAvailable < scaledLpConstraint) { // scaledDeposit is binding constraint removedAmount_ = scaledDepositAvailable; - redeemedLPs_ = Maths.wdiv(removedAmount_, exchangeRate); + redeemedLP_ = Maths.wdiv(removedAmount_, exchangeRate); } else { - // redeeming all LPs - redeemedLPs_ = params_.lpConstraint; - removedAmount_ = Maths.wmul(redeemedLPs_, exchangeRate); + // redeeming all LP + redeemedLP_ = params_.lpConstraint; + removedAmount_ = Maths.wmul(redeemedLP_, exchangeRate); } // If clearing out the bucket deposit, ensure it's zeroed out - if (redeemedLPs_ == params_.bucketLPs) { + if (redeemedLP_ == params_.bucketLP) { removedAmount_ = scaledDepositAvailable; } diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 95ea4e4e7..dbffe991a 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -47,6 +47,7 @@ library PoolCommons { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `updateInterestState` function local vars. struct UpdateInterestLocalVars { uint256 debtEma; uint256 depositEma; @@ -71,11 +72,11 @@ library PoolCommons { /** * @notice Calculates EMAs, caches values required for calculating interest rate, and saves new values in storage. * @notice Calculates new pool interest rate (Never called more than once every 12 hours) and saves new values in storage. - * @dev write state: - * - EMAs state - * - interest rate accumulator and interestRateUpdate state - * @dev emit events: - * - UpdateInterestRate + * @dev === Write state === + * @dev `EMA`s state + * @dev interest rate accumulator and `interestRateUpdate` state + * @dev === Emit events === + * @dev - `UpdateInterestRate` / `ResetInterestRate` */ function updateInterestState( InterestState storage interestParams_, @@ -200,12 +201,15 @@ library PoolCommons { /** * @notice Calculates new pool interest and scale the fenwick tree to update amount of debt owed to lenders (saved in storage). - * @dev write state: - * - Deposits.mult (scale Fenwick tree with new interest accrued): - * - update scaling array state + * @dev === Write state === + * @dev - `Deposits.mult` (scale `Fenwick` tree with new interest accrued): + * @dev update `scaling` array state + * @param emaParams_ Struct for pool `EMA`s state. + * @param deposits_ Struct for pool deposits state. + * @param poolState_ Current state of the pool. * @param thresholdPrice_ Current Pool Threshold Price. * @param elapsed_ Time elapsed since last inflator update. - * @return newInflator_ The new value of pool inflator. + * @return newInflator_ The new value of pool inflator. */ function accrueInterest( EmaState storage emaParams_, @@ -292,8 +296,8 @@ library PoolCommons { /** * @notice Calculates pool meaningful actual utilization. - * @param debtEma_ EMA of pool debt. - * @param depositEma_ EMA of meaningful pool deposit. + * @param debtEma_ `EMA` of pool debt. + * @param depositEma_ `EMA` of meaningful pool deposit. * @return utilization_ Pool meaningful actual utilization value. */ function _utilization( diff --git a/src/libraries/external/PositionNFTSVG.sol b/src/libraries/external/PositionNFTSVG.sol index f0a5e6270..539f6400c 100644 --- a/src/libraries/external/PositionNFTSVG.sol +++ b/src/libraries/external/PositionNFTSVG.sol @@ -7,7 +7,7 @@ import { Base64 } from '@base64-sol/base64.sol'; /** @title Position NFT SVG library - @notice External library containing logic for generating SVG for a Position NFT. + @notice External library containing logic for generating `SVG` for a Position `NFT`. */ library PositionNFTSVG { diff --git a/src/libraries/external/SettlerActions.sol b/src/libraries/external/SettlerActions.sol index 8dd4c22b9..2075d668d 100644 --- a/src/libraries/external/SettlerActions.sol +++ b/src/libraries/external/SettlerActions.sol @@ -36,7 +36,7 @@ import { Maths } from '../internal/Maths.sol'; /** @title Auction settler library @notice External library containing actions involving auctions within pool: - - settle auctions + - `settle` auctions */ library SettlerActions { @@ -44,12 +44,13 @@ library SettlerActions { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `_settlePoolDebtWithDeposit` function local vars. struct SettleLocalVars { uint256 collateralUsed; // [WAD] collateral used to settle debt uint256 debt; // [WAD] debt to settle uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle - uint256 hpbLPs; // [WAD] amount of LP in HPB bucket + uint256 hpbLP; // [WAD] amount of LP in HPB bucket uint256 index; // index of settling bucket uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral uint256 price; // [WAD] price of settling bucket @@ -64,7 +65,7 @@ library SettlerActions { // See `IPoolEvents` for descriptions event AuctionSettle(address indexed borrower, uint256 collateral); - event AuctionNFTSettle(address indexed borrower, uint256 collateral, uint256 lps, uint256 index); + event AuctionNFTSettle(address indexed borrower, uint256 collateral, uint256 lp, uint256 index); event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event Settle(address indexed borrower, uint256 settledDebt); @@ -81,26 +82,19 @@ library SettlerActions { /***************************/ /** + * @notice See `IPoolSettlerActions` for descriptions. * @notice Settles the debt of the given loan / borrower by performing following steps: * 1. settle debt with `HPB`s deposit, up to specified buckets depth. * 2. settle debt with pool reserves (if there's still debt and no collateral left after step 1). * 3. forgive bad debt from next `HPB`, up to remaining buckets depth (and if there's still debt after step 2). - * @dev write state: - * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): - * - update values array state - * - Buckets.addCollateral: - * - increment bucket.collateral and bucket.lps accumulator - * - addLenderLP: - * - increment lender.lps accumulator and lender.depositTime state - * - update borrower state - * @dev reverts on: - * - loan is not in auction NoAuction() - * - 72 hours didn't pass and auction still has collateral AuctionNotClearable() - * @dev emit events: - * - Settle - * - BucketBankruptcy - * @param params_ Settle params - * @return result_ The result of settle action. + * @dev === Write state === + * @dev update borrower state + * @dev === Reverts on === + * @dev loan is not in auction `NoAuction()` + * @dev `72` hours didn't pass and auction still has collateral `AuctionNotClearable()` + * @dev === Emit events === + * @dev - `Settle` + * @return result_ The `SettleResult` struct result of settle action. */ function settlePoolDebt( AuctionsState storage auctions_, @@ -191,13 +185,16 @@ library SettlerActions { /** * @notice Performs auction settle based on pool type, emits settle event and removes auction from auctions queue. - * @dev emit events: - * - AuctionNFTSettle or AuctionSettle + * @dev === Emit events === + * @dev - `AuctionNFTSettle` or `AuctionSettle` + * @param auctions_ Struct for pool auctions state. + * @param buckets_ Struct for pool buckets state. + * @param deposits_ Struct for pool deposits state. * @param borrowerAddress_ Address of the borrower that exits auction. - * @param borrowerCollateral_ Borrower collateral amount before auction exit (in NFT could be fragmented as result of partial takes). - * @param poolType_ Type of the pool (can be ERC20 or NFT). - * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for ERC20 pool, rounded collateral for NFT pool). - * @return compensatedCollateral_ Amount of collateral compensated (NFT settle only), to be deducted from pool pledged collateral accumulator. 0 for ERC20 pools. + * @param borrowerCollateral_ Borrower collateral amount before auction exit (in `NFT` could be fragmented as result of partial takes). + * @param poolType_ Type of the pool (can be `ERC20` or `ERC721`). + * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for `ERC20` pool, rounded collateral for `ERC721` pool). + * @return compensatedCollateral_ Amount of collateral compensated (`ERC721` settle only), to be deducted from pool pledged collateral accumulator. Always `0` for `ERC20` pools. */ function _settleAuction( AuctionsState storage auctions_, @@ -209,7 +206,7 @@ library SettlerActions { ) internal returns (uint256 remainingCollateral_, uint256 compensatedCollateral_) { if (poolType_ == uint8(PoolType.ERC721)) { - uint256 lps; + uint256 lp; uint256 bucketIndex; remainingCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // floor collateral of borrower @@ -230,7 +227,7 @@ library SettlerActions { bucketIndex = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; // deposit collateral in bucket and reward LP to compensate fractional collateral - lps = Buckets.addCollateral( + lp = Buckets.addCollateral( buckets_[bucketIndex], borrowerAddress_, Deposits.valueAt(deposits_, bucketIndex), @@ -242,7 +239,7 @@ library SettlerActions { emit AuctionNFTSettle( borrowerAddress_, remainingCollateral_, - lps, + lp, bucketIndex ); @@ -260,11 +257,12 @@ library SettlerActions { /** * @notice Removes auction and repairs the queue order. - * @notice Updates kicker's claimable balance with bond size awarded and subtracts bond size awarded from liquidationBondEscrowed. - * @dev write state: - * - decrement kicker locked accumulator, increment kicker claimable accumumlator - * - decrement auctions count accumulator - * - update auction queue state + * @notice Updates kicker's claimable balance with bond size awarded and subtracts bond size awarded from `liquidationBondEscrowed`. + * @dev === Write state === + * @dev decrement kicker locked accumulator, increment kicker claimable accumumlator + * @dev decrement auctions count accumulator + * @dev update auction queue state + * @param auctions_ Struct for pool auctions state. * @param borrower_ Auctioned borrower address. */ function _removeAuction( @@ -307,7 +305,15 @@ library SettlerActions { } /** - * @notice Called to settle debt using `HPB` deposits. + * @notice Called to settle debt using `HPB` deposits, up to the number of specified buckets depth. + * @dev === Write state === + * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): + * @dev update `values` array state + * @dev - `Buckets.addCollateral`: + * @dev increment `bucket.collateral` and `bucket.lps` accumulator + * @dev increment `lender.lps` accumulator and `lender.depositTime` state + * @dev === Emit events === + * @dev - `BucketBankruptcy` * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param params_ Struct containing params for settle action. @@ -370,7 +376,7 @@ library SettlerActions { // use HPB bucket to swap loan collateral for loan debt Bucket storage hpb = buckets_[vars.index]; - vars.hpbLPs = hpb.lps; + vars.hpbLP = hpb.lps; vars.hpbCollateral = hpb.collateral + vars.collateralUsed; // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) @@ -380,14 +386,14 @@ library SettlerActions { // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); - // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LPs - if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLPs != 0) { + // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LP + if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLP != 0) { hpb.lps = 0; hpb.bankruptcyTime = block.timestamp; emit BucketBankruptcy( vars.index, - vars.hpbLPs + vars.hpbLP ); } else { // add settled collateral into bucket @@ -412,7 +418,13 @@ library SettlerActions { } /** - * @notice Called to forgive bad debt starting from next `HPB`. + * @notice Called to forgive bad debt starting from next `HPB`, up to the number of remaining buckets depth. + * @dev === Write state === + * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): + * @dev update `values` array state + * @dev reset `bucket.lps` accumulator and update `bucket.bankruptcyTime` + * @dev === Emit events === + * @dev - `BucketBankruptcy` * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param params_ Struct containing params for settle action. diff --git a/src/libraries/external/TakerActions.sol b/src/libraries/external/TakerActions.sol index e0cbf6662..eb59d6705 100644 --- a/src/libraries/external/TakerActions.sol +++ b/src/libraries/external/TakerActions.sol @@ -42,7 +42,7 @@ import { Maths } from '../internal/Maths.sol'; /** @title Auction Taker Actions library @notice External library containing actions involving taking auctions within pool: - - take auctioned collateral; take reserves + - `take` and `bucketTake` auctioned collateral; take reserves */ library TakerActions { @@ -50,6 +50,7 @@ library TakerActions { /*** Function Params Structs ***/ /*******************************/ + /// @dev Struct used to hold `bucketTake` function params. struct BucketTakeParams { address borrower; // borrower address to take from bool depositTake; // deposit or arb take, used by bucket take @@ -57,6 +58,8 @@ library TakerActions { uint256 inflator; // [WAD] current pool inflator uint256 collateralScale; // precision of collateral token based on decimals } + + /// @dev Struct used to hold `take` function params. struct TakeParams { address borrower; // borrower address to take from uint256 takeCollateral; // [WAD] desired amount to take @@ -69,6 +72,7 @@ library TakerActions { /*** Local Var Structs ***/ /*************************/ + /// @dev Struct used for `take` function local vars. struct TakeLocalVars { uint256 auctionPrice; // [WAD] The price of auction. uint256 bondChange; // [WAD] The change made on the bond size (beeing reward or penalty). @@ -120,13 +124,11 @@ library TakerActions { /***************************/ /** + * @notice See `IPoolTakerActions` for descriptions. * @notice Performs bucket take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). - * @dev reverts on: - * - insufficient collateral InsufficientCollateral() - * @param borrowerAddress_ Borrower address to take. - * @param depositTake_ If true then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. - * @param index_ Index of a bucket, likely the HPB, in which collateral will be deposited. - * @return result_ BucketTakeResult struct containing details of take. + * @dev === Reverts on === + * @dev not enough collateral to take `InsufficientCollateral()` + * @return result_ `BucketTakeResult` struct containing details of bucket take result. */ function bucketTake( AuctionsState storage auctions_, @@ -189,12 +191,11 @@ library TakerActions { } /** + * @notice See `IPoolTakerActions` for descriptions. * @notice Performs take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). - * @dev reverts on: - * - insufficient collateral InsufficientCollateral() - * @param borrowerAddress_ Borrower address to take. - * @param collateral_ Max amount of collateral that will be taken from the auction (max number of NFTs in case of ERC721 pool). - * @return result_ TakeResult struct containing details of take. + * @dev === Reverts on === + * @dev insufficient collateral to take `InsufficientCollateral()` + * @return result_ `TakeResult` struct containing details of take result. */ function take( AuctionsState storage auctions_, @@ -268,13 +269,13 @@ library TakerActions { /*************************/ /** - * @notice See `IPoolReserveAuctionActions` for descriptions. - * @dev write state: - * - decrement reserveAuction.unclaimed accumulator - * @dev reverts on: - * - not kicked or 72 hours didn't pass NoReservesAuction() - * @dev emit events: - * - ReserveAuction + * @notice See `IPoolTakerActions` for descriptions. + * @dev === Write state === + * @dev decrement `reserveAuction.unclaimed` accumulator + * @dev === Reverts on === + * @dev not kicked or `72` hours didn't pass `NoReservesAuction()` + * @dev === Emit events === + * @dev - `ReserveAuction` */ function takeReserves( ReserveAuctionState storage reserveAuction_, @@ -324,8 +325,9 @@ library TakerActions { /** * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. - * @dev emit events: - * - Take + * @dev === Emit events === + * @dev - `Take` + * @param auctions_ Struct for pool auctions state. * @param borrower_ Struct containing auctioned borrower details. * @param params_ Struct containing take action params details. * @return vars_ Struct containing auction take vars. @@ -396,8 +398,11 @@ library TakerActions { /** * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). - * @dev emit events: - * - BucketTake + * @dev === Emit events === + * @dev - `BucketTake` + * @param auctions_ Struct for pool auctions state. + * @param buckets_ Struct for pool buckets state. + * @param deposits_ Struct for pool deposits state. * @param borrower_ Struct containing auctioned borrower details. * @param params_ Struct containing take action details. * @return vars_ Struct containing auction take vars. @@ -466,14 +471,18 @@ library TakerActions { /** * @notice Performs update of an auctioned loan that was taken (using bucket or regular take). * @notice If borrower becomes recollateralized then auction is settled. Update loan's state. - * @dev reverts on: - * - borrower debt less than pool min debt AmountLTMinDebt() - * @param borrower_ Struct containing pool details. + * @dev === Reverts on === + * @dev borrower debt less than pool min debt `AmountLTMinDebt()` + * @param auctions_ Struct for pool auctions state. + * @param buckets_ Struct for pool buckets state. + * @param deposits_ Struct for pool deposits state. + * @param loans_ Struct for pool loans state. + * @param poolState_ Struct containing pool details. * @param borrower_ The borrower details owning loan that is taken. * @param borrowerAddress_ The address of the borrower. - * @return newLup_ The new LUP of pool (after debt is repaid). - * @return settledAuction_ True if auction is settled by the take action. (NFT take: rebalance borrower collateral in pool if true) - * @return remainingCollateral_ Borrower collateral remaining after take action. (NFT take: collateral to be rebalanced in case of NFT settlement) + * @return newLup_ The new `LUP` of pool (after debt is repaid). + * @return settledAuction_ True if auction is settled by the take action. (`NFT` take: rebalance borrower collateral in pool if true) + * @return remainingCollateral_ Borrower collateral remaining after take action. (`NFT` take: collateral to be rebalanced in case of `NFT` settlement) * @return compensatedCollateral_ Amount of collateral compensated, to be deducted from pool pledged collateral accumulator. */ function _takeLoan( @@ -539,11 +548,13 @@ library TakerActions { /** * @notice Rewards actors of a regular take action. - * @dev write state: - * - update liquidation bond size accumulator - * - update kicker's locked balance accumulator - * - update auctions.totalBondEscrowed accumulator - * @param vars Struct containing take action result details. + * @dev === Write state === + * @dev update liquidation `bond size` accumulator + * @dev update kicker's `locked balance` accumulator + * @dev update `auctions.totalBondEscrowed` accumulator + * @param auctions_ Struct for pool auctions state. + * @param liquidation_ Struct containing details of auction. + * @param vars Struct containing take action result details. */ function _rewardTake( AuctionsState storage auctions_, @@ -567,19 +578,25 @@ library TakerActions { /** * @notice Rewards actors of a bucket take action. - * @dev write state: - * - Buckets.addLenderLP: - * - increment taker lender.lps accumulator and lender.depositTime state - * - increment kicker lender.lps accumulator and lender.depositTime state - * - update liquidation bond size accumulator - * - update kicker's locked balance accumulator - * - update auctions.totalBondEscrowed accumulator - * - Deposits.unscaledRemove() (remove amount in Fenwick tree, from index): - * - update values array state - * - increment bucket.collateral and bucket.lps accumulator - * @dev emit events: - * - BucketTakeLPAwarded - * @param vars Struct containing take action result details. + * @dev === Write state === + * @dev - `Buckets.addLenderLP`: + * @dev increment taker `lender.lps` accumulator and `lender.depositTime` state + * @dev increment kicker `lender.lps` accumulator and l`ender.depositTime` state + * @dev - update liquidation bond size accumulator + * @dev - update kicker's locked balance accumulator + * @dev - update `auctions.totalBondEscrowed` accumulator + * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): + * @dev update `values` array state + * @dev - increment `bucket.collateral` and `bucket.lps` accumulator + * @dev === Emit events === + * @dev - `BucketTakeLPAwarded` + * @param auctions_ Struct for pool auctions state. + * @param deposits_ Struct for pool deposits state. + * @param buckets_ Struct for pool buckets state. + * @param liquidation_ Struct containing details of auction to be taken from. + * @param bucketIndex_ Index of a bucket, likely the `HPB`, in which collateral will be deposited. + * @param depositTake_ If `true` then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. + * @param vars Struct containing bucket take action result details. */ function _rewardBucketTake( AuctionsState storage auctions_, @@ -602,25 +619,25 @@ library TakerActions { ); uint256 bankruptcyTime = bucket.bankruptcyTime; - uint256 totalLPsReward; + uint256 totalLPReward; // if arb take - taker is awarded collateral * (bucket price - auction price) worth (in quote token terms) units of LPB in the bucket if (!depositTake_) { uint256 takerReward = Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice); - totalLPsReward = Maths.wdiv(takerReward, exchangeRate); + totalLPReward = Maths.wdiv(takerReward, exchangeRate); - Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, totalLPsReward); + Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, totalLPReward); } - uint256 kickerLPsReward; + uint256 kickerLPReward; // the bondholder/kicker is awarded bond change worth of LPB in the bucket if (vars.isRewarded) { - kickerLPsReward = Maths.wdiv(vars.bondChange, exchangeRate); - totalLPsReward += kickerLPsReward; + kickerLPReward = Maths.wdiv(vars.bondChange, exchangeRate); + totalLPReward += kickerLPReward; - Buckets.addLenderLP(bucket, bankruptcyTime, vars.kicker, kickerLPsReward); + Buckets.addLenderLP(bucket, bankruptcyTime, vars.kicker, kickerLPReward); } else { // take is above neutralPrice, Kicker is penalized vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); @@ -634,7 +651,7 @@ library TakerActions { Deposits.unscaledRemove(deposits_, bucketIndex_, vars.unscaledQuoteTokenAmount); // remove quote tokens from bucket’s deposit // total rewarded LP are added to the bucket LP balance - bucket.lps += totalLPsReward; + bucket.lps += totalLPReward; // collateral is added to the bucket’s claimable collateral bucket.collateral += vars.collateralAmount; @@ -642,8 +659,8 @@ library TakerActions { emit BucketTakeLPAwarded( msg.sender, vars.kicker, - totalLPsReward - kickerLPsReward, - kickerLPsReward + totalLPReward - kickerLPReward, + kickerLPReward ); } diff --git a/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index 3e0285e1e..3125d365b 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -16,33 +16,30 @@ import { Maths } from '../internal/Maths.sol'; /*** Price Conversions ***/ /*************************/ - /** - @dev constant price indices defining the min and max of the potential price range - */ + /// @dev constant price indices defining the min and max of the potential price range int256 constant MAX_BUCKET_INDEX = 4_156; int256 constant MIN_BUCKET_INDEX = -3_232; uint256 constant MAX_FENWICK_INDEX = 7_388; uint256 constant MIN_PRICE = 99_836_282_890; uint256 constant MAX_PRICE = 1_004_968_987.606512354182109771 * 1e18; - /** - @dev step amounts in basis points. This is a constant across pools at .005, achieved by dividing WAD by 10,000 - */ + + /// @dev step amounts in basis points. This is a constant across pools at `0.005`, achieved by dividing `WAD` by `10,000` int256 constant FLOAT_STEP_INT = 1.005 * 1e18; /** - * @notice Calculates the price for a given Fenwick index - * @dev Throws if index exceeds maximum constant - * @dev Uses fixed-point math to get around lack of floating point numbers in EVM - * @dev Price expected to be inputted as a 18 decimal WAD - * @dev Fenwick index is converted to bucket index - * @dev Fenwick index to bucket index conversion - * 1.00 : bucket index 0, fenwick index 4156: 7388-4156-3232=0 - * MAX_PRICE : bucket index 4156, fenwick index 0: 7388-0-3232=4156. - * MIN_PRICE : bucket index -3232, fenwick index 7388: 7388-7388-3232=-3232. - * @dev V1: price = MIN_PRICE + (FLOAT_STEP * index) - * V2: price = MAX_PRICE * (FLOAT_STEP ** (abs(int256(index - MAX_PRICE_INDEX)))); - * V3 (final): x^y = 2^(y*log_2(x)) + * @notice Calculates the price for a given `Fenwick` index. + * @dev Reverts with `BucketIndexOutOfBounds` if index exceeds maximum constant. + * @dev Uses fixed-point math to get around lack of floating point numbers in `EVM`. + * @dev Price expected to be inputted as a `WAD` (`18` decimal). + * @dev Fenwick index is converted to bucket index. + * @dev Fenwick index to bucket index conversion: + * @dev `1.00` : bucket index `0`, fenwick index `4156`: `7388-4156-3232=0`. + * @dev `MAX_PRICE` : bucket index `4156`, fenwick index `0`: `7388-0-3232=4156`. + * @dev `MIN_PRICE` : bucket index - `3232`, fenwick index `7388`: `7388-7388-3232=-3232`. + * @dev `V1`: `price = MIN_PRICE + (FLOAT_STEP * index)` + * @dev `V2`: `price = MAX_PRICE * (FLOAT_STEP ** (abs(int256(index - MAX_PRICE_INDEX))));` + * @dev `V3 (final)`: `x^y = 2^(y*log_2(x))` */ function _priceAt( uint256 index_ @@ -62,13 +59,13 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Calculates the Fenwick index for a given price - * @dev Throws if price exceeds maximum constant - * @dev Price expected to be inputted as a 18 decimal WAD - * @dev V1: bucket index = (price - MIN_PRICE) / FLOAT_STEP - * V2: bucket index = (log(FLOAT_STEP) * price) / MAX_PRICE - * V3 (final): bucket index = log_2(price) / log_2(FLOAT_STEP) - * @dev Fenwick index = 7388 - bucket index + 3232 + * @notice Calculates the Fenwick index for a given price. + * @dev Reverts with `BucketPriceOutOfBounds` if price exceeds maximum constant. + * @dev Price expected to be inputted as a `WAD` (`18` decimal). + * @dev `V1`: `bucket index = (price - MIN_PRICE) / FLOAT_STEP` + * @dev `V2`: `bucket index = (log(FLOAT_STEP) * price) / MAX_PRICE` + * @dev `V3 (final)`: `bucket index = log_2(price) / log_2(FLOAT_STEP)` + * @dev `Fenwick index = 7388 - bucket index + 3232` */ function _indexOf( uint256 price_ @@ -108,7 +105,7 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Calculates origination fee for a given interest rate. - * @notice Calculated as greater of the current annualized interest rate divided by 52 (one week of interest) or 5 bps. + * @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps. * @param interestRate_ The current interest rate. * @return Fee rate based upon the given interest rate. */ @@ -120,7 +117,7 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Calculates the unutilized deposit fee, charged to lenders who deposit below the LUP. + * @notice Calculates the unutilized deposit fee, charged to lenders who deposit below the `LUP`. * @param interestRate_ The current interest rate. * @return Fee rate based upon the given interest rate. */ @@ -132,10 +129,10 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Calculates debt-weighted average threshold price - * @param t0Debt_ pool debt owed by borrowers in t0 terms - * @param inflator_ pool's borrower inflator - * @param t0Debt2ToCollateral_ t0-debt-squared-to-collateral accumulator + * @notice Calculates debt-weighted average threshold price. + * @param t0Debt_ Pool debt owed by borrowers in `t0` terms. + * @param inflator_ Pool's borrower inflator. + * @param t0Debt2ToCollateral_ `t0-debt-squared-to-collateral` accumulator. */ function _dwatp( uint256 t0Debt_, @@ -151,7 +148,7 @@ import { Maths } from '../internal/Maths.sol'; * @param collateral_ Collateral to calculate collateralization for. * @param price_ Price to calculate collateralization for. * @param type_ Type of the pool. - * @return True if collateralization calculated is equal or greater than 1. + * @return `True` if collateralization calculated is equal or greater than `1`. */ function _isCollateralized( uint256 debt_, @@ -171,7 +168,7 @@ import { Maths } from '../internal/Maths.sol'; * @notice Price precision adjustment used in calculating collateral dust for a bucket. * To ensure the accuracy of the exchange rate calculation, buckets with smaller prices require * larger minimum amounts of collateral. This formula imposes a lower bound independent of token scale. - * @param bucketIndex_ Index of the bucket, or 0 for encumbered collateral with no bucket affinity. + * @param bucketIndex_ Index of the bucket, or `0` for encumbered collateral with no bucket affinity. * @return pricePrecisionAdjustment_ Unscaled integer of the minimum number of decimal places the dust limit requires. */ function _getCollateralDustPricePrecisionAdjustment( @@ -186,25 +183,25 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Returns the amount of collateral calculated for the given amount of LP. + * @notice Returns the amount of collateral calculated for the given amount of `LP`. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LP in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. - * @param lenderLPsBalance_ The amount of LP to calculate collateral for. - * @param bucketPrice_ Bucket price. - * @return collateralAmount_ Amount of collateral calculated for the given LP amount. + * @param bucketLP_ Amount of `LP` in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. + * @param lenderLPBalance_ The amount of `LP` to calculate collateral for. + * @param bucketPrice_ Bucket's price. + * @return collateralAmount_ Amount of collateral calculated for the given `LP `amount. */ function _lpToCollateral( uint256 bucketCollateral_, - uint256 bucketLPs_, + uint256 bucketLP_, uint256 deposit_, - uint256 lenderLPsBalance_, + uint256 lenderLPBalance_, uint256 bucketPrice_ ) pure returns (uint256 collateralAmount_) { - // max collateral to lps - uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); + // max collateral to lp + uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLP_, deposit_, bucketPrice_); - collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLPsBalance_, rate), bucketPrice_); + collateralAmount_ = Maths.wdiv(Maths.wmul(lenderLPBalance_, rate), bucketPrice_); if (collateralAmount_ > bucketCollateral_) { // user is owed more collateral than is available in the bucket @@ -213,26 +210,26 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Returns the amount of quote tokens calculated for the given amount of LP. - * @param bucketLPs_ Amount of LP in bucket. + * @notice Returns the amount of quote tokens calculated for the given amount of `LP`. + * @param bucketLP_ Amount of `LP` in bucket. * @param bucketCollateral_ Amount of collateral in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. - * @param lenderLPsBalance_ The amount of LP to calculate quote token amount for. - * @param maxQuoteToken_ The max quote token amount to calculate LP for. - * @param bucketPrice_ Bucket price. - * @return quoteTokenAmount_ Amount of quote tokens calculated for the given LP amount. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. + * @param lenderLPBalance_ The amount of `LP` to calculate quote token amount for. + * @param maxQuoteToken_ The max quote token amount to calculate `LP` for. + * @param bucketPrice_ Bucket's price. + * @return quoteTokenAmount_ Amount of quote tokens calculated for the given `LP` amount. */ function _lpToQuoteToken( - uint256 bucketLPs_, + uint256 bucketLP_, uint256 bucketCollateral_, uint256 deposit_, - uint256 lenderLPsBalance_, + uint256 lenderLPBalance_, uint256 maxQuoteToken_, uint256 bucketPrice_ ) pure returns (uint256 quoteTokenAmount_) { - uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); + uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLP_, deposit_, bucketPrice_); - quoteTokenAmount_ = Maths.wmul(lenderLPsBalance_, rate); + quoteTokenAmount_ = Maths.wmul(lenderLPBalance_, rate); if (quoteTokenAmount_ > deposit_) quoteTokenAmount_ = deposit_; if (quoteTokenAmount_ > maxQuoteToken_) quoteTokenAmount_ = maxQuoteToken_; @@ -241,7 +238,7 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Rounds a token amount down to the minimum amount permissible by the token scale. * @param amount_ Value to be rounded. - * @param tokenScale_ Scale of the token, presented as a power of 10. + * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ function _roundToScale( @@ -254,7 +251,7 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Rounds a token amount up to the next amount permissible by the token scale. * @param amount_ Value to be rounded. - * @param tokenScale_ Scale of the token, presented as a power of 10. + * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ function _roundUpToScale( @@ -273,6 +270,15 @@ import { Maths } from '../internal/Maths.sol'; uint256 constant MINUTE_HALF_LIFE = 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) + /** + * @notice Calculates claimable reserves within the pool. + * @param debt_ Pool's debt. + * @param poolSize_ Pool's deposit size. + * @param totalBondEscrowed_ Total bond escrowed. + * @param reserveAuctionUnclaimed_ Pool's unclaimed reserve auction. + * @param quoteTokenBalance_ Pool's quote token balance. + * @return claimable_ Calculated pool reserves. + */ function _claimableReserves( uint256 debt_, uint256 poolSize_, @@ -285,15 +291,20 @@ import { Maths } from '../internal/Maths.sol'; claimable_ -= Maths.min(claimable_, poolSize_ + totalBondEscrowed_ + reserveAuctionUnclaimed_); } + /** + * @notice Calculates reserves auction price. + * @param reserveAuctionKicked_ Time when reserve auction was started (kicked). + * @return price_ Calculated auction price. + */ function _reserveAuctionPrice( uint256 reserveAuctionKicked_ - ) view returns (uint256 _price) { + ) view returns (uint256 price_) { if (reserveAuctionKicked_ != 0) { uint256 secondsElapsed = block.timestamp - reserveAuctionKicked_; uint256 hoursComponent = 1e27 >> secondsElapsed / 3600; uint256 minutesComponent = Maths.rpow(MINUTE_HALF_LIFE, secondsElapsed % 3600 / 60); - _price = Maths.rayToWad(1_000_000_000 * Maths.rmul(hoursComponent, minutesComponent)); + price_ = Maths.rayToWad(1_000_000_000 * Maths.rmul(hoursComponent, minutesComponent)); } } @@ -303,10 +314,10 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Calculates auction price. - * @param kickMomp_ MOMP recorded at the time of kick. - * @param neutralPrice_ Neutral Price of the auction. - * @param kickTime_ Time when auction was kicked. - * @return price_ Calculated auction price. + * @param kickMomp_ `MOMP` recorded at the time of kick. + * @param neutralPrice_ `Neutral Price` of the auction. + * @param kickTime_ Time when auction was kicked. + * @return price_ Calculated auction price. */ function _auctionPrice( uint256 kickMomp_, @@ -328,10 +339,10 @@ import { Maths } from '../internal/Maths.sol'; * @dev Called in kick and take. * @param debt_ Borrower debt. * @param collateral_ Borrower collateral. - * @param neutralPrice_ NP of auction. + * @param neutralPrice_ `NP` of auction. * @param bondFactor_ Factor used to determine bondSize. * @param auctionPrice_ Auction price at the time of call. - * @return bpf_ Factor used in determining bond Reward (positive) or penalty (negative). + * @return bpf_ Factor used in determining bond `reward` (positive) or `penalty` (negative). */ function _bpf( uint256 debt_, @@ -368,7 +379,7 @@ import { Maths } from '../internal/Maths.sol'; * @notice Calculates bond parameters of an auction. * @param borrowerDebt_ Borrower's debt before entering in liquidation. * @param collateral_ Borrower's collateral before entering in liquidation. - * @param momp_ Current pool momp. + * @param momp_ Current pool `momp`. */ function _bondParams( uint256 borrowerDebt_, diff --git a/src/libraries/helpers/RevertsHelper.sol b/src/libraries/helpers/RevertsHelper.sol index 89f34535e..fa04283bf 100644 --- a/src/libraries/helpers/RevertsHelper.sol +++ b/src/libraries/helpers/RevertsHelper.sol @@ -25,8 +25,9 @@ import { Maths } from '../internal/Maths.sol'; error TransactionExpired(); /** - * @notice Called by LPB removal functions assess whether or not LPB is locked. - * @param index_ The deposit index from which LPB is attempting to be removed. + * @notice Called by `LP` removal functions assess whether or not `LP` is locked. + * @dev Reverts with `RemoveDepositLockedByAuctionDebt` if debt locked. + * @param index_ The deposit index from which `LP` is attempting to be removed. * @param inflator_ The pool inflator used to properly assess t0 debt in auctions. */ function _revertIfAuctionDebtLocked( @@ -43,8 +44,8 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Check if head auction is clearable (auction is kicked and 72 hours passed since kick time or auction still has debt but no remaining collateral). - * @notice Revert if auction is clearable + * @notice Check if head auction is clearable (auction is kicked and `72` hours passed since kick time or auction still has debt but no remaining collateral). + * @dev Reverts with `AuctionNotCleared` if auction is clearable. */ function _revertIfAuctionClearable( AuctionsState storage auctions_, @@ -61,10 +62,11 @@ import { Maths } from '../internal/Maths.sol'; } /** - * @notice Check if provided price is at or above index limit provided by borrower. - * @notice Prevents stale transactions and certain MEV manipulations. - * @param newPrice_ New price to be compared with given limit price (can be LUP, NP). - * @param limitIndex_ Limit price index provided by user creating the TX. + * @notice Check if provided price is at or above index limit provided by borrower. + * @notice Prevents stale transactions and certain `MEV` manipulations. + * @dev Reverts with `LimitIndexExceeded` if index limit provided exceeded. + * @param newPrice_ New price to be compared with given limit price (can be `LUP`, `NP`). + * @param limitIndex_ Limit price index provided by user creating the transaction. */ function _revertIfPriceDroppedBelowLimit( uint256 newPrice_, @@ -76,7 +78,8 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Check if expiration provided by user has met or exceeded current block height timestamp. * @notice Prevents stale transactions interacting with the pool at potentially unfavorable prices. - * @param expiry_ Expiration provided by user when creating the TX. + * @dev Reverts with `TransactionExpired` if expired. + * @param expiry_ Expiration provided by user when creating the transaction. */ function _revertOnExpiry( uint256 expiry_ @@ -86,10 +89,11 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Called when borrower debt changes, ensuring minimum debt rules are honored. - * @param loans_ Loans heap, used to determine loan count. - * @param poolDebt_ Total pool debt, used to calculate average debt. - * @param borrowerDebt_ New debt for the borrower, assuming the current transaction succeeds. - * @param quoteDust_ Smallest amount of quote token when can be transferred, determined by token scale. + * @dev Reverts with `DustAmountNotExceeded` if under dust amount or with `AmountLTMinDebt` if amount under min debt value. + * @param loans_ Loans heap, used to determine loan count. + * @param poolDebt_ Total pool debt, used to calculate average debt. + * @param borrowerDebt_ New debt for the borrower, assuming the current transaction succeeds. + * @param quoteDust_ Smallest amount of quote token when can be transferred, determined by token scale. */ function _revertOnMinDebt( LoansState storage loans_, diff --git a/src/libraries/internal/Buckets.sol b/src/libraries/internal/Buckets.sol index e374aa4c8..9a52cbdd3 100644 --- a/src/libraries/internal/Buckets.sol +++ b/src/libraries/internal/Buckets.sol @@ -24,15 +24,15 @@ library Buckets { /***********************************/ /** - * @notice Add collateral to a bucket and updates LP for bucket and lender with the amount coresponding to collateral amount added. - * @dev Increment bucket.collateral and bucket.lps accumulator - * - addLenderLP: - * - increment lender.lps accumulator and lender.depositTime state + * @notice Add collateral to a bucket and updates `LP` for bucket and lender with the amount coresponding to collateral amount added. + * @dev Increment `bucket.collateral` and `bucket.lps` accumulator + * @dev - `addLenderLP`: + * @dev increment `lender.lps` accumulator and `lender.depositTime` state * @param lender_ Address of the lender. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param collateralAmountToAdd_ Additional collateral amount to add to bucket. * @param bucketPrice_ Bucket price. - * @return addedLPs_ Amount of bucket LP for the collateral amount added. + * @return addedLP_ Amount of bucket `LP` for the collateral amount added. */ function addCollateral( Bucket storage bucket_, @@ -40,13 +40,13 @@ library Buckets { uint256 deposit_, uint256 collateralAmountToAdd_, uint256 bucketPrice_ - ) internal returns (uint256 addedLPs_) { + ) internal returns (uint256 addedLP_) { // cannot deposit in the same block when bucket becomes insolvent uint256 bankruptcyTime = bucket_.bankruptcyTime; if (bankruptcyTime == block.timestamp) revert BucketBankruptcyBlock(); // calculate amount of LP to be added for the amount of collateral added to bucket - addedLPs_ = collateralToLP( + addedLP_ = collateralToLP( bucket_.collateral, bucket_.lps, deposit_, @@ -58,30 +58,30 @@ library Buckets { // update bucket collateral bucket_.collateral += collateralAmountToAdd_; // update bucket and lender LP balance and deposit timestamp - bucket_.lps += addedLPs_; + bucket_.lps += addedLP_; - addLenderLP(bucket_, bankruptcyTime, lender_, addedLPs_); + addLenderLP(bucket_, bankruptcyTime, lender_, addedLP_); } /** - * @notice Add amount of LP for a given lender in a given bucket. + * @notice Add amount of `LP` for a given lender in a given bucket. * @dev Increments lender lps accumulator and updates the deposit time. - * @param bucket_ Bucket to record lender LP. + * @param bucket_ Bucket to record lender `LP`. * @param bankruptcyTime_ Time when bucket become insolvent. - * @param lender_ Lender address to add LP for in the given bucket. - * @param lpsAmount_ Amount of LP to be recorded for the given lender. + * @param lender_ Lender address to add `LP` for in the given bucket. + * @param lpAmount_ Amount of `LP` to be recorded for the given lender. */ function addLenderLP( Bucket storage bucket_, uint256 bankruptcyTime_, address lender_, - uint256 lpsAmount_ + uint256 lpAmount_ ) internal { - if (lpsAmount_ != 0) { + if (lpAmount_ != 0) { Lender storage lender = bucket_.lenders[lender_]; - if (bankruptcyTime_ >= lender.depositTime) lender.lps = lpsAmount_; - else lender.lps += lpsAmount_; + if (bankruptcyTime_ >= lender.depositTime) lender.lps = lpAmount_; + else lender.lps += lpAmount_; lender.depositTime = block.timestamp; } @@ -92,62 +92,62 @@ library Buckets { /**********************/ /** - * @notice Returns the amount of bucket LP calculated for the given amount of collateral. + * @notice Returns the amount of bucket `LP` calculated for the given amount of collateral. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LP in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. - * @param collateral_ The amount of collateral to calculate bucket LP for. - * @param bucketPrice_ Price bucket. - * @return lps_ Amount of LP calculated for the amount of collateral. + * @param bucketLP_ Amount of `LP` in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. + * @param collateral_ The amount of collateral to calculate bucket LP for. + * @param bucketPrice_ Bucket's price. + * @return lp_ Amount of `LP` calculated for the amount of collateral. */ function collateralToLP( uint256 bucketCollateral_, - uint256 bucketLPs_, + uint256 bucketLP_, uint256 deposit_, uint256 collateral_, uint256 bucketPrice_ - ) internal pure returns (uint256 lps_) { - uint256 rate = getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_); + ) internal pure returns (uint256 lp_) { + uint256 rate = getExchangeRate(bucketCollateral_, bucketLP_, deposit_, bucketPrice_); - lps_ = Maths.wdiv(Maths.wmul(collateral_, bucketPrice_), rate); + lp_ = Maths.wdiv(Maths.wmul(collateral_, bucketPrice_), rate); } /** - * @notice Returns the amount of LP calculated for the given amount of quote tokens. + * @notice Returns the amount of `LP` calculated for the given amount of quote tokens. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LP in bucket. - * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / LP. - * @param quoteTokens_ The amount of quote tokens to calculate LP amount for. - * @param bucketPrice_ Price bucket. - * @return The amount of LP coresponding to the given quote tokens in current bucket. + * @param bucketLP_ Amount of `LP` in bucket. + * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. + * @param quoteTokens_ The amount of quote tokens to calculate `LP` amount for. + * @param bucketPrice_ Bucket's price. + * @return The amount of `LP` coresponding to the given quote tokens in current bucket. */ function quoteTokensToLP( uint256 bucketCollateral_, - uint256 bucketLPs_, + uint256 bucketLP_, uint256 deposit_, uint256 quoteTokens_, uint256 bucketPrice_ ) internal pure returns (uint256) { return Maths.wdiv( quoteTokens_, - getExchangeRate(bucketCollateral_, bucketLPs_, deposit_, bucketPrice_) + getExchangeRate(bucketCollateral_, bucketLP_, deposit_, bucketPrice_) ); } /** * @notice Returns the exchange rate for a given bucket. * @param bucketCollateral_ Amount of collateral in bucket. - * @param bucketLPs_ Amount of LP in bucket. + * @param bucketLP_ Amount of `LP` in bucket. * @param bucketDeposit_ The amount of quote tokens deposited in the given bucket. * @param bucketPrice_ Bucket's price. */ function getExchangeRate( uint256 bucketCollateral_, - uint256 bucketLPs_, + uint256 bucketLP_, uint256 bucketDeposit_, uint256 bucketPrice_ ) internal pure returns (uint256) { - return bucketLPs_ == 0 ? Maths.WAD : - Maths.wdiv(bucketDeposit_ + Maths.wmul(bucketPrice_, bucketCollateral_), bucketLPs_); + return bucketLP_ == 0 ? Maths.WAD : + Maths.wdiv(bucketDeposit_ + Maths.wmul(bucketPrice_, bucketCollateral_), bucketLP_); } } diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index 8003f6eeb..2b2e64266 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -11,15 +11,15 @@ import { Maths } from './Maths.sol'; /** @title Deposits library @notice Internal library containing common logic for deposits management. - @dev Implemented as Fenwick Tree data structure. + @dev Implemented as `Fenwick Tree` data structure. */ library Deposits { - // Max index supported in the Fenwick tree + /// @dev Max index supported in the `Fenwick` tree uint256 internal constant SIZE = 8192; /** - * @notice increase a value in the FenwickTree at an index. + * @notice Increase a value in the FenwickTree at an index. * @dev Starts at leaf/target and moved up towards root * @param index_ The deposit index. * @param unscaledAddAmount_ The unscaled amount to increase deposit by. @@ -64,11 +64,11 @@ library Deposits { /** * @notice Finds index and sum of first bucket that EXCEEDS the given sum - * @dev Used in lup calculation + * @dev Used in `LUP` calculation * @param targetSum_ The sum to find index for. - * @return sumIndex_ Smallest index where prefixsum greater than the sum - * @return sumIndexSum_ Sum at index PRECEDING sumIndex_ - * @return sumIndexScale_ Scale of bucket PRECEDING sumIndex_ + * @return sumIndex_ Smallest index where prefixsum greater than the sum. + * @return sumIndexSum_ Sum at index PRECEDING `sumIndex_`. + * @return sumIndexScale_ Scale of bucket PRECEDING `sumIndex_`. */ function findIndexAndSumOfSum( DepositsState storage deposits_, @@ -114,10 +114,10 @@ library Deposits { } /** - * @notice Finds index of passed sum. Helper function for findIndexAndSumOfSum - * @dev Used in lup calculation + * @notice Finds index of passed sum. Helper function for `findIndexAndSumOfSum`. + * @dev Used in `LUP` calculation * @param sum_ The sum to find index for. - * @return sumIndex_ Smallest index where prefixsum greater than the sum + * @return sumIndex_ Smallest index where prefixsum greater than the sum. */ function findIndexOfSum( DepositsState storage deposits_, @@ -127,9 +127,9 @@ library Deposits { } /** - * @notice Get least significant bit (LSB) of intiger, i_. + * @notice Get least significant bit (`LSB`) of integer `i_`. * @dev Used primarily to decrement the binary index in loops, iterating over range parents. - * @param i_ The integer with which to return the LSB. + * @param i_ The integer with which to return the `LSB`. */ function lsb( uint256 i_ @@ -142,7 +142,7 @@ library Deposits { /** * @notice Scale values in the tree from the index provided, upwards. - * @dev Starts at passed in node and increments through range parent nodes, and ends at 8192. + * @dev Starts at passed in node and increments through range parent nodes, and ends at `8192`. * @param index_ The index to start scaling from. * @param factor_ The factor to scale the values by. */ @@ -168,7 +168,7 @@ library Deposits { // subtree what was scaled earlier. Therefore: we need to increment it's stored value // (in sum) which was set in a prior interation in case 1. while (bit <= SIZE) { - if((bit & index_) != 0) { + if ((bit & index_) != 0) { // Case 1 as described above value = deposits_.values[index_]; scaling = deposits_.scaling[index_]; @@ -210,7 +210,7 @@ library Deposits { /** * @notice Get prefix sum of all indexes from provided index downwards. - * @dev Starts at tree root and decrements through range parent nodes summing from index i_'s range to index 0. + * @dev Starts at tree root and decrements through range parent nodes summing from index `sumIndex_`'s range to index `0`. * @param sumIndex_ The index to receive the prefix sum. * @param sum_ The prefix sum from current index downwards. */ @@ -232,13 +232,13 @@ library Deposits { // Skip considering indices outside bounds of Fenwick tree if (index + j > SIZE) continue; - // We are considering whether to include node index_+j in the sum or not. Either way, we need to scaling[index_+j], + // We are considering whether to include node index + j in the sum or not. Either way, we need to scaling[index + j], // either to increment sum_ or to accumulate in runningScale - uint256 scaled = deposits_.scaling[index+j]; + uint256 scaled = deposits_.scaling[index + j]; if (sumIndex_ & j != 0) { - // node index+j of tree is included in sum - uint256 value = deposits_.values[index+j]; + // node index + j of tree is included in sum + uint256 value = deposits_.values[index + j]; // Accumulate in sum_, recall that scaled==0 means that the scale factor is actually 1 sum_ += scaled != 0 ? (runningScale * scaled * value + 5e35) / 1e36 : Maths.wmul(runningScale, value); @@ -257,10 +257,10 @@ library Deposits { } /** - * @notice Decrease a node in the FenwickTree at an index. - * @dev Starts at leaf/target and moved up towards root - * @param index_ The deposit index. - * @param unscaledRemoveAmount_ Unscaled amount to decrease deposit by. + * @notice Decrease a node in the `FenwickTree` at an index. + * @dev Starts at leaf/target and moved up towards root. + * @param index_ The deposit index. + * @param unscaledRemoveAmount_ Unscaled amount to decrease deposit by. */ function unscaledRemove( DepositsState storage deposits_, @@ -293,7 +293,7 @@ library Deposits { /** * @notice Scale tree starting from given index. - * @dev Starts at leaf/target and moved up towards root + * @dev Starts at leaf/target and moved up towards root. * @param index_ The deposit index. * @return scaled_ Scaled value. */ @@ -364,9 +364,9 @@ library Deposits { } /** - * @notice Returns LUP for a given debt value (capped at min bucket price). - * @param debt_ The debt amount to calculate LUP for. - * @return LUP for given debt. + * @notice Returns `LUP` for a given debt value (capped at min bucket price). + * @param debt_ The debt amount to calculate `LUP` for. + * @return `LUP` for given debt. */ function getLup( DepositsState storage deposits_, diff --git a/src/libraries/internal/Loans.sol b/src/libraries/internal/Loans.sol index ff1bf1200..6cab47a6e 100644 --- a/src/libraries/internal/Loans.sol +++ b/src/libraries/internal/Loans.sol @@ -18,7 +18,11 @@ import { Maths } from './Maths.sol'; /** @title Loans library @notice Internal library containing common logic for loans management. - @dev Implemented as Max Heap data structure. + @dev The `Loans` heap is a `Max Heap` data structure (complete binary tree), the root node is the loan with the highest threshold price (`TP`) + at a given time. The heap is represented as an array, where the first element is a dummy element (`Loan(address(0), 0)`) and the first + value of the heap starts at index `1`, `ROOT_INDEX`. The threshold price of a loan's parent is always greater than or equal to the + threshold price of the loan. + @dev This code was modified from the following source: https://github.com/zmitton/eth-heap/blob/master/contracts/Heap.sol */ library Loans { @@ -37,34 +41,28 @@ library Loans { /** * @notice Initializes Loans Max Heap. - * @dev Organizes loans so Highest Threshold Price can be retreived easily. - * @param loans_ Holds Loan heap data. + * @dev Organizes loans so `Highest Threshold Price` can be retrieved easily. + * @param loans_ Holds Loan heap data. */ function init(LoansState storage loans_) internal { loans_.loans.push(Loan(address(0), 0)); } - /** - * The Loans heap is a Max Heap data structure (complete binary tree), the root node is the loan with the highest threshold price (TP) - * at a given time. The heap is represented as an array, where the first element is a dummy element (Loan(address(0), 0)) and the first - * value of the heap starts at index 1, ROOT_INDEX. The threshold price of a loan's parent is always greater than or equal to the - * threshold price of the loan. - * This code was modified from the following source: https://github.com/zmitton/eth-heap/blob/master/contracts/Heap.sol - */ - /***********************************/ /*** Loans Management Functions ***/ /***********************************/ /** - * @notice Updates a loan: updates heap (upsert if TP not 0, remove otherwise) and borrower balance. - * @dev write state: - * - _upsert: - * - insert or update loan in loans array - * - remove: - * - remove loan from loans array - * - update borrower in address => borrower mapping - * @param loans_ Holds loan heap data. + * @notice Updates a loan: updates heap (`upsert` if `TP` not `0`, `remove` otherwise) and borrower balance. + * @dev === Write state === + * @dev - `_upsert`: + * @dev insert or update loan in `loans` array + * @dev - `remove`: + * @dev remove loan from `loans` array + * @dev - update borrower in `address => borrower` mapping + * @param loans_ Holds loans heap data. + * @param auctions_ Struct for pool auctions state. + * @param deposits_ Struct for pool deposits state. * @param borrower_ Borrower struct with borrower details. * @param borrowerAddress_ Borrower's address to update. * @param poolDebt_ Pool's current debt. @@ -128,34 +126,34 @@ library Loans { /**************************************/ /** - * @notice Moves a Loan up the heap. - * @param loans_ Holds loan heap data. - * @param loan_ Loan to be moved. - * @param i_ Index for Loan to be moved to. + * @notice Moves a `Loan` up the heap. + * @param loans_ Holds loans heap data. + * @param loan_ `Loan` to be moved. + * @param index_ Index of `Loan` to be moved to. */ - function _bubbleUp(LoansState storage loans_, Loan memory loan_, uint i_) private { + function _bubbleUp(LoansState storage loans_, Loan memory loan_, uint index_) private { uint256 count = loans_.loans.length; - if (i_ == ROOT_INDEX || loan_.thresholdPrice <= loans_.loans[i_ / 2].thresholdPrice){ - _insert(loans_, loan_, i_, count); + if (index_ == ROOT_INDEX || loan_.thresholdPrice <= loans_.loans[index_ / 2].thresholdPrice){ + _insert(loans_, loan_, index_, count); } else { - _insert(loans_, loans_.loans[i_ / 2], i_, count); - _bubbleUp(loans_, loan_, i_ / 2); + _insert(loans_, loans_.loans[index_ / 2], index_, count); + _bubbleUp(loans_, loan_, index_ / 2); } } /** - * @notice Moves a Loan down the heap. - * @param loans_ Holds Loan heap data. - * @param loan_ Loan to be moved. - * @param i_ Index for Loan to be moved to. + * @notice Moves a `Loan` down the heap. + * @param loans_ Holds loans heap data. + * @param loan_ `Loan` to be moved. + * @param index_ Index of `Loan` to be moved to. */ - function _bubbleDown(LoansState storage loans_, Loan memory loan_, uint i_) private { + function _bubbleDown(LoansState storage loans_, Loan memory loan_, uint index_) private { // Left child index. - uint cIndex = i_ * 2; + uint cIndex = index_ * 2; uint256 count = loans_.loans.length; if (count <= cIndex) { - _insert(loans_, loan_, i_, count); + _insert(loans_, loan_, index_, count); } else { Loan memory largestChild = loans_.loans[cIndex]; @@ -164,67 +162,67 @@ library Loans { } if (largestChild.thresholdPrice <= loan_.thresholdPrice) { - _insert(loans_, loan_, i_, count); + _insert(loans_, loan_, index_, count); } else { - _insert(loans_, largestChild, i_, count); + _insert(loans_, largestChild, index_, count); _bubbleDown(loans_, loan_, cIndex); } } } /** - * @notice Inserts a Loan in the heap. - * @param loans_ Holds loan heap data. - * @param loan_ Loan to be inserted. - * @param i_ index for Loan to be inserted at. + * @notice Inserts a `Loan` in the heap. + * @param loans_ Holds loans heap data. + * @param loan_ `Loan` to be inserted. + * @param index_ Index of `Loan` to be inserted at. */ - function _insert(LoansState storage loans_, Loan memory loan_, uint i_, uint256 count_) private { - if (i_ == count_) loans_.loans.push(loan_); - else loans_.loans[i_] = loan_; + function _insert(LoansState storage loans_, Loan memory loan_, uint index_, uint256 count_) private { + if (index_ == count_) loans_.loans.push(loan_); + else loans_.loans[index_] = loan_; - loans_.indices[loan_.borrower] = i_; + loans_.indices[loan_.borrower] = index_; } /** - * @notice Removes loan from heap given borrower address. - * @param loans_ Holds loan heap data. - * @param borrower_ Borrower address whose loan is being updated or inserted. - * @param id_ Loan id. + * @notice Removes `Loan` from heap given borrower address. + * @param loans_ Holds loans heap data. + * @param borrower_ Borrower address whose `Loan` is being updated or inserted. + * @param index_ Index of `Loan` to be removed. */ - function remove(LoansState storage loans_, address borrower_, uint256 id_) internal { + function remove(LoansState storage loans_, address borrower_, uint256 index_) internal { delete loans_.indices[borrower_]; uint256 tailIndex = loans_.loans.length - 1; - if (id_ == tailIndex) loans_.loans.pop(); // we're removing the tail, pop without sorting + if (index_ == tailIndex) loans_.loans.pop(); // we're removing the tail, pop without sorting else { Loan memory tail = loans_.loans[tailIndex]; loans_.loans.pop(); // remove tail loan - _bubbleUp(loans_, tail, id_); - _bubbleDown(loans_, loans_.loans[id_], id_); + _bubbleUp(loans_, tail, index_); + _bubbleDown(loans_, loans_.loans[index_], index_); } } /** * @notice Performs an insert or an update dependent on borrowers existance. - * @param loans_ Holds loan heap data. + * @param loans_ Holds loans heap data. * @param borrower_ Borrower address that is being updated or inserted. - * @param id_ Loan id. - * @param thresholdPrice_ Threshold Price that is updated or inserted. + * @param index_ Index of `Loan` to be upserted. + * @param thresholdPrice_ `Threshold Price` that is updated or inserted. */ function _upsert( LoansState storage loans_, address borrower_, - uint256 id_, + uint256 index_, uint96 thresholdPrice_ ) internal { // Loan exists, update in place. - if (id_ != 0) { - Loan memory currentLoan = loans_.loans[id_]; + if (index_ != 0) { + Loan memory currentLoan = loans_.loans[index_]; if (currentLoan.thresholdPrice > thresholdPrice_) { currentLoan.thresholdPrice = thresholdPrice_; - _bubbleDown(loans_, currentLoan, id_); + _bubbleDown(loans_, currentLoan, index_); } else { currentLoan.thresholdPrice = thresholdPrice_; - _bubbleUp(loans_, currentLoan, id_); + _bubbleUp(loans_, currentLoan, index_); } // New loan, insert it @@ -239,19 +237,19 @@ library Loans { /**********************/ /** - * @notice Retreives Loan by index, i_. - * @param loans_ Holds loan heap data. - * @param i_ Index to retreive Loan. - * @return Loan Loan retrieved by index. + * @notice Retreives `Loan` by index, `index_`. + * @param loans_ Holds loans heap data. + * @param index_ Index to retrieve `Loan`. + * @return `Loan` struct retrieved by index. */ - function getByIndex(LoansState storage loans_, uint256 i_) internal view returns(Loan memory) { - return loans_.loans.length > i_ ? loans_.loans[i_] : Loan(address(0), 0); + function getByIndex(LoansState storage loans_, uint256 index_) internal view returns(Loan memory) { + return loans_.loans.length > index_ ? loans_.loans[index_] : Loan(address(0), 0); } /** - * @notice Retreives Loan with the highest threshold price value. - * @param loans_ Holds loan heap data. - * @return Loan Max Loan in the Heap. + * @notice Retreives `Loan` with the highest threshold price value. + * @param loans_ Holds loans heap data. + * @return `Max Loan` in the heap. */ function getMax(LoansState storage loans_) internal view returns(Loan memory) { return getByIndex(loans_, ROOT_INDEX); @@ -259,8 +257,8 @@ library Loans { /** * @notice Returns number of loans in pool. - * @param loans_ Holds loan heap data. - * @return number of loans in pool. + * @param loans_ Holds loans heap data. + * @return Number of loans in pool. */ function noOfLoans(LoansState storage loans_) internal view returns (uint256) { return loans_.loans.length - 1; diff --git a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 63fd35a17..3cb90041d 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol @@ -143,7 +143,7 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl // ensure actor has collateral to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if(lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); + if (lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); } function _prePledgeCollateral( diff --git a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol index 129dcb47e..c57141ec6 100644 --- a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol @@ -90,7 +90,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { uint256 collateral; uint256[] memory collateralBuckets = IBaseHandler(_handler).getCollateralBuckets(); - for(uint256 i = 0; i < collateralBuckets.length; i++) { + for (uint256 i = 0; i < collateralBuckets.length; i++) { uint256 bucketIndex = collateralBuckets[i]; (, collateral, , , ) = _erc721pool.bucketInfo(bucketIndex); bucketCollateral += collateral; diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol index 3b9153003..d3286f13d 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -144,7 +144,7 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan // ensure actor has collateral to remove (uint256 lpBalanceBefore, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if(lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); + if (lpBalanceBefore == 0) _addCollateral(boundedAmount_, _lenderBucketIndex); } function _prePledgeCollateral( diff --git a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol index 97c39bd95..227391879 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/unbounded/UnboundedBasicERC721PoolHandler.sol @@ -43,7 +43,7 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, _collateral.mint(_actor, amount_); _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](amount_); - for(uint256 i = 0; i < amount_; i++) { + for (uint256 i = 0; i < amount_; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } @@ -102,7 +102,7 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, _collateral.mint(_actor, amount_); _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](amount_); - for(uint256 i = 0; i < amount_; i++) { + for (uint256 i = 0; i < amount_; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } @@ -161,7 +161,7 @@ abstract contract UnboundedBasicERC721PoolHandler is UnboundedBasicPoolHandler, _collateral.mint(_actor, collateralToPledge); _collateral.setApprovalForAll(address(_pool), true); uint256[] memory tokenIds = new uint256[](collateralToPledge); - for(uint256 i = 0; i < collateralToPledge; i++) { + for (uint256 i = 0; i < collateralToPledge; i++) { tokenIds[i] = _collateral.tokenOfOwnerByIndex(_actor, i); } diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index ae714fa34..064416b10 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -245,7 +245,7 @@ abstract contract BasicInvariants is BaseInvariants { } uint256 poolT0Debt = _pool.totalT0Debt(); - if(poolT0Debt == 0) require(currentInflator == 1e18, "Incorrect inflator update"); + if (poolT0Debt == 0) require(currentInflator == 1e18, "Incorrect inflator update"); previousInflator = currentInflator; previousInflatorUpdate = currentInflatorUpdate; @@ -336,7 +336,7 @@ abstract contract BasicInvariants is BaseInvariants { uint256 nextNonzeroBucket = _pool.depositIndex(_pool.depositUpToIndex(bucketIndex)+1); console.log("bucketIndex: ", bucketIndex); console.log("Next nonzero bucket: ", nextNonzeroBucket); - for(uint256 j = bucketIndex + 1; j < nextNonzeroBucket && j < LENDER_MAX_BUCKET_INDEX; j++) { + for (uint256 j = bucketIndex + 1; j < nextNonzeroBucket && j < LENDER_MAX_BUCKET_INDEX; j++) { (, , , uint256 depositAtJ, ) = _pool.bucketInfo(j); console.log("Deposit at %s is %s", j, depositAtJ); require( diff --git a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol index 133178ed5..e19d14fee 100644 --- a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -125,7 +125,7 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { ) internal returns (address receiver_, uint256 boundedLps_) { // ensure actor has LP to transfer (uint256 senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); - if(senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); + if (senderLpBalance == 0) _addQuoteToken(1e24, _lenderBucketIndex); (senderLpBalance, ) = _pool.lenderInfo(_lenderBucketIndex, _actor); diff --git a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol index ba80bd9da..800244f51 100644 --- a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol @@ -159,7 +159,7 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas // Action phase _actor = kicker; - if(!borrowerKicked) _kickAuction(borrower_); + if (!borrowerKicked) _kickAuction(borrower_); } function _constrictTakeAmount(uint256 amountToTake_) internal view virtual returns(uint256 boundedAmount_); diff --git a/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol index c6a392f7d..ce523bfc5 100644 --- a/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol +++ b/tests/forge/unit/ERC20Pool/ERC20DSTestPlus.sol @@ -80,13 +80,13 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { uint256 lpRedeemed; // redeem LP for quote token if available - if(lenderLpBalance != 0 && bucketQuote != 0) { + if (lenderLpBalance != 0 && bucketQuote != 0) { (, lpRedeemed) = _pool.removeQuoteToken(type(uint256).max, bucketIndex); lenderLpBalance -= lpRedeemed; } // redeem LP for collateral if available - if(lenderLpBalance != 0 && bucketCollateral != 0) { + if (lenderLpBalance != 0 && bucketCollateral != 0) { (, lpRedeemed) = ERC20Pool(address(_pool)).removeCollateral(type(uint256).max, bucketIndex); lenderLpBalance -= lpRedeemed; } @@ -108,7 +108,7 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { } uint256 pledgedCollateral = 0; - for(uint i = 0; i < borrowers.length(); i++) { + for (uint i = 0; i < borrowers.length(); i++) { (, uint256 collateral,) = _poolUtils.borrowerInfo(address(_pool), borrowers.at(i)); pledgedCollateral += collateral; } @@ -143,11 +143,11 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { _; validateCollateral(bucketsUsed, borrowers); - for(uint i = 0; i < borrowers.length(); i++) { + for (uint i = 0; i < borrowers.length(); i++) { repayDebt(borrowers.at(i)); } - for(uint i = 0; i < lenders.length(); i++) { + for (uint i = 0; i < lenders.length(); i++) { redeemLendersLp(lenders.at(i), lendersDepositedIndex[lenders.at(i)]); } @@ -420,8 +420,8 @@ abstract contract ERC20DSTestPlus is DSTestPlus, IERC20PoolEvents { emit TransferLP(from, to, indexes, lpBalance); _pool.transferLP(from, to, indexes); - for(uint256 i = 0; i < indexes.length ;i++ ){ - if(lenders.contains(from)){ + for (uint256 i = 0; i < indexes.length ;i++ ){ + if (lenders.contains(from)){ lenders.add(to); lendersDepositedIndex[to].add(indexes[i]); } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol index bd0d09337..ebd6ab799 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol @@ -101,7 +101,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { uint256 price, uint256 quoteTokens, uint256 collateral, - uint256 bucketLPs, + uint256 bucketLP, uint256 scale, uint256 exchangeRate ) = _poolUtils.bucketInfo(address(_pool), 5000); @@ -109,7 +109,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { assertEq(price, 0.014854015662334135 * 1e18); assertEq(quoteTokens, 0); assertEq(collateral, 0); - assertEq(bucketLPs, 0); + assertEq(bucketLP, 0); assertEq(scale, 1 * 1e18); assertEq(exchangeRate, 1 * 1e18); @@ -117,14 +117,14 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { price, quoteTokens, collateral, - bucketLPs, + bucketLP, scale, exchangeRate ) = _poolUtils.bucketInfo(address(_pool), high); assertEq(price, 2_995.912459898389633881 * 1e18); assertEq(quoteTokens, 10_000 * 1e18); assertEq(collateral, 0); - assertEq(bucketLPs, 10_000 * 1e18); + assertEq(bucketLP, 10_000 * 1e18); assertEq(scale, 1 * 1e18); assertEq(exchangeRate, 1 * 1e18); } @@ -227,7 +227,7 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract { assertEq(_poolUtils.unutilizedDepositFeeRate(address(_pool)), 0.000136986301369863 * 1e18); } - function testPoolInfoUtilsLPsToCollateralAndQuote() external { + function testPoolInfoUtilsLPToCollateralAndQuote() external { assertEq( _poolUtils.lpToCollateral( address(_pool), diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol index c2bfed467..9d42ca489 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsScaled.t.sol @@ -266,21 +266,21 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { } function _kickWithDeposit(address lender, uint256 bucketId) internal { - (uint256 lastLenderLPs, ) = _pool.lenderInfo(bucketId, lender); - (, uint256 lastBucketDeposit, , uint256 lastBucketLPs, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); + (uint256 lastLenderLP, ) = _pool.lenderInfo(bucketId, lender); + (, uint256 lastBucketDeposit, , uint256 lastBucketLP, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); changePrank(lender); _pool.kickWithDeposit(bucketId, MAX_FENWICK_INDEX); _checkAuctionStateUponKick(lender); - // confirm user has redeemed some of their LPs to post liquidation bond - (uint256 lenderLPs, ) = _pool.lenderInfo(bucketId, lender); - assertLt(lenderLPs, lastLenderLPs); + // confirm user has redeemed some of their LP to post liquidation bond + (uint256 lenderLP, ) = _pool.lenderInfo(bucketId, lender); + assertLt(lenderLP, lastLenderLP); // confirm deposit has been removed from bucket - (, uint256 bucketDeposit, , uint256 bucketLPs, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); + (, uint256 bucketDeposit, , uint256 bucketLP, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); assertLt(bucketDeposit, lastBucketDeposit); - assertLt(bucketLPs, lastBucketLPs); + assertLt(bucketLP, lastBucketLP); } function _checkAuctionStateUponKick(address kicker) internal { @@ -326,15 +326,15 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { (uint256 lastAuctionDebt, uint256 lastAuctionCollateral, ) = _poolUtils.borrowerInfo(address(_pool), _borrower); assertGt(lastAuctionDebt, 0); assertGt(lastAuctionCollateral, 0); - (, uint256 lastBucketDeposit, uint256 lastBucketCollateral, uint256 lastBucketLPs, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); - uint256 lastKickerLPs = _kickerLP(bucketId); + (, uint256 lastBucketDeposit, uint256 lastBucketCollateral, uint256 lastBucketLP, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); + uint256 lastKickerLP = _kickerLP(bucketId); assertGt(lastAuctionDebt, 0); assertGt(lastAuctionCollateral, 0); _pool.bucketTake(_borrower, true, bucketId); // confirm auction debt and collateral have decremented - uint256 bucketLPs; + uint256 bucketLP; { (uint256 auctionDebt, uint256 auctionCollateral, ) = _poolUtils.borrowerInfo(address(_pool), _borrower); assertLt(auctionDebt, lastAuctionDebt); @@ -344,7 +344,7 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { // confirm bucket deposit was exchanged for collateral uint256 bucketDeposit; uint256 bucketCollateral; - (, bucketDeposit, bucketCollateral, bucketLPs, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); + (, bucketDeposit, bucketCollateral, bucketLP, , ) = _poolUtils.bucketInfo(address(_pool), bucketId); assertLt(bucketDeposit, lastBucketDeposit); assertEq(bucketCollateral, lastBucketCollateral + collateralTaken); } @@ -353,10 +353,10 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { (address kicker, , , uint256 kickTime, uint256 kickMomp, uint256 neutralPrice, , , , ) = _pool.auctionInfo(_borrower); uint256 auctionPrice = _auctionPrice(kickMomp, neutralPrice, kickTime); if (auctionPrice < neutralPrice) { - uint256 kickerLPs = _kickerLP(bucketId); - assertGt(kickerLPs, lastKickerLPs); - uint256 kickerLpChange = kickerLPs - lastKickerLPs; - assertEq(bucketLPs, lastBucketLPs + kickerLpChange); + uint256 kickerLP = _kickerLP(bucketId); + assertGt(kickerLP, lastKickerLP); + uint256 kickerLpChange = kickerLP - lastKickerLP; + assertEq(bucketLP, lastBucketLP + kickerLpChange); } // Add for tearDown @@ -370,7 +370,7 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { assertGt(lastAuctionDebt, 0); assertGt(lastAuctionCollateral, 0); (, uint256 lastBucketDeposit, uint256 lastBucketCollateral, , , ) = _poolUtils.bucketInfo(address(_pool), bucketId); - (uint256 lastBidderLPs, ) = _pool.lenderInfo(bucketId, bidder); + (uint256 lastBidderLP, ) = _pool.lenderInfo(bucketId, bidder); uint256 lastBidderQuoteBalance = _quote.balanceOf(bidder); changePrank(bidder); @@ -382,10 +382,10 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { assertLt(auctionCollateral, lastAuctionCollateral); uint256 collateralTaken = lastAuctionCollateral - auctionCollateral; - // confirm bidder was awarded LPs without spending any quote token + // confirm bidder was awarded LP without spending any quote token { - (uint256 bidderLPs, ) = _pool.lenderInfo(bucketId, bidder); - assertGt(bidderLPs, lastBidderLPs); + (uint256 bidderLP, ) = _pool.lenderInfo(bucketId, bidder); + assertGt(bidderLP, lastBidderLP); assertEq(_quote.balanceOf(bidder), lastBidderQuoteBalance); } @@ -448,7 +448,7 @@ contract ERC20PoolLiquidationsScaledTest is ERC20DSTestPlus { function _kickerLP(uint256 bucketId) internal view returns (uint256) { (address kicker, , , , , , , , , ) = _pool.auctionInfo(_borrower); - (uint256 kickerLPs, ) = _pool.lenderInfo(bucketId, kicker); - return kickerLPs; + (uint256 kickerLP, ) = _pool.lenderInfo(bucketId, kicker); + return kickerLP; } } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol index ada3d5b55..cc963ce0b 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol @@ -551,9 +551,9 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { // check bucket uint256 quoteScale = _pool.quoteTokenScale(); - (, uint256 curDeposit, , uint256 bucketLPs,,) = _poolUtils.bucketInfo(address(_pool), bucketId); + (, uint256 curDeposit, , uint256 bucketLP,,) = _poolUtils.bucketInfo(address(_pool), bucketId); assertEq(curDeposit, _roundToScale(quoteAmount1, quoteScale) + _roundToScale(quoteAmount2, quoteScale)); - assertEq(bucketLPs, lpBalance1 + lpBalance2); + assertEq(bucketLP, lpBalance1 + lpBalance2); } function testMoveQuoteToken( diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol index 1a403d726..d0bc50a5f 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolTransferLPs.t.sol @@ -186,7 +186,7 @@ contract ERC20PoolTransferLPTest is ERC20HelperContract { }); } - function testIncreaseDecreaseLPsWithInvalidInput() external tearDown { + function testIncreaseDecreaseLPWithInvalidInput() external tearDown { uint256[] memory indexes = new uint256[](3); indexes[0] = 2550; indexes[1] = 2551; diff --git a/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol index b2e425ae9..dc44a3ffb 100644 --- a/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol +++ b/tests/forge/unit/ERC721Pool/ERC721DSTestPlus.sol @@ -433,7 +433,7 @@ abstract contract ERC721DSTestPlus is DSTestPlus, IERC721PoolEvents { function _assertCollateralInvariants() internal { uint256 collateralInBuckets; - for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + for (uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { (, uint256 bucketCollateral, , , ) = _pool.bucketInfo(bucketIndex); collateralInBuckets += bucketCollateral; } diff --git a/tests/forge/unit/PositionManager.t.sol b/tests/forge/unit/PositionManager.t.sol index a5c553905..72833270e 100644 --- a/tests/forge/unit/PositionManager.t.sol +++ b/tests/forge/unit/PositionManager.t.sol @@ -178,12 +178,12 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.memorializePositions(memorializeParams); // check memorialization success - uint256 positionAtPriceOneLPs = _positionManager.getLP(tokenId, indexes[0]); - assertGt(positionAtPriceOneLPs, 0); + uint256 positionAtPriceOneLP = _positionManager.getLP(tokenId, indexes[0]); + assertGt(positionAtPriceOneLP, 0); // check lps at non added to price - uint256 positionAtWrongPriceLPs = _positionManager.getLP(tokenId, uint256(MAX_BUCKET_INDEX)); - assertEq(positionAtWrongPriceLPs, 0); + uint256 positionAtWrongPriceLP = _positionManager.getLP(tokenId, uint256(MAX_BUCKET_INDEX)); + assertEq(positionAtWrongPriceLP, 0); assertTrue(_positionManager.isIndexInPosition(tokenId, 2550)); assertTrue(_positionManager.isIndexInPosition(tokenId, 2551)); @@ -2679,7 +2679,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract tokenIds[1] = _mintNFT(alice, alice, address(_pool)); assertFalse(_positionManager.isIndexInPosition(tokenIds[1], 2550)); - for(uint256 i = 0; i < addresses.length; ++i) { + for (uint256 i = 0; i < addresses.length; ++i) { // construct memorialize params struct IPositionManagerOwnerActions.MemorializePositionsParams memory memorializeParams = IPositionManagerOwnerActions.MemorializePositionsParams( tokenIds[i], indexes @@ -2711,7 +2711,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract assertEq(depositTime, aliceDepositTime + 1 hours); // both alice and bob redeem - for(uint256 i = 0; i < addresses.length; ++i) { + for (uint256 i = 0; i < addresses.length; ++i) { // construct memorialize params struct IPositionManagerOwnerActions.RedeemPositionsParams memory params = IPositionManagerOwnerActions.RedeemPositionsParams( tokenIds[i], address(_pool), indexes @@ -2731,7 +2731,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract assertEq(depositTime, aliceDepositTime + 1 hours); // attempt to redeem again should fail - for(uint256 i = 0; i < addresses.length; ++i) { + for (uint256 i = 0; i < addresses.length; ++i) { // construct memorialize params struct IPositionManagerOwnerActions.RedeemPositionsParams memory params = IPositionManagerOwnerActions.RedeemPositionsParams( tokenIds[i], address(_pool), indexes diff --git a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol index a54ae2526..93fe06234 100644 --- a/tests/forge/unit/Rewards/RewardsDSTestPlus.sol +++ b/tests/forge/unit/Rewards/RewardsDSTestPlus.sol @@ -96,7 +96,7 @@ abstract contract RewardsDSTestPlus is IRewardsManagerEvents, ERC20HelperContrac assertEq(PositionManager(address(_positionManager)).ownerOf(tokenId), owner); // invariant: all bucket snapshots are removed for the token id that was unstaken - for(uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { + for (uint256 bucketIndex = 0; bucketIndex <= 7388; bucketIndex++) { (uint256 lps, uint256 rate) = _rewardsManager.getBucketStateStakeInfo(tokenId, bucketIndex); assertEq(lps, 0); assertEq(rate, 0); @@ -484,12 +484,12 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { // Helper function that returns a random subset from array function _getRandomSubsetFromArray(uint256[] memory array) internal returns (uint256[] memory subsetArray) { uint256[] memory copyOfArray = new uint256[](array.length); - for(uint j = 0; j < copyOfArray.length; j++){ + for (uint j = 0; j < copyOfArray.length; j++){ copyOfArray[j] = array[j]; } uint256 randomNoOfNfts = randomInRange(1, copyOfArray.length); subsetArray = new uint256[](randomNoOfNfts); - for(uint256 i = 0; i < randomNoOfNfts; i++) { + for (uint256 i = 0; i < randomNoOfNfts; i++) { uint256 randomIndex = randomInRange(0, copyOfArray.length - i - 1); subsetArray[i] = copyOfArray[randomIndex]; copyOfArray[randomIndex] = copyOfArray[copyOfArray.length - i - 1]; @@ -499,7 +499,7 @@ abstract contract RewardsHelperContract is RewardsDSTestPlus { // Returns N addresses array function _getAddresses(uint256 noOfAddress) internal returns(address[] memory addresses_) { addresses_ = new address[](noOfAddress); - for(uint i = 0; i < noOfAddress; i++) { + for (uint i = 0; i < noOfAddress; i++) { addresses_[i] = makeAddr(string(abi.encodePacked("Minter", Strings.toString(i)))); } } diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index f5521735e..f6892ac12 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -1919,7 +1919,7 @@ contract RewardsManagerTest is RewardsHelperContract { address[] memory minters = _getAddresses(deposits); // stake variable no of deposits - for(uint256 i = 0; i < deposits; ++i) { + for (uint256 i = 0; i < deposits; ++i) { tokenIds[i] = _mintAndMemorializePositionNFT({ indexes: depositIndexes, @@ -1933,12 +1933,12 @@ contract RewardsManagerTest is RewardsHelperContract { uint256 updaterBalance = _ajnaToken.balanceOf(_updater); - for(uint i = 0; i < deposits; i++) { + for (uint i = 0; i < deposits; i++) { minterToBalance[minters[i]] = _ajnaToken.balanceOf(minters[i]); } // start variable no of reserve Auctions and claim rewards for random tokenIds in each epoch - for(uint i = 0; i < reserveAuctions; ++i) { + for (uint i = 0; i < reserveAuctions; ++i) { uint256 limitIndex = _findSecondLowestIndexPrice(depositIndexes); // start and end new reserve auction @@ -1967,7 +1967,7 @@ contract RewardsManagerTest is RewardsHelperContract { // pick random NFTs from all NFTs to claim rewards uint256[] memory randomNfts = _getRandomSubsetFromArray(tokenIds); - for(uint j = 0; j < randomNfts.length; j++) { + for (uint j = 0; j < randomNfts.length; j++) { address minterAddress = tokenIdToMinter[randomNfts[j]]; changePrank(minterAddress); diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index ad24f0e89..1aab9bcae 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -246,7 +246,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { changePrank(from); vm.expectEmit(true, true, false, true); emit Kick(borrower, debt, collateral, bond); - if(transferAmount != 0) _assertQuoteTokenTransferEvent(from, address(_pool), transferAmount); + if (transferAmount != 0) _assertQuoteTokenTransferEvent(from, address(_pool), transferAmount); _pool.kick(borrower, MAX_FENWICK_INDEX); } @@ -266,7 +266,7 @@ abstract contract DSTestPlus is Test, IPoolEvents { emit Kick(borrower, debt, collateral, bond); vm.expectEmit(true, true, false, true); emit RemoveQuoteToken(from, index, removedFromDeposit, removedFromDeposit, lup); - if(transferAmount != 0) _assertQuoteTokenTransferEvent(from, address(_pool), transferAmount); + if (transferAmount != 0) _assertQuoteTokenTransferEvent(from, address(_pool), transferAmount); _pool.kickWithDeposit(index, MAX_FENWICK_INDEX); } @@ -595,12 +595,12 @@ abstract contract DSTestPlus is Test, IPoolEvents { uint256 curLpBalance; // sum up LP across lenders - for(uint i = 0; i < lenders.length(); i++ ){ + for (uint i = 0; i < lenders.length(); i++ ){ (curLpBalance, ) = _pool.lenderInfo(index, lenders.at(i)); lenderLps += curLpBalance; } // handle borrowers awarded LP from liquidation - for(uint i = 0; i < borrowers.length(); i++ ){ + for (uint i = 0; i < borrowers.length(); i++ ){ address borrower = borrowers.at(i); if (!lenders.contains(borrower)) { (curLpBalance, ) = _pool.lenderInfo(index, borrowers.at(i)); From 45d8448472114f420c90a7bcc4521f45c667fa0f Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 20 Apr 2023 20:59:57 +0300 Subject: [PATCH 65/70] TOB-AJNA-2: global scalar (at index 8192) is never updated (#753) * TOB-AJNA-2: global scalar (at index 8192) is never updated - save an SLOAD in Deposits.treeSum as scalar is not updated (always 1) * Changes after review: add F5 invariant --- src/base/Pool.sol | 5 +++++ src/interfaces/pool/commons/IPoolDerivedState.sol | 9 +++++++++ src/libraries/internal/Deposits.sol | 6 ++---- tests/INVARIANTS.md | 1 + tests/forge/invariants/base/BasicInvariants.t.sol | 6 ++++++ 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 60956e0e2..5b17a397e 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -780,6 +780,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { return PoolCommons.utilization(emaState); } + /// @inheritdoc IPoolDerivedState + function depositScale(uint256 index_) external view override returns (uint256) { + return deposits.scaling[index_]; + } + /// @inheritdoc IPoolState function emasInfo() external view override returns (uint256, uint256, uint256, uint256) { return ( diff --git a/src/interfaces/pool/commons/IPoolDerivedState.sol b/src/interfaces/pool/commons/IPoolDerivedState.sol index 94294f83d..ba47a4092 100644 --- a/src/interfaces/pool/commons/IPoolDerivedState.sol +++ b/src/interfaces/pool/commons/IPoolDerivedState.sol @@ -46,4 +46,13 @@ interface IPoolDerivedState { */ function depositUtilization() external view returns (uint256); + /** + * @notice Returns the scaling value of deposit at given index. + * @param index_ Deposit index. + * @return Deposit scaling. + */ + function depositScale( + uint256 index_ + ) external view returns (uint256); + } diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index 2b2e64266..57f721e82 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -321,10 +321,8 @@ library Deposits { function treeSum( DepositsState storage deposits_ ) internal view returns (uint256) { - // In a scaled Fenwick tree, sum is at the root node, but needs to be scaled - uint256 scaling = deposits_.scaling[SIZE]; - // scaling == 0 means scale factor is actually 1 - return (scaling != 0) ? Maths.wmul(scaling, deposits_.values[SIZE]) : deposits_.values[SIZE]; + // In a scaled Fenwick tree, sum is at the root node and never scaled + return deposits_.values[SIZE]; } /** diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 24866d478..7d95d18ee 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -49,6 +49,7 @@ - **F2**: For any index `i`, the prefix sum up to and including `i` is the sum of values stored in indices `j<=i` - **F3**: For any index `i < MAX_FENWICK_INDEX`, `findIndexOfSum(prefixSum(i)) > i` - **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `depositAtIndex(j) == 0 for i < j < findIndexOfSum(prefixSum(i)+1)` +- **F5**: Global scalar is never updated (`DepositsState.scaling[8192]` is always 0) ## Exchange rate invariants ## - **R1**: Exchange rates are unchanged by pledging collateral diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index 064416b10..e5dcf137b 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -43,6 +43,7 @@ abstract contract BasicInvariants is BaseInvariants { * F2: For any index i, the prefix sum up to and including i is the sum of values stored in indices j<=i * F3: For any index i < MAX_FENWICK_INDEX, findIndexOfSum(prefixSum(i)) > i * F4: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)), where j is the next index from i with deposits != 0 + * F5: Global scalar is never updated (`DepositsState.values[8192]` is always 0) ****************************************************************************************************************************************/ // checks pool lps are equal to sum of all lender lps in a bucket @@ -352,6 +353,11 @@ abstract contract BasicInvariants is BaseInvariants { } } + // **F5**: Global scalar is never updated (`DepositsState.scaling[8192]` is always 0) + function invariant_fenwick_globalscalar_F5() public useCurrentTimestamp { + require(_pool.depositScale(8192) == 0, "F5: Global scalar was updated"); + } + function invariant_call_summary() public virtual useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); From 658d47c3b0345fe61deb210de959ee5513f64fa5 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 21 Apr 2023 17:48:05 +0300 Subject: [PATCH 66/70] CT1-CT7 failure: collateral transfered out from bucket should never be larger than bucket collateral (#759) * Add unit test for CT1-CT7 failure Error: a >= b not satisfied [uint] Value a: 1200553203896460071992180228 Value b: 1200553203896460071993273178 * When remove max collateral always make sure amount transfered out is not greater than collateral in bucket * Revert "When remove max collateral always make sure amount transfered out is not greater than collateral in bucket" This reverts commit aefe63ff3d7749ee45cd7f54c25e5f935895acc2. * When remove max collateral always make sure amount transfered out is not greater than collateral in bucket * Set no of buckets to 10 for regression test --- src/libraries/external/LenderActions.sol | 7 ++-- .../RegressionTestBasicERC20Pool.t.sol | 38 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/libraries/external/LenderActions.sol b/src/libraries/external/LenderActions.sol index c23ec6d56..ace04181b 100644 --- a/src/libraries/external/LenderActions.sol +++ b/src/libraries/external/LenderActions.sol @@ -651,11 +651,10 @@ library LenderActions { bucketLP -= Maths.min(bucketLP, lpAmount_); // If clearing out the bucket collateral, ensure it's zeroed out - if (bucketLP == 0 && bucketDeposit == 0) { - collateralAmount_ = bucketCollateral; - } + if (bucketLP == 0 && bucketDeposit == 0) collateralAmount_ = bucketCollateral; - bucketCollateral -= Maths.min(bucketCollateral, collateralAmount_); + collateralAmount_ = Maths.min(bucketCollateral, collateralAmount_); + bucketCollateral -= collateralAmount_; bucket.collateral = bucketCollateral; // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP diff --git a/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol index 8d89bc290..9faf8c231 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol @@ -244,7 +244,6 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { invariant_collateralBalance_CT1_CT7(); } - // TODO: poolBalance + poolDebt >= _pool.depositSize fails by 1 unit of WAD (1.000115588871659711 >= 1.000115588871659712) function test_regression_invariant_quoteTokenBalance_QT1() external { _basicERC20PoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098); _basicERC20PoolHandler.repayDebt(1, 2); @@ -260,4 +259,41 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { invariant_fenwick_depositAtIndex_F1(); } + + function test_regression_CT1_CT7() external { + // failure reproduced with 10 active buckets + vm.setEnv("NO_OF_BUCKETS", "10"); + super.setUp(); + + _basicERC20PoolHandler.transferLps(19078983173372942890417748018722377435183373748499322243247546781962442185, 1, 4391160926701505967325397265181132015972183318, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _basicERC20PoolHandler.transferLps(270997981512080867078682324706934707221242205867293069, 7864, 3651, 3166); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3635889843872116931397407365290249, 23021664368573277020436789355588670855277006870); + _basicERC20PoolHandler.drawDebt(268, 181582671641966396883195899256); + _basicERC20PoolHandler.pullCollateral(1227515749685864358137095510127654245525351748189001609, 3818828157139154512); + _basicERC20PoolHandler.repayDebt(3042268540744255610589705434124741203255613373849507141, 0); + _basicERC20PoolHandler.pullCollateral(6802, 6279); + _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 127935328935713960735227335223838560292175); + _basicERC20PoolHandler.pullCollateral(2930, 36104017659498278503100244564470932293); + _basicERC20PoolHandler.addQuoteToken(1681414255953633817920736095340458401, 2, 331564777626112378272493610241099454882166422929878794700); + _basicERC20PoolHandler.addCollateral(1834, 38146939, 39424949171633211915315804); + _basicERC20PoolHandler.addQuoteToken(8640216505061661298, 13070, 10696); + _basicERC20PoolHandler.pullCollateral(962704044, 8898752); + _basicERC20PoolHandler.moveQuoteToken(34331296, 236168072844073492133156025194, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 19256400511825415508859240250358); + _basicERC20PoolHandler.transferLps(2, 1183061996845258949, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); + _basicERC20PoolHandler.drawDebt(2169832191680919598423992113933409675947, 459159100237615494082512466719091280519979494228); + _basicERC20PoolHandler.pledgeCollateral(363165343283932793766391798512, 14747); + _basicERC20PoolHandler.removeCollateral(3985, 1824, 6960); + _basicERC20PoolHandler.addCollateral(1650938994639952252010012965502, 7253, 7574); + _basicERC20PoolHandler.transferLps(190195262656118760688724739, 999999999999999999999999999999999999999997115, 1248306590932896398273554030427, 6130); + _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 8113004849018889317737081296151463737388429); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _basicERC20PoolHandler.moveQuoteToken(3, 2, 10960996744849996375050327179144415056797335841576787, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _basicERC20PoolHandler.transferLps(1, 743324445647492969999445842202717517856825, 1214649867011363567867599949068071550706, 239704513237); + _basicERC20PoolHandler.moveQuoteToken(78781837290735535753552291770891423860043710162602546000110480894858317836924, 993620562130177991745562150, 50000000000000000, 7110950); + _basicERC20PoolHandler.removeQuoteToken(3201781718151524032696177608091, 2788940717158963266260644637275, 1001719209787209063137009778273); + _basicERC20PoolHandler.removeCollateral(1481112300711317586348171215820373858161462484006514, 168870288186037165317237437722657252078900747583218061139236575915, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + + invariant_collateralBalance_CT1_CT7(); + } } From 23e74852ffde8e32d2f490d5787bf7f899fc6966 Mon Sep 17 00:00:00 2001 From: Prateek Gupta Date: Fri, 21 Apr 2023 20:47:17 +0530 Subject: [PATCH 67/70] Fix overflow underflow (#754) * Fix underflow lender.lps in kickWithDeposit * Fix overflow in _calculateInterestRate * Update redeemedLps calculation and add bucket bankruptcy check in kickWithDeposit * Merge develop * Update regression test description * PR feedback * Optimize tu mau computation --- src/libraries/external/KickerActions.sol | 26 ++++++++++++++++--- src/libraries/external/PoolCommons.sol | 2 +- .../RegressionTestReservesERC20Pool.t.sol | 10 +++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/libraries/external/KickerActions.sol b/src/libraries/external/KickerActions.sol index ed516d6cc..36bf3ad24 100644 --- a/src/libraries/external/KickerActions.sol +++ b/src/libraries/external/KickerActions.sol @@ -85,6 +85,7 @@ library KickerActions { event Kick(address indexed borrower, uint256 debt, uint256 collateral, uint256 bond); event RemoveQuoteToken(address indexed lender, uint256 indexed price, uint256 amount, uint256 lpRedeemed, uint256 lup); event KickReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); + event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); /**************/ /*** Errors ***/ @@ -207,20 +208,37 @@ library KickerActions { vars.redeemedLP = vars.bucketLP; Deposits.unscaledRemove(deposits_, index_, vars.bucketUnscaledDeposit); + vars.bucketUnscaledDeposit = 0; } else { vars.redeemedLP = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketRate); + uint256 unscaledAmountToRemove = Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketScale); Deposits.unscaledRemove( deposits_, index_, - Maths.wdiv(vars.amountToDebitFromDeposit, vars.bucketScale) + unscaledAmountToRemove ); + vars.bucketUnscaledDeposit -= unscaledAmountToRemove; } - // remove bucket LP coresponding to the amount removed from deposits - lender.lps -= vars.redeemedLP; - bucket.lps -= vars.redeemedLP; + vars.redeemedLP = Maths.min(lender.lps, vars.redeemedLP); + + uint256 bucketRemainingLP = vars.bucketLP - vars.redeemedLP; + + if (vars.bucketCollateral == 0 && vars.bucketUnscaledDeposit == 0 && bucketRemainingLP != 0) { + bucket.lps = 0; + bucket.bankruptcyTime = block.timestamp; + + emit BucketBankruptcy( + index_, + bucketRemainingLP + ); + } else { + // update lender and bucket LP balances + lender.lps -= vars.redeemedLP; + bucket.lps -= vars.redeemedLP; + } emit RemoveQuoteToken( msg.sender, diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index dbffe991a..82bda1f20 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -283,7 +283,7 @@ library PoolCommons { newInterestRate_ = poolState_.rate; // raise rates if 4*(tu-1.02*mau) < (tu+1.02*mau-1)^2-1 - if (4 * (tu - mau102) < ((tu + mau102 - 1e18) ** 2) / 1e18 - 1e18) { + if (4 * (tu - mau102) < (((tu + mau102 - 1e18) / 1e9) ** 2) - 1e18) { newInterestRate_ = Maths.wmul(poolState_.rate, INCREASE_COEFFICIENT); // decrease rates if 4*(tu-mau) > 1-(tu+mau-1)^2 } else if (4 * (tu - mau) > 1e18 - ((tu + mau - 1e18) ** 2) / 1e18) { diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 63b9f73dd..4b2bb7898 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -437,11 +437,10 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { } /* - FIXME Test was reverting when redeemedLps = bucketLps but lenderlps < redeemedLps, this happens due to slight rounding error in deposit calculation from lps - Can be fixed by updating lenderLps calculations to `lender.lps -= Maths.min(lender.lps, vars.redeemedLPs)` + Fixed by updating redeemedLP calculations to `vars.redeemedLP = Maths.min(lender.lps, vars.redeemedLP)` */ - function _test_regression_evm_revert_2() external { + function test_regression_evm_revert_2() external { _reserveERC20PoolHandler.moveQuoteToken(1, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 158129467307529830729349478455); _reserveERC20PoolHandler.removeQuoteToken(2999999999999999484865294266928579000517539849, 20462, 1576762402919713971836094859031); _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); @@ -461,11 +460,10 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { } /* - FIXME Test was reverting with overflow in `(tu + mau102 - 1e18) ** 2)` calculation in _calculateInterestRate - can be fixed by updating `((tu + mau102 - 1e18) ** 2) / 1e18` to `((tu / 1e9 + mau102 / 1e9 - 1e9) ** 2)` + Fixed by updating `((tu + mau102 - 1e18) ** 2) / 1e18` to `(((tu + mau102 - 1e18) / 1e9) ** 2)` */ - function _test_regression_evm_revert_3() external { + function test_regression_evm_revert_3() external { _reserveERC20PoolHandler.drawDebt(1000011592650618236, 427626464706163901647666438633); _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 222282777921247831223488066461); From 2b31b19a86cb7e9e21294acebb8a061354cf82ad Mon Sep 17 00:00:00 2001 From: Ian Harvey Date: Fri, 21 Apr 2023 16:13:59 -0400 Subject: [PATCH 68/70] Invariant cleanup (#741) * initial commit * initial commit * cleanup * cleanup complete * updated C4 * removed B4 * updated so it runs * more cleanup * Re ordered the Bucket invariants to include B4 again * re-added B4 back as future auditors may find it helpful * resolved merge * last cleanup * clarifying B4 * Add random time skip between each handler call in invariant testing (#757) * added bankruptcy invariant * update of regressions needed * Revert "update of regressions needed" This reverts commit 3fe1eef669f5afc5e808ab01aa3d5e1d8425f871. * Add extra parameter for time skip in all handlers, update regression tests * cleaned up merge conflicts * fixed in response to Matts comments * removed uneeded code --------- Co-authored-by: Ian Harvey Co-authored-by: Prateek Gupta Co-authored-by: prateek105 --- tests/INVARIANTS.md | 12 +- .../ERC20Pool/BasicERC20PoolInvariants.t.sol | 14 +- .../ReserveERC20PoolInvariants.t.sol | 2 - .../handlers/BasicERC20PoolHandler.sol | 30 +- .../BasicERC721PoolInvariants.t.sol | 29 +- .../handlers/BasicERC721PoolHandler.sol | 30 +- .../invariants/base/BasicInvariants.t.sol | 86 ++-- .../base/LiquidationInvariants.t.sol | 26 +- .../invariants/base/ReserveInvariants.t.sol | 20 +- .../base/handlers/BasicPoolHandler.sol | 20 +- .../base/handlers/LiquidationPoolHandler.sol | 30 +- .../base/handlers/ReservePoolHandler.sol | 10 +- .../base/handlers/unbounded/BaseHandler.sol | 14 + .../unbounded/UnboundedBasicPoolHandler.sol | 1 - .../invariants/interfaces/IBaseHandler.sol | 1 + .../RegressionTestBasicERC20Pool.t.sol | 245 +++++---- .../RegressionTestLiquidationERC20Pool.t.sol | 336 ++++++------ .../RegressionTestReservesERC20Pool.t.sol | 485 +++++++++--------- .../RegressionTestBasicERC721Pool.t.sol | 36 +- .../RegressionTestLiquidationERC20Pool.t.sol | 60 +-- .../RegressionTestReservesERC721Pool.t.sol | 72 +-- 21 files changed, 756 insertions(+), 803 deletions(-) diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 7d95d18ee..652ffa667 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -5,7 +5,7 @@ - **CT1**: pool collateral token balance (`Collateral.balanceOf(pool)`) = sum of collateral balances across all borrowers (`Borrower.collateral`) + sum of claimable collateral across all buckets (`Bucket.collateral`) - #### NFT: - **CT2**: number of tokens owned by the pool (`Collateral.balanceOf(pool)`) * `1e18` = sum of collateral across all borrowers (`Borrower.collateral`) + sum of claimable collateral across all buckets (`Bucket.collateral`) - - **CT3**: number of tokens owned by the pool (`Collateral.balanceOf(pool)` = length of borrower array token ids (`ERC721Pool.borrowerTokenIds.length`) + length of buckets array token ids (`ERC721Pool.bucketTokenIds.length`) + - **CT3**: number of tokens owned by the pool (`Collateral.balanceOf(pool)`) = length of borrower array token ids (`ERC721Pool.borrowerTokenIds.length`) + length of buckets array token ids (`ERC721Pool.bucketTokenIds.length`) - **CT4**: number of borrower token ids (`ERC721Pool.borrowerTokenIds.length`) * `1e18` >= borrower balance (`Borrower.collateral`) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids - **CT5**: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) are owned by pool contract (`Collateral.ownerOf(tokenId)`) - **CT6**: in case of subset pools: token ids in buckets array (`ERC721Pool.bucketTokenIds`) and in borrowers array (`ERC721Pool.borrowerTokenIds`) should have a mapping of `True` in allowed token ids mapping (`ERC721Pool.tokenIdsAllowed`) @@ -13,7 +13,7 @@ - **CT7**: total pledged collateral in pool (`PoolBalancesState.pledgedCollateral`) = sum of collateral balances across all borrowers (`Borrower.collateral`) ## Quote Token -- **QT1**: pool quote token balance (`Quote.balanceOf(pool)`) >= liquidation bonds (`AuctionsState.totalBondEscrowed`) + pool deposit size (`Pool.depositSize()`) + reserve auction unclaimed amount (`reserveAuction.unclaimed`) - pool t0 debt (`PoolBalancesState.t0Debt`) +- **QT1**: pool quote token balance (`Quote.balanceOf(pool)`) >= liquidation bonds (`AuctionsState.totalBondEscrowed`) + pool deposit size (`Pool.depositSize()`) + reserve auction unclaimed amount (`reserveAuction.unclaimed`) - pool t0 debt (`PoolBalancesState.t0Debt`) (with a `1e13` margin) - **QT2**: pool t0 debt (`PoolBalancesState.t0Debt`) = sum of t0 debt across all borrowers (`Borrower.t0Debt`) ## Auctions @@ -32,8 +32,8 @@ ## Buckets - **B1**: sum of LP of lenders in bucket (`Lender.lps`) = bucket LP accumulator (`Bucket.lps`) - **B2**: bucket LP accumulator (`Bucket.lps`) = 0 if no deposit / collateral in bucket -- **B3**: if no collateral or deposit in bucket then the bucket exchange rate is `1e27` -- **B4**: bankrupt bucket LP accumulator = 0; lender LP for deposits before bankruptcy time = 0 +- **B3**: if no collateral or deposit in bucket then the bucket exchange rate is `1e18` +- **B4**: bucket LP accumulator (`Bucket.lps`) = 0 when a bucket is bankrupted - **B5**: when adding / moving quote tokens or adding collateral : lender deposit time (`Lender.depositTime`) = timestamp of block when deposit happened (`block.timestamp`) - **B6**: when receiving transferred LP : receiver deposit time (`Lender.depositTime`) = max of sender and receiver deposit time - **B7**: when awarded bucket take LP : taker/kicker deposit time (`Lender.depositTime`) = timestamp of block when award happened (`block.timestamp`) @@ -51,7 +51,7 @@ - **F4**: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): `depositAtIndex(j) == 0 for i < j < findIndexOfSum(prefixSum(i)+1)` - **F5**: Global scalar is never updated (`DepositsState.scaling[8192]` is always 0) -## Exchange rate invariants ## +## Exchange rate (Margin of 1e12 - 1e16 on comparisons, dependent on amounts) - **R1**: Exchange rates are unchanged by pledging collateral - **R2**: Exchange rates are unchanged by pulling collateral - **R3**: Exchange rates are unchanged by depositing quote token into a bucket @@ -61,7 +61,7 @@ - **R7**: Exchange rates are unchanged under depositTakes - **R8**: Exchange rates are unchanged under arbTakes -## Reserves ## +## Reserves (margin of 1e15 on comparisons) - **RE1**: Reserves are unchanged by pledging collateral - **RE2**: Reserves are unchanged by removing collateral - **RE3**: Reserves increase only when depositing quote token into a bucket below LUP. Reserves increase only when moving quote tokens into a bucket below LUP. diff --git a/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol index f1b8d31e2..31e78de02 100644 --- a/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/BasicERC20PoolInvariants.t.sol @@ -18,16 +18,6 @@ import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; // contains invariants for the test contract BasicERC20PoolInvariants is BasicInvariants { - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - - * Collateral Token - * CT1: poolCtBal >= sum of all borrower's collateral + sum of all bucket's claimable collateral - * CT7: pool Pledged collateral = sum of all borrower's pledged collateral - - ****************************************************************************************************************************************/ - uint256 internal constant NUM_ACTORS = 10; TokenWithNDecimals internal _collateral; @@ -93,7 +83,7 @@ contract BasicERC20PoolInvariants is BasicInvariants { totalCollateralPledged += borrowerCollateral; } - assertEq(_erc20pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); + assertEq(_erc20pool.pledgedCollateral(), totalCollateralPledged, "Collateral Invariant CT7"); // convert pool collateral balance into WAD uint256 collateralBalance = _collateral.balanceOf(address(_erc20pool)) * 10**(18 - _collateral.decimals()); @@ -105,7 +95,7 @@ contract BasicERC20PoolInvariants is BasicInvariants { bucketCollateral += collateral; } - assertGe(collateralBalance, bucketCollateral + _erc20pool.pledgedCollateral()); + assertGe(collateralBalance, bucketCollateral + _erc20pool.pledgedCollateral(), "Collateral Invariant CT1"); } } diff --git a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol index f7e5cf753..eca4b5604 100644 --- a/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC20Pool/ReserveERC20PoolInvariants.t.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.14; -import "@std/console.sol"; - import { BaseInvariants } from '../base/BaseInvariants.sol'; import { LiquidationInvariants } from '../base/LiquidationInvariants.t.sol'; import { ReserveInvariants } from '../base/ReserveInvariants.t.sol'; diff --git a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol index 3cb90041d..9055c83b5 100644 --- a/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol +++ b/tests/forge/invariants/ERC20Pool/handlers/BasicERC20PoolHandler.sol @@ -36,8 +36,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function addCollateral( uint256 actorIndex_, uint256 amountToAdd_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.addCollateral']++; // Prepare test phase @@ -50,8 +51,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function removeCollateral( uint256 actorIndex_, uint256 amountToRemove_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.removeCollateral']++; // Prepare test phase @@ -67,8 +69,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function pledgeCollateral( uint256 actorIndex_, - uint256 amountToPledge_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToPledge_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.pledgeCollateral']++; // Prepare test phase @@ -83,8 +86,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function pullCollateral( uint256 actorIndex_, - uint256 amountToPull_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToPull_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.pullCollateral']++; // Prepare test phase @@ -96,8 +100,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function drawDebt( uint256 actorIndex_, - uint256 amountToBorrow_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToBorrow_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.drawDebt']++; // Prepare test phase @@ -112,8 +117,9 @@ contract BasicERC20PoolHandler is UnboundedBasicERC20PoolHandler, BasicPoolHandl function repayDebt( uint256 actorIndex_, - uint256 amountToRepay_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToRepay_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.repayDebt']++; // Prepare test phase diff --git a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol index c57141ec6..f102a81fb 100644 --- a/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol +++ b/tests/forge/invariants/ERC721Pool/BasicERC721PoolInvariants.t.sol @@ -18,19 +18,6 @@ import { IBaseHandler } from '../interfaces/IBaseHandler.sol'; // contains invariants for the test contract BasicERC721PoolInvariants is BasicInvariants { - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - - * Collateral Token - * CT2: number of tokens owned by the pool (Collateral.balanceOf(pool)) * 1e18 = sum of collateral across all borrowers (Borrower.collateral) + sum of claimable collateral across all buckets (Bucket.collateral) - * CT3: number of tokens owned by the pool (Collateral.balanceOf(pool) = length of borrower array token ids (ERC721Pool.borrowerTokenIds.length) + length of buckets array token ids (ERC721Pool.bucketTokenIds.length) - * CT4: number of borrower token ids (ERC721Pool.borrowerTokenIds.length) * 1e18 >= borrower balance (Borrower.collateral) Note: can be lower in case when fractional collateral that is rebalanced / moved to buckets claimable token ids - * CT5: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) are owned by pool contract (Collateral.ownerOf(tokenId)) - * CT6: in case of subset pools: token ids in buckets array (ERC721Pool.bucketTokenIds) and in borrowers array (ERC721Pool.borrowerTokenIds) should have a mapping of True in allowed token ids mapping (ERC721Pool.tokenIdsAllowed) - * CT7: total pledged collateral in pool (PoolBalancesState.pledgedCollateral) = sum of collateral balances across all borrowers (Borrower.collateral) - ****************************************************************************************************************************************/ - uint256 internal constant NUM_ACTORS = 10; NFTCollateralToken internal _collateral; @@ -96,7 +83,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { bucketCollateral += collateral; } - assertEq(collateralBalance, bucketCollateral + _erc721pool.pledgedCollateral()); + assertEq(collateralBalance, bucketCollateral + _erc721pool.pledgedCollateral(), "Collateral Invariant CT2"); } function invariant_CT3() public useCurrentTimestamp { @@ -111,7 +98,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { } uint256 bucketTokens = _erc721pool.totalBucketTokens(); - assertEq(collateralBalance, borrowerTokens + bucketTokens); + assertEq(collateralBalance, borrowerTokens + bucketTokens, "Collateral Invariant CT3"); } function invariant_CT4() public useCurrentTimestamp { @@ -123,7 +110,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { (, uint256 borrowerCollateral, ) = _erc721pool.borrowerInfo(borrower); - assertGe(borrowerTokens * 1e18, borrowerCollateral); + assertGe(borrowerTokens * 1e18, borrowerCollateral, "Collateral Invariant CT4"); } } @@ -137,7 +124,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { for (uint256 tokenIndex = 0; tokenIndex < borrowerTokens; tokenIndex++) { uint256 borrowerTokenId = _erc721pool.borrowerTokenIds(borrower, tokenIndex); - assertEq(_collateral.ownerOf(borrowerTokenId), address(_erc721pool)); + assertEq(_collateral.ownerOf(borrowerTokenId), address(_erc721pool), "Collateral Invariant CT5"); } } @@ -145,7 +132,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { for (uint256 tokenIndex = 0; tokenIndex < bucketTokens; tokenIndex++) { uint256 bucketTokenId = _erc721pool.bucketTokenIds(tokenIndex); - assertEq(_collateral.ownerOf(bucketTokenId), address(_erc721pool)); + assertEq(_collateral.ownerOf(bucketTokenId), address(_erc721pool), "Collateral Invariant CT5"); } } @@ -159,7 +146,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { for (uint256 tokenIndex = 0; tokenIndex < borrowerTokens; tokenIndex++) { uint256 borrowerTokenId = _erc721pool.borrowerTokenIds(borrower, tokenIndex); - assertTrue(_erc721pool.tokenIdsAllowed(borrowerTokenId)); + assertTrue(_erc721pool.tokenIdsAllowed(borrowerTokenId), "Collateral Invariant CT6"); } } @@ -167,7 +154,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { for (uint256 tokenIndex = 0; tokenIndex < bucketTokens; tokenIndex++) { uint256 bucketTokenId = _erc721pool.bucketTokenIds(tokenIndex); - assertTrue(_erc721pool.tokenIdsAllowed(bucketTokenId)); + assertTrue(_erc721pool.tokenIdsAllowed(bucketTokenId), "Collateral Invariant CT6"); } } } @@ -184,7 +171,7 @@ contract BasicERC721PoolInvariants is BasicInvariants { totalCollateralPledged += borrowerCollateral; } - assertEq(_erc721pool.pledgedCollateral(), totalCollateralPledged, "Incorrect Collateral Pledged"); + assertEq(_erc721pool.pledgedCollateral(), totalCollateralPledged, "Collateral Invariant CT7"); } } diff --git a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol index d3286f13d..6ae9e9ec2 100644 --- a/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol +++ b/tests/forge/invariants/ERC721Pool/handlers/BasicERC721PoolHandler.sol @@ -37,8 +37,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function addCollateral( uint256 actorIndex_, uint256 amountToAdd_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.addCollateral']++; // Prepare test phase @@ -51,8 +52,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function removeCollateral( uint256 actorIndex_, uint256 amountToRemove_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.removeCollateral']++; // Prepare test phase @@ -68,8 +70,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function pledgeCollateral( uint256 actorIndex_, - uint256 amountToPledge_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToPledge_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.pledgeCollateral']++; // Prepare test phase @@ -84,8 +87,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function pullCollateral( uint256 actorIndex_, - uint256 amountToPull_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToPull_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.pullCollateral']++; // Prepare test phase @@ -97,8 +101,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function drawDebt( uint256 actorIndex_, - uint256 amountToBorrow_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToBorrow_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.drawDebt']++; // Prepare test phase @@ -113,8 +118,9 @@ contract BasicERC721PoolHandler is UnboundedBasicERC721PoolHandler, BasicPoolHan function repayDebt( uint256 actorIndex_, - uint256 amountToRepay_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToRepay_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.repayDebt']++; // Prepare test phase diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index e5dcf137b..afbc23517 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -12,42 +12,8 @@ import { BaseInvariants } from '../base/BaseInvariants.sol'; // contains invariants for the test abstract contract BasicInvariants is BaseInvariants { - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - * Bucket - * B1: totalBucketLP === totalLenderLps - * B2: bucketLps == 0 (if bucket quote and collateral is 0) - * B3: exchangeRate == 0 (if bucket quote and collateral is 0) - * B4: bankrupt bucket LP accumulator = 0; lender LP for deposits before bankruptcy time = 0 - * B5: block.timestamp == lenderDepositTime (if lps are added to lender lp balance) - * B6: block.timestamp == max(sender's depositTime, receiver's depositTime), when receiving transferred LP - * B7: lenderDepositTime == block.timestamp (timestamp of block when taker is rewarded by bucketTake) - * Quote Token - * QT1: poolQtBal + poolDebt >= totalBondEscrowed + poolDepositSize - * QT2: pool t0 debt = sum of all borrower's t0 debt - - * Loan - * L1: for each Loan in loans array (LoansState.loans) starting from index 1, the corresponding address (Loan.borrower) is not 0x, the threshold price (Loan.thresholdPrice) is different than 0 - * L2: Loan in loans array (LoansState.loans) at index 0 has the corresponding address (Loan.borrower) equal with 0x address and the threshold price (Loan.thresholdPrice) equal with 0 - * L3: Loans array (LoansState.loans) is a max-heap with respect to t0-threshold price: the t0TP of loan at index i is >= the t0-threshold price of the loans at index 2i and 2i+1 - - * Interest Rate - * I1: Interest rate should only update once in 12 hours - * I2: ReserveAuctionState.totalInterestEarned accrues only once per block and equals to 1e18 if pool debt = 0 - * I3: Inflator should only update once per block - * I4: t0Debt2ToCollateral should sum correctly accross borrowers - - * Fenwick tree - * F1: Value represented at index i (Deposits.valueAt(i)) is equal to the accumulation of scaled values incremented or decremented from index i - * F2: For any index i, the prefix sum up to and including i is the sum of values stored in indices j<=i - * F3: For any index i < MAX_FENWICK_INDEX, findIndexOfSum(prefixSum(i)) > i - * F4: For any index i, there is zero deposit above i and below findIndexOfSum(prefixSum(i) + 1): findIndexOfSum(prefixSum(i)) == findIndexOfSum(prefixSum(j) - deposits.valueAt(j)), where j is the next index from i with deposits != 0 - * F5: Global scalar is never updated (`DepositsState.values[8192]` is always 0) - ****************************************************************************************************************************************/ - // checks pool lps are equal to sum of all lender lps in a bucket - function invariant_Lps_B1_B4() public useCurrentTimestamp { + function invariant_Lps_B1() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { @@ -62,12 +28,25 @@ abstract contract BasicInvariants is BaseInvariants { (uint256 bucketLps, , , , ) = _pool.bucketInfo(bucketIndex); - assertEq(bucketLps, totalLps, "Incorrect Bucket/lender lps"); + assertEq(bucketLps, totalLps, "Buckets Invariant B1"); + } + } + + // checks pool lps are equal to sum of all lender lps in a bucket + function invariant_Lps_B4() public useCurrentTimestamp { + + for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { + + // if bucket bankruptcy occured, then previousBankruptcy should be equal to current timestamp + if (IBaseHandler(_handler).previousBankruptcy(bucketIndex) == block.timestamp) { + (uint256 bucketLps, , , , ) = _pool.bucketInfo(bucketIndex); + assertEq(bucketLps, 0, "Buckets Invariant B4"); + } } } // checks bucket lps are equal to 0 if bucket quote and collateral are 0 - // checks exchange rate is 1e27 if bucket quote and collateral are 0 + // checks exchange rate is 1e18 if bucket quote and collateral are 0 function invariant_Buckets_B2_B3() public useCurrentTimestamp { for (uint256 bucketIndex = LENDER_MIN_BUCKET_INDEX; bucketIndex <= LENDER_MAX_BUCKET_INDEX; bucketIndex++) { ( @@ -80,8 +59,8 @@ abstract contract BasicInvariants is BaseInvariants { ) = _poolInfo.bucketInfo(address(_pool), bucketIndex); if (collateral == 0 && deposit == 0) { - require(bucketLps == 0, "Incorrect bucket lps"); - require(exchangeRate == 1e18, "Incorrect exchange rate"); + require(bucketLps == 0, "Buckets Invariant B2"); + require(exchangeRate == 1e18, "Buckets Invariant B3"); } } } @@ -98,7 +77,7 @@ abstract contract BasicInvariants is BaseInvariants { require( depositTime == IBaseHandler(_handler).lenderDepositTime(lender, bucketIndex), - "Incorrect deposit Time" + "Buckets Invariant B5, B6 or B7" ); } } @@ -126,7 +105,7 @@ abstract contract BasicInvariants is BaseInvariants { assets, liabilities, 1e13, - "Incorrect pool quote token" + "Quote Token Invariant QT1" ); } @@ -144,7 +123,7 @@ abstract contract BasicInvariants is BaseInvariants { uint256 poolDebt = _pool.totalT0Debt(); - require(poolDebt == totalDebt, "Incorrect pool debt"); + require(poolDebt == totalDebt, "Quote Token Invariant QT2"); } function invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8() public useCurrentTimestamp { @@ -162,21 +141,22 @@ abstract contract BasicInvariants is BaseInvariants { console.log("Current bucket lps -->", bucketLps); console.log("======================================"); - + // This edge case is if less than 1 one millionth (0.000_001) of a quote token is inserted into a single bucket if (bucketLps < 1e12) { requireWithinDiff( Maths.wmul(currentExchangeRate, bucketLps), Maths.wmul(previousExchangeRate, bucketLps), - 1e16, // allow changes up to 0.01 qt in value if bucket LP < 1e-6 - "Incorrect exchange Rate changed" + 1e16, // allow changes up to 0.01 qt in value if bucket LPs < 1e-6 + "Exchange Rate Invariant R1, R2, R3, R4, R5, R6, R7 or R8" ); } else { + // Common case, 1 one millionth (0.000_001) of a quote token or greater is inserted into a single bucket requireWithinDiff( currentExchangeRate, previousExchangeRate, 1e12, // otherwise require exchange rates to be within 1e-6 - "Incorrect exchange Rate changed" - ); + "Exchange Rate Invariant R1, R2, R3, R4, R5, R6, R7 or R8" + ); } } } @@ -186,8 +166,8 @@ abstract contract BasicInvariants is BaseInvariants { (address borrower, uint256 tp) = _pool.loanInfo(0); // first loan in loan heap should be 0 - require(borrower == address(0), "Incorrect borrower"); - require(tp == 0, "Incorrect threshold price"); + require(borrower == address(0), "Loan Invariant L2"); + require(tp == 0, "Loan Invariant L2"); ( , , uint256 totalLoans) = _pool.loansInfo(); @@ -195,15 +175,15 @@ abstract contract BasicInvariants is BaseInvariants { (borrower, tp) = _pool.loanInfo(loanId); // borrower address and threshold price should not 0 - require(borrower != address(0), "Incorrect borrower"); - require(tp != 0, "Incorrect threshold price"); + require(borrower != address(0), "Loan Invariant L1"); + require(tp != 0, "Loan Invariant L1"); // tp of a loan at index 'i' in loan array should be greater than equals to loans at index '2i' and '2i+1' (, uint256 tp1) = _pool.loanInfo(2 * loanId); (, uint256 tp2) = _pool.loanInfo(2 * loanId + 1); - require(tp >= tp1, "Incorrect loan heap"); - require(tp >= tp2, "Incorrect loan heap"); + require(tp >= tp1, "Loan Invariant L3"); + require(tp >= tp2, "Loan Invariant L3"); } } diff --git a/tests/forge/invariants/base/LiquidationInvariants.t.sol b/tests/forge/invariants/base/LiquidationInvariants.t.sol index 428b2528a..b10165275 100644 --- a/tests/forge/invariants/base/LiquidationInvariants.t.sol +++ b/tests/forge/invariants/base/LiquidationInvariants.t.sol @@ -9,18 +9,6 @@ import { BasicInvariants } from './BasicInvariants.t.sol'; abstract contract LiquidationInvariants is BasicInvariants { - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - * Auction - * A1: totalDebtInAuction = sum of all debt of all borrowers kicked - * A2: totalBondEscrowed = sum of all kicker's bond = total Bond in Auction - * A3: number of borrowers with debt = number of loans + number of auctioned borrowers - * A4: number of auctions = total borrowers kicked - * A5: for each auction, kicker locked bond is more than equal to auction bond - * A6: if a Liquidation is not taken then the take flag (Liquidation.alreadyTaken) should be False, if already taken then the take flag should be True - ****************************************************************************************************************************************/ - // checks sum of all borrower's t0debt is equals to total pool t0debtInAuction function invariant_debtInAuction_A1() public useCurrentTimestamp { uint256 actorCount = IBaseHandler(_handler).getActorsCount(); @@ -36,7 +24,7 @@ abstract contract LiquidationInvariants is BasicInvariants { } } - require(_pool.totalT0DebtInAuction() == totalT0debtInAuction, "Incorrect debt in auction"); + require(_pool.totalT0DebtInAuction() == totalT0debtInAuction, "Auction Invariant A1"); } // checks sum of all kicker bond is equal to total pool bond @@ -53,7 +41,7 @@ abstract contract LiquidationInvariants is BasicInvariants { (uint256 totalPoolBond, , , ) = _pool.reservesInfo(); - require(totalPoolBond == totalKickerBond, "Incorrect bond"); + require(totalPoolBond == totalKickerBond, "Auction Invariant A2"); } // checks total borrowers with debt is equals to sum of borrowers unkicked and borrowers kicked @@ -74,7 +62,7 @@ abstract contract LiquidationInvariants is BasicInvariants { require( totalBorrowersWithDebt == loansCount + totalAuction, - "incorrect no of borrowers in LoanState" + "Auction Invariant A3" ); uint256 borrowersKicked; @@ -87,7 +75,7 @@ abstract contract LiquidationInvariants is BasicInvariants { if (kickTime != 0) borrowersKicked += 1; } - require(borrowersKicked == totalAuction, "Incorrect borrowers in auction"); + require(totalAuction == borrowersKicked, "Auction Invariant A4"); } // for each auction, kicker locked bond is more than equal to auction bond @@ -99,7 +87,7 @@ abstract contract LiquidationInvariants is BasicInvariants { (address kicker, , uint256 bondSize, , , , , , , ) = _pool.auctionInfo(borrower); (, uint256 lockedAmount) = _pool.kickerInfo(kicker); - require(lockedAmount >= bondSize, "Incorrect bond locked"); + require(lockedAmount >= bondSize, "Auction Invariant A5"); } } @@ -113,11 +101,11 @@ abstract contract LiquidationInvariants is BasicInvariants { require( alreadyTaken == IBaseHandler(_handler).alreadyTaken(borrower), - "Incorrect take call on auction" + "Auction Invariant A6" ); } } - + function invariant_call_summary() public virtual override useCurrentTimestamp { console.log("\nCall Summary\n"); console.log("--Lender----------"); diff --git a/tests/forge/invariants/base/ReserveInvariants.t.sol b/tests/forge/invariants/base/ReserveInvariants.t.sol index a530c0fbf..294581d43 100644 --- a/tests/forge/invariants/base/ReserveInvariants.t.sol +++ b/tests/forge/invariants/base/ReserveInvariants.t.sol @@ -9,27 +9,9 @@ import { LiquidationInvariants } from './LiquidationInvariants.t.sol'; abstract contract ReserveInvariants is LiquidationInvariants { - /**************************************************************************************************************************************/ - /*** Invariant Tests ***/ - /*************************************************************************************************************************************** - * Reserves - * RE1 : Reserves are unchanged by pledging collateral - * RE2 : Reserves are unchanged by removing collateral - * RE3 : Reserves are unchanged by depositing quote token into a bucket - * RE4 : Reserves are unchanged by withdrawing deposit (quote token) from a bucket after the penalty period hes expired - * RE5 : Reserves are unchanged by adding collateral token into a bucket - * RE6 : Reserves are unchanged by removing collateral token from a bucket - * RE7 : Reserves increase by 7% of the loan quantity upon the first take (including depositTake or arbTake) and increase/decrease by bond penalty/reward on take. - * RE8 : Reserves are unchanged under takes/depositTakes/arbTakes after the first take but increase/decrease by bond penalty/reward on take. - * RE9 : Reserves increase by 3 months of interest when a loan is kicked - * 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 - ****************************************************************************************************************************************/ - function invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12() public useCurrentTimestamp { - uint256 previousReserves = IBaseHandler(_handler).previousReserves(); + uint256 previousReserves = IBaseHandler(_handler).previousReserves(); uint256 increaseInReserves = IBaseHandler(_handler).increaseInReserves(); uint256 decreaseInReserves = IBaseHandler(_handler).decreaseInReserves(); (uint256 currentReserves, , , , ) = _poolInfo.poolReservesInfo(address(_pool)); diff --git a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol index e19d14fee..5190ebcee 100644 --- a/tests/forge/invariants/base/handlers/BasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/BasicPoolHandler.sol @@ -20,8 +20,9 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { function addQuoteToken( uint256 actorIndex_, uint256 amountToAdd_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.addQuoteToken']++; // Prepare test phase @@ -34,8 +35,9 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { function removeQuoteToken( uint256 actorIndex_, uint256 amountToRemove_, - uint256 bucketIndex_ - ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.removeQuoteToken']++; // Prepare test phase @@ -49,8 +51,9 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 actorIndex_, uint256 amountToMove_, uint256 fromIndex_, - uint256 toIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 toIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BBasicHandler.moveQuoteToken']++; // Prepare test phase @@ -68,8 +71,9 @@ abstract contract BasicPoolHandler is UnboundedBasicPoolHandler { uint256 fromActorIndex_, uint256 toActorIndex_, uint256 lpsToTransfer_, - uint256 bucketIndex_ - ) external useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(fromActorIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { // Prepare test phase (address receiver, uint256 boundedLps) = _preTransferLps(toActorIndex_, lpsToTransfer_); diff --git a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol index 800244f51..59e677514 100644 --- a/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/LiquidationPoolHandler.sol @@ -14,22 +14,25 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas function kickAuction( uint256 borrowerIndex_, uint256 amount_, - uint256 kickerIndex_ - ) external useTimestamps { + uint256 kickerIndex_, + uint256 skippedTime_ + ) external useTimestamps skipTime(skippedTime_) { _kickAuction(borrowerIndex_, amount_, kickerIndex_); } function kickWithDeposit( uint256 kickerIndex_, - uint256 bucketIndex_ - ) external useRandomActor(kickerIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps { + uint256 bucketIndex_, + uint256 skippedTime_ + ) external useRandomActor(kickerIndex_) useRandomLenderBucket(bucketIndex_) useTimestamps skipTime(skippedTime_) { _kickWithDeposit(_lenderBucketIndex); } function withdrawBonds( uint256 kickerIndex_, - uint256 maxAmount_ - ) external useRandomActor(kickerIndex_) useTimestamps { + uint256 maxAmount_, + uint256 skippedTime_ + ) external useRandomActor(kickerIndex_) useTimestamps skipTime(skippedTime_) { _withdrawBonds(_actor, maxAmount_); } @@ -40,8 +43,9 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas function takeAuction( uint256 borrowerIndex_, uint256 amount_, - uint256 takerIndex_ - ) external useRandomActor(takerIndex_) useTimestamps { + uint256 takerIndex_, + uint256 skippedTime_ + ) external useRandomActor(takerIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BLiquidationHandler.takeAuction']++; // Prepare test phase @@ -60,8 +64,9 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas uint256 borrowerIndex_, uint256 bucketIndex_, bool depositTake_, - uint256 takerIndex_ - ) external useRandomActor(takerIndex_) useTimestamps { + uint256 takerIndex_, + uint256 skippedTime_ + ) external useRandomActor(takerIndex_) useTimestamps skipTime(skippedTime_) { numberOfCalls['BLiquidationHandler.bucketTake']++; // Prepare test phase @@ -81,8 +86,9 @@ abstract contract LiquidationPoolHandler is UnboundedLiquidationPoolHandler, Bas function settleAuction( uint256 actorIndex_, uint256 borrowerIndex_, - uint256 kickerIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 kickerIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { // prepare phase address actor = _actor; diff --git a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol index fe326493a..2d7b1e4dc 100644 --- a/tests/forge/invariants/base/handlers/ReservePoolHandler.sol +++ b/tests/forge/invariants/base/handlers/ReservePoolHandler.sol @@ -14,16 +14,18 @@ abstract contract ReservePoolHandler is UnboundedReservePoolHandler, Liquidation /*******************************/ function kickReserveAuction( - uint256 actorIndex_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 actorIndex_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { // Action phase _kickReserveAuction(); } function takeReserves( uint256 actorIndex_, - uint256 amountToTake_ - ) external useRandomActor(actorIndex_) useTimestamps { + uint256 amountToTake_, + uint256 skippedTime_ + ) external useRandomActor(actorIndex_) useTimestamps skipTime(skippedTime_) { // Prepare test phase uint256 boundedAmount = _preTakeReserves(amountToTake_); diff --git a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol index 75387591c..718b3dd2e 100644 --- a/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol @@ -63,6 +63,7 @@ abstract contract BaseHandler is Test { // exchange rate invariant test state mapping(uint256 => bool) public exchangeRateShouldNotChange; // bucket exchange rate invariant check mapping(uint256 => uint256) public previousExchangeRate; // mapping from bucket index to exchange rate before action + mapping(uint256 => uint256) public previousBankruptcy; // mapping from bucket index to last bankruptcy before action // reserves invariant test state uint256 public previousReserves; // reserves before action @@ -110,6 +111,16 @@ abstract contract BaseHandler is Test { testContract.setCurrentTimestamp(block.timestamp); } + /** + * @dev Skips some time before each action + */ + modifier skipTime(uint256 time_) { + time_ = constrictToRange(time_, 0, 24 hours); + vm.warp(block.timestamp + time_); + + _; + } + /** * @dev Resets all local states before each action. */ @@ -211,6 +222,9 @@ abstract contract BaseHandler is Test { exchangeRateShouldNotChange[bucketIndex] = false; // record exchange rate before each action previousExchangeRate[bucketIndex] = _pool.bucketExchangeRate(bucketIndex); + // record bankrupcy block before each action + (,,uint256 bankruptcyTimestamp,,) = _pool.bucketInfo(bucketIndex); + previousBankruptcy[bucketIndex] = bankruptcyTimestamp; } // reset the reserves before each action diff --git a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol index 5b70e1c27..eab1a9b26 100644 --- a/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol +++ b/tests/forge/invariants/base/handlers/unbounded/UnboundedBasicPoolHandler.sol @@ -67,7 +67,6 @@ abstract contract UnboundedBasicPoolHandler is BaseHandler { (uint256 lpBalanceBeforeAction, ) = _pool.lenderInfo(bucketIndex_, _actor); 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; diff --git a/tests/forge/invariants/interfaces/IBaseHandler.sol b/tests/forge/invariants/interfaces/IBaseHandler.sol index 74da2422f..ef620bd15 100644 --- a/tests/forge/invariants/interfaces/IBaseHandler.sol +++ b/tests/forge/invariants/interfaces/IBaseHandler.sol @@ -18,6 +18,7 @@ interface IBaseHandler { function exchangeRateShouldNotChange(uint256) external view returns(bool); function previousExchangeRate(uint256) external view returns(uint256); + function previousBankruptcy(uint256) external view returns(uint256); function isKickerRewarded() external view returns(bool); function kickerBondChange() external view returns(uint256); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol index 9faf8c231..7b9f62344 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestBasicERC20Pool.t.sol @@ -11,72 +11,67 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { } function test_regression_Underflow_1() external { - _basicERC20PoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000); + _basicERC20PoolHandler.addQuoteToken(14227, 5211, 3600000000000000000000, 0); // check invariants hold true invariant_quoteTokenBalance_QT1(); } function test_regression_exchange_rate_1() external { - _basicERC20PoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809); - _basicERC20PoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592); - _basicERC20PoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636); + _basicERC20PoolHandler.addQuoteToken(999999999844396154169639088436193915956854451, 6879, 2809, 0); + _basicERC20PoolHandler.addCollateral(2, 36429077592820139327392187131, 202214962129783771592, 0); + _basicERC20PoolHandler.removeCollateral(1, 2296695924278944779257290397234298756, 10180568736759156593834642286260647915348262280903719122483474452532722106636, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 function test_regression_exchange_rate_2() external { - _basicERC20PoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233); - _basicERC20PoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); - _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _basicERC20PoolHandler.removeCollateral(1, 1e36, 2570); - _basicERC20PoolHandler.removeQuoteToken(1, 1e36, 2570); - _basicERC20PoolHandler.removeCollateral(2, 1e36, 2570); - _basicERC20PoolHandler.removeQuoteToken(2, 1e36, 2570); + _basicERC20PoolHandler.addQuoteToken(211670885988646987334214990781526025942, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6894274025938223490357894120267612065037086600750070030707794233, 0); + _basicERC20PoolHandler.addCollateral(117281, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 0); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 12612911637698029036253737442696522, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _basicERC20PoolHandler.removeCollateral(1, 1e36, 2570, 0); + _basicERC20PoolHandler.removeQuoteToken(1, 1e36, 2570, 0); + _basicERC20PoolHandler.removeCollateral(2, 1e36, 2570, 0); + _basicERC20PoolHandler.removeQuoteToken(2, 1e36, 2570, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // test will fail when actors = 10, buckets = [2570], maxAmount = 1e36 function test_regression_exchange_rate_3() external { - _basicERC20PoolHandler.addQuoteToken(2842, 304, 2468594405605444095992); - _basicERC20PoolHandler.addCollateral(0, 1, 3); - _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _basicERC20PoolHandler.removeCollateral(0, 1, 3); + _basicERC20PoolHandler.addQuoteToken(2842, 304, 2468594405605444095992, 0); + _basicERC20PoolHandler.addCollateral(0, 1, 3, 0); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _basicERC20PoolHandler.removeCollateral(0, 1, 3, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 function test_regression_exchange_rate_4() external { - _basicERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 587135207579305083672251579076072787077); - _basicERC20PoolHandler.removeCollateral(712291886391993882782748602346033231793324080118979183300958, 673221151277569661050873992210938589, 999999997387885196930781163353866909746906615); - _basicERC20PoolHandler.removeCollateral(4434852123445331038838, 92373980881732279172264, 16357203); - _basicERC20PoolHandler.addQuoteToken(6532756, 16338, 2488340072929715905208495398161339232954907500634); - _basicERC20PoolHandler.removeCollateral(934473801621702106582064701468475360, 999999998588451849650292641565069384488310108, 2726105246641027837873401505120164058057757115396); - _basicERC20PoolHandler.addQuoteToken(0, 3272, 688437777000000000); - _basicERC20PoolHandler.removeQuoteToken(36653992905059663682442427, 3272, 688437777000000000); + _basicERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 587135207579305083672251579076072787077, 0); + _basicERC20PoolHandler.removeCollateral(712291886391993882782748602346033231793324080118979183300958, 673221151277569661050873992210938589, 999999997387885196930781163353866909746906615, 0); + _basicERC20PoolHandler.removeCollateral(4434852123445331038838, 92373980881732279172264, 16357203, 0); + _basicERC20PoolHandler.addQuoteToken(6532756, 16338, 2488340072929715905208495398161339232954907500634, 0); + _basicERC20PoolHandler.removeCollateral(934473801621702106582064701468475360, 999999998588451849650292641565069384488310108, 2726105246641027837873401505120164058057757115396, 0); + _basicERC20PoolHandler.addQuoteToken(0, 3272, 688437777000000000, 0); + _basicERC20PoolHandler.removeQuoteToken(36653992905059663682442427, 3272, 688437777000000000, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // test was failing when actors = 1, buckets = [2570], maxAmount = 1e36 function test_regression_exchange_rate_5() external { - _basicERC20PoolHandler.drawDebt(1156, 1686); + _basicERC20PoolHandler.drawDebt(1156, 1686, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicERC20PoolHandler.addQuoteToken(711, 2161, 2012); + _basicERC20PoolHandler.addQuoteToken(711, 2161, 2012, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } - // test was failing when actors = 1, buckets = [2570] function test_regression_exchange_rate_6() external { - _basicERC20PoolHandler.addCollateral(999999999000000000000000081002632733724231666, 999999999243662968633890481597751057821356823, 1827379824097500721086759239664926559); - _basicERC20PoolHandler.addQuoteToken(108018811574020559, 3, 617501271956497833026154369680502407518122199901237699791086943); - _basicERC20PoolHandler.addCollateral(95036573736725249741129171676163161793295193492729984020, 5009341426566798172861627799, 2); - _basicERC20PoolHandler.removeCollateral(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 5814100241); + _basicERC20PoolHandler.addCollateral(999999999000000000000000081002632733724231666, 999999999243662968633890481597751057821356823, 1827379824097500721086759239664926559, 0); + _basicERC20PoolHandler.addQuoteToken(108018811574020559, 3, 617501271956497833026154369680502407518122199901237699791086943, 0); + _basicERC20PoolHandler.addCollateral(95036573736725249741129171676163161793295193492729984020, 5009341426566798172861627799, 2, 0); + _basicERC20PoolHandler.removeCollateral(1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 5814100241, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } @@ -84,142 +79,142 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 // Fixed with commit -> https://github.com/ajna-finance/contracts/pull/613/commits/f106f0f7c96c1662325bdb5151fd745544e6dce0 function test_regression_exchange_rate_7() external { - _basicERC20PoolHandler.addCollateral(999999999249784004703856120761629301735818638, 15200, 2324618456838396048595845067026807532884041462750983926777912015561); - _basicERC20PoolHandler.addQuoteToken(0, 2, 60971449684543878); - _basicERC20PoolHandler.addCollateral(0, 648001392760875820320327007315181208349883976901103343226563974622543668416, 38134304133913510899173609232567613); - _basicERC20PoolHandler.removeCollateral(0, 1290407354289435191451647900348688457414638662069174249777953, 125945131546441554612275631955778759442752893948134984981883798); + _basicERC20PoolHandler.addCollateral(999999999249784004703856120761629301735818638, 15200, 2324618456838396048595845067026807532884041462750983926777912015561, 0); + _basicERC20PoolHandler.addQuoteToken(0, 2, 60971449684543878, 0); + _basicERC20PoolHandler.addCollateral(0, 648001392760875820320327007315181208349883976901103343226563974622543668416, 38134304133913510899173609232567613, 0); + _basicERC20PoolHandler.removeCollateral(0, 1290407354289435191451647900348688457414638662069174249777953, 125945131546441554612275631955778759442752893948134984981883798, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } // test was failing when actors = 10, buckets = [2570], maxAmount = 1e36 function test_regression_exchange_rate_8() external { - _basicERC20PoolHandler.drawDebt(0, 10430); + _basicERC20PoolHandler.drawDebt(0, 10430, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicERC20PoolHandler.addCollateral(86808428701435509359888008280539191473421, 35, 89260656586096811497271673595050); + _basicERC20PoolHandler.addCollateral(86808428701435509359888008280539191473421, 35, 89260656586096811497271673595050, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_exchange_rate_9() external { - _basicERC20PoolHandler.addQuoteToken(179828875014111774829603408358905079754763388655646874, 39999923045226513122629818514849844245682430, 12649859691422584279364490330583846883); + _basicERC20PoolHandler.addQuoteToken(179828875014111774829603408358905079754763388655646874, 39999923045226513122629818514849844245682430, 12649859691422584279364490330583846883, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicERC20PoolHandler.addCollateral(472, 2100, 11836); + _basicERC20PoolHandler.addCollateral(472, 2100, 11836, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); - _basicERC20PoolHandler.pledgeCollateral(7289, 8216); + _basicERC20PoolHandler.pledgeCollateral(7289, 8216, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_fenwick_deposit_1() external { - _basicERC20PoolHandler.addQuoteToken(60321923115154876306287876901335341390357684483818363750, 2, 0); - _basicERC20PoolHandler.repayDebt(58055409653178, 2); + _basicERC20PoolHandler.addQuoteToken(60321923115154876306287876901335341390357684483818363750, 2, 0, 0); + _basicERC20PoolHandler.repayDebt(58055409653178, 2, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_fenwick_deposit_2() external { - _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2146593659305556796718319988088528090847459411703413796483450011160); - _basicERC20PoolHandler.addCollateral(16885296866566559818671993560820380984757301691657405859955072474117, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 7764878663795446754367); - _basicERC20PoolHandler.removeCollateral(999999997000000000000000000000000000000756426, 7366, 4723); - _basicERC20PoolHandler.addQuoteToken(5673, 8294, 11316); - _basicERC20PoolHandler.moveQuoteToken(919997327910338711763724656061931477, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3933155006830995444792575696); + _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 2146593659305556796718319988088528090847459411703413796483450011160, 0); + _basicERC20PoolHandler.addCollateral(16885296866566559818671993560820380984757301691657405859955072474117, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 7764878663795446754367, 0); + _basicERC20PoolHandler.removeCollateral(999999997000000000000000000000000000000756426, 7366, 4723, 0); + _basicERC20PoolHandler.addQuoteToken(5673, 8294, 11316, 0); + _basicERC20PoolHandler.moveQuoteToken(919997327910338711763724656061931477, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3933155006830995444792575696, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_fenwick_deposit_3() external { - _basicERC20PoolHandler.pullCollateral(64217420783566726909348297066823202824683000164554083, 651944294303386510182040138076901697073); - _basicERC20PoolHandler.removeQuoteToken(172614182, 2999, 725); - _basicERC20PoolHandler.addQuoteToken(52646814442098488638488433580148374391481084017027388775686120188766352301, 5021, 16410); - _basicERC20PoolHandler.moveQuoteToken(2, 1, 3, 11769823729834119405789456482320067049929344685247053661486); - _basicERC20PoolHandler.moveQuoteToken(1, 2833727997543655292227288672285470, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 7962018528962356191057551420322350); + _basicERC20PoolHandler.pullCollateral(64217420783566726909348297066823202824683000164554083, 651944294303386510182040138076901697073, 0); + _basicERC20PoolHandler.removeQuoteToken(172614182, 2999, 725, 0); + _basicERC20PoolHandler.addQuoteToken(52646814442098488638488433580148374391481084017027388775686120188766352301, 5021, 16410, 0); + _basicERC20PoolHandler.moveQuoteToken(2, 1, 3, 11769823729834119405789456482320067049929344685247053661486, 0); + _basicERC20PoolHandler.moveQuoteToken(1, 2833727997543655292227288672285470, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 7962018528962356191057551420322350, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_fenwick_deposit_4() external { - _basicERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 2, 1267); - _basicERC20PoolHandler.pledgeCollateral(1700127358962530, 0); - _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); + _basicERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 2, 1267, 0); + _basicERC20PoolHandler.pledgeCollateral(1700127358962530, 0, 0); + _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_fenwick_deposit_5() external { - _basicERC20PoolHandler.repayDebt(281, 1502); - _basicERC20PoolHandler.addCollateral(5529, 1090, 5431); - _basicERC20PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + _basicERC20PoolHandler.repayDebt(281, 1502, 0); + _basicERC20PoolHandler.addCollateral(5529, 1090, 5431, 0); + _basicERC20PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_fenwick_deposit_6() external { - _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); - _basicERC20PoolHandler.addQuoteToken(1000000000000000, 19319, 308); - _basicERC20PoolHandler.pullCollateral(4218, 4175); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 0, 0); + _basicERC20PoolHandler.addQuoteToken(1000000000000000, 19319, 308, 0); + _basicERC20PoolHandler.pullCollateral(4218, 4175, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_fenwick_prefixSum_1() external { - _basicERC20PoolHandler.addQuoteToken(5851, 999999999999999999999999999999000087, 1938); - _basicERC20PoolHandler.addCollateral(135454721201807374404103595951250949, 172411742705067521609848985260337891060745418778973, 3); - _basicERC20PoolHandler.pledgeCollateral(2, 185978674898652363737734333012844452989790885966093618883814734917759475); - _basicERC20PoolHandler.moveQuoteToken(976453319, 2825105681459470134743617749102858205411027017903767825282483319, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 145056082857394229503325854914710239303685607721150607568547620026); + _basicERC20PoolHandler.addQuoteToken(5851, 999999999999999999999999999999000087, 1938, 0); + _basicERC20PoolHandler.addCollateral(135454721201807374404103595951250949, 172411742705067521609848985260337891060745418778973, 3, 0); + _basicERC20PoolHandler.pledgeCollateral(2, 185978674898652363737734333012844452989790885966093618883814734917759475, 0); + _basicERC20PoolHandler.moveQuoteToken(976453319, 2825105681459470134743617749102858205411027017903767825282483319, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 145056082857394229503325854914710239303685607721150607568547620026, 0); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_fenwick_index_1() external { - _basicERC20PoolHandler.addQuoteToken(3056, 915, 1594); - _basicERC20PoolHandler.pullCollateral(274694202801760577094218807, 1); - _basicERC20PoolHandler.addQuoteToken(1088, 3407, 3555); - _basicERC20PoolHandler.addCollateral(1557, 13472, 15303); - _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 40692552539277917058910464963); - _basicERC20PoolHandler.pullCollateral(1131485716992204156645660898919702, 30971207810832254868222941038507448); - _basicERC20PoolHandler.removeCollateral(27428712668923640148402320299830959263828759458932482391338247903954077260349, 1136, 3944); - _basicERC20PoolHandler.moveQuoteToken(9746204317995874651524496302383356801834068305156642323380998069579800880, 1723109236200550802774859945265636287, 3213180193920898024510373220802133410941904907229061207617048152428481, 0); + _basicERC20PoolHandler.addQuoteToken(3056, 915, 1594, 0); + _basicERC20PoolHandler.pullCollateral(274694202801760577094218807, 1, 0); + _basicERC20PoolHandler.addQuoteToken(1088, 3407, 3555, 0); + _basicERC20PoolHandler.addCollateral(1557, 13472, 15303, 0); + _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 40692552539277917058910464963, 0); + _basicERC20PoolHandler.pullCollateral(1131485716992204156645660898919702, 30971207810832254868222941038507448, 0); + _basicERC20PoolHandler.removeCollateral(27428712668923640148402320299830959263828759458932482391338247903954077260349, 1136, 3944, 0); + _basicERC20PoolHandler.moveQuoteToken(9746204317995874651524496302383356801834068305156642323380998069579800880, 1723109236200550802774859945265636287, 3213180193920898024510373220802133410941904907229061207617048152428481, 0, 0); invariant_fenwick_bucket_index_F3(); } function test_regression_transferLps_1() external { - _basicERC20PoolHandler.transferLps(0, 1, 200, 2570); + _basicERC20PoolHandler.transferLps(0, 1, 200, 2570, 0); invariant_Bucket_deposit_time_B5_B6_B7(); } function test_regression_transferLps_2() external { - _basicERC20PoolHandler.transferLps(37233021465377552730514154972012012669272, 45957263314208417069590941186697869465410494677646946058359554, 405, 89727160292150007024940); + _basicERC20PoolHandler.transferLps(37233021465377552730514154972012012669272, 45957263314208417069590941186697869465410494677646946058359554, 405, 89727160292150007024940, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_transferLps_3() external { - _basicERC20PoolHandler.transferLps(1795, 6198, 3110, 11449); + _basicERC20PoolHandler.transferLps(1795, 6198, 3110, 11449, 0); invariant_Bucket_deposit_time_B5_B6_B7(); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_pull_collateral_when_encumbered_greater_than_pledged() external { - _basicERC20PoolHandler.drawDebt(1535776046383997344779595, 5191646246012456798576386242824793107669233); - _basicERC20PoolHandler.transferLps(17293, 19210, 227780, 999999999999999999999999999999999999999999997); - _basicERC20PoolHandler.removeQuoteToken(0, 0, 2); - _basicERC20PoolHandler.pullCollateral(115, 149220); + _basicERC20PoolHandler.drawDebt(1535776046383997344779595, 5191646246012456798576386242824793107669233, 0); + _basicERC20PoolHandler.transferLps(17293, 19210, 227780, 999999999999999999999999999999999999999999997, 0); + _basicERC20PoolHandler.removeQuoteToken(0, 0, 2, 0); + _basicERC20PoolHandler.pullCollateral(115, 149220, 0); } function test_regression_incorrect_zero_deposit_buckets_1() external { - _basicERC20PoolHandler.repayDebt(15119, 6786); - _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1578322581132549441186648538841, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _basicERC20PoolHandler.repayDebt(15119, 6786, 0); + _basicERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1578322581132549441186648538841, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); invariant_fenwick_prefixSumIndex_F4(); } @@ -227,35 +222,35 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { uint256 depositAt2570 = 570036521745120847917211; uint256 depositAt2571 = _basicERC20PoolHandler.constrictToRange(2578324552477056269186646552413, 1e6, 1e28); uint256 depositAt2572 = _basicERC20PoolHandler.constrictToRange(1212, 1e6, 1e28); - _basicERC20PoolHandler.addQuoteToken(1, depositAt2570, 2570); - _basicERC20PoolHandler.addQuoteToken(1, depositAt2571, 2571); - _basicERC20PoolHandler.addQuoteToken(1, depositAt2572, 2572); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2570, 2570, 0); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2571, 2571, 0); + _basicERC20PoolHandler.addQuoteToken(1, depositAt2572, 2572, 0); assertEq(_pool.depositIndex(depositAt2570), 2570); assertEq(_pool.depositIndex(depositAt2570 + depositAt2571), 2571); assertEq(_pool.depositIndex(depositAt2570 + depositAt2571 + depositAt2572), 2572); } function test_regression_collateralBalance_CT1_CT7() external { - _basicERC20PoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _basicERC20PoolHandler.repayDebt(2712912126128356234217, 251720382485531952743041849848); - _basicERC20PoolHandler.addQuoteToken(253022590763482364356576159, 999999999999999273028438503236995092261608400, 712808213364422679443324012750); - _basicERC20PoolHandler.removeQuoteToken(121890555084215923472733925382, 0, 3); + _basicERC20PoolHandler.pullCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _basicERC20PoolHandler.repayDebt(2712912126128356234217, 251720382485531952743041849848, 0); + _basicERC20PoolHandler.addQuoteToken(253022590763482364356576159, 999999999999999273028438503236995092261608400, 712808213364422679443324012750, 0); + _basicERC20PoolHandler.removeQuoteToken(121890555084215923472733925382, 0, 3, 0); invariant_collateralBalance_CT1_CT7(); } function test_regression_invariant_quoteTokenBalance_QT1() external { - _basicERC20PoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098); - _basicERC20PoolHandler.repayDebt(1, 2); - _basicERC20PoolHandler.removeCollateral(200953640940463935290718680397023889633667961549, 2481, 3); - _basicERC20PoolHandler.moveQuoteToken(695230664226651211376892782958299806602599384639648126900062519785408512, 1000115588871659705, 22812, 1955101796782211288928817909562); - _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639932, 103); + _basicERC20PoolHandler.pledgeCollateral(47134563260349377955683144555119028889734284095914219439962386869, 2323610696462098, 0); + _basicERC20PoolHandler.repayDebt(1, 2, 0); + _basicERC20PoolHandler.removeCollateral(200953640940463935290718680397023889633667961549, 2481, 3, 0); + _basicERC20PoolHandler.moveQuoteToken(695230664226651211376892782958299806602599384639648126900062519785408512, 1000115588871659705, 22812, 1955101796782211288928817909562, 0); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639932, 103, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_fenwick_deposit_8() external { - _basicERC20PoolHandler.drawDebt(226719918559509764892175185709, 228676957600917178383525685311331); + _basicERC20PoolHandler.drawDebt(226719918559509764892175185709, 228676957600917178383525685311331, 0); invariant_fenwick_depositAtIndex_F1(); } @@ -265,34 +260,34 @@ contract RegressionTestBasicERC20Pool is BasicERC20PoolInvariants { vm.setEnv("NO_OF_BUCKETS", "10"); super.setUp(); - _basicERC20PoolHandler.transferLps(19078983173372942890417748018722377435183373748499322243247546781962442185, 1, 4391160926701505967325397265181132015972183318, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _basicERC20PoolHandler.transferLps(270997981512080867078682324706934707221242205867293069, 7864, 3651, 3166); - _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3635889843872116931397407365290249, 23021664368573277020436789355588670855277006870); - _basicERC20PoolHandler.drawDebt(268, 181582671641966396883195899256); - _basicERC20PoolHandler.pullCollateral(1227515749685864358137095510127654245525351748189001609, 3818828157139154512); - _basicERC20PoolHandler.repayDebt(3042268540744255610589705434124741203255613373849507141, 0); - _basicERC20PoolHandler.pullCollateral(6802, 6279); - _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 127935328935713960735227335223838560292175); - _basicERC20PoolHandler.pullCollateral(2930, 36104017659498278503100244564470932293); - _basicERC20PoolHandler.addQuoteToken(1681414255953633817920736095340458401, 2, 331564777626112378272493610241099454882166422929878794700); - _basicERC20PoolHandler.addCollateral(1834, 38146939, 39424949171633211915315804); - _basicERC20PoolHandler.addQuoteToken(8640216505061661298, 13070, 10696); - _basicERC20PoolHandler.pullCollateral(962704044, 8898752); - _basicERC20PoolHandler.moveQuoteToken(34331296, 236168072844073492133156025194, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 19256400511825415508859240250358); - _basicERC20PoolHandler.transferLps(2, 1183061996845258949, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2); - _basicERC20PoolHandler.drawDebt(2169832191680919598423992113933409675947, 459159100237615494082512466719091280519979494228); - _basicERC20PoolHandler.pledgeCollateral(363165343283932793766391798512, 14747); - _basicERC20PoolHandler.removeCollateral(3985, 1824, 6960); - _basicERC20PoolHandler.addCollateral(1650938994639952252010012965502, 7253, 7574); - _basicERC20PoolHandler.transferLps(190195262656118760688724739, 999999999999999999999999999999999999999997115, 1248306590932896398273554030427, 6130); - _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 8113004849018889317737081296151463737388429); - _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _basicERC20PoolHandler.moveQuoteToken(3, 2, 10960996744849996375050327179144415056797335841576787, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _basicERC20PoolHandler.transferLps(1, 743324445647492969999445842202717517856825, 1214649867011363567867599949068071550706, 239704513237); - _basicERC20PoolHandler.moveQuoteToken(78781837290735535753552291770891423860043710162602546000110480894858317836924, 993620562130177991745562150, 50000000000000000, 7110950); - _basicERC20PoolHandler.removeQuoteToken(3201781718151524032696177608091, 2788940717158963266260644637275, 1001719209787209063137009778273); - _basicERC20PoolHandler.removeCollateral(1481112300711317586348171215820373858161462484006514, 168870288186037165317237437722657252078900747583218061139236575915, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _basicERC20PoolHandler.transferLps(19078983173372942890417748018722377435183373748499322243247546781962442185, 1, 4391160926701505967325397265181132015972183318, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _basicERC20PoolHandler.transferLps(270997981512080867078682324706934707221242205867293069, 7864, 3651, 3166, 0); + _basicERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3635889843872116931397407365290249, 23021664368573277020436789355588670855277006870, 0); + _basicERC20PoolHandler.drawDebt(268, 181582671641966396883195899256, 0); + _basicERC20PoolHandler.pullCollateral(1227515749685864358137095510127654245525351748189001609, 3818828157139154512, 0); + _basicERC20PoolHandler.repayDebt(3042268540744255610589705434124741203255613373849507141, 0, 0); + _basicERC20PoolHandler.pullCollateral(6802, 6279, 0); + _basicERC20PoolHandler.addQuoteToken(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 127935328935713960735227335223838560292175, 0); + _basicERC20PoolHandler.pullCollateral(2930, 36104017659498278503100244564470932293, 0); + _basicERC20PoolHandler.addQuoteToken(1681414255953633817920736095340458401, 2, 331564777626112378272493610241099454882166422929878794700, 0); + _basicERC20PoolHandler.addCollateral(1834, 38146939, 39424949171633211915315804, 0); + _basicERC20PoolHandler.addQuoteToken(8640216505061661298, 13070, 10696, 0); + _basicERC20PoolHandler.pullCollateral(962704044, 8898752, 0); + _basicERC20PoolHandler.moveQuoteToken(34331296, 236168072844073492133156025194, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 19256400511825415508859240250358, 0); + _basicERC20PoolHandler.transferLps(2, 1183061996845258949, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 0 ); + _basicERC20PoolHandler.drawDebt(2169832191680919598423992113933409675947, 459159100237615494082512466719091280519979494228, 0 ); + _basicERC20PoolHandler.pledgeCollateral(363165343283932793766391798512, 14747, 0 ); + _basicERC20PoolHandler.removeCollateral(3985, 1824, 6960, 0); + _basicERC20PoolHandler.addCollateral(1650938994639952252010012965502, 7253, 7574, 0); + _basicERC20PoolHandler.transferLps(190195262656118760688724739, 999999999999999999999999999999999999999997115, 1248306590932896398273554030427, 6130, 0); + _basicERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 8113004849018889317737081296151463737388429, 0); + _basicERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0 ); + _basicERC20PoolHandler.moveQuoteToken(3, 2, 10960996744849996375050327179144415056797335841576787, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _basicERC20PoolHandler.transferLps(1, 743324445647492969999445842202717517856825, 1214649867011363567867599949068071550706, 239704513237, 0); + _basicERC20PoolHandler.moveQuoteToken(78781837290735535753552291770891423860043710162602546000110480894858317836924, 993620562130177991745562150, 50000000000000000, 7110950, 0); + _basicERC20PoolHandler.removeQuoteToken(3201781718151524032696177608091, 2788940717158963266260644637275, 1001719209787209063137009778273, 0); + _basicERC20PoolHandler.removeCollateral(1481112300711317586348171215820373858161462484006514, 168870288186037165317237437722657252078900747583218061139236575915, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); invariant_collateralBalance_CT1_CT7(); } diff --git a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol index 3218dc647..7637d31be 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -11,54 +11,54 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { } function test_regression_quote_token() external { - _liquidationERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_arithmetic_overflow() external { - _liquidationERC20PoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903); - _liquidationERC20PoolHandler.addQuoteToken(3487, 871, 1654); + _liquidationERC20PoolHandler.kickAuction(128942392769655840156268259377571235707684499808935108685525899532745, 9654010200996517229486923829624352823010316518405842367464881, 135622574118732106350824249104903, 0); + _liquidationERC20PoolHandler.addQuoteToken(3487, 871, 1654, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_bucket_take_lps() external { - _liquidationERC20PoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.addQuoteToken(1, 20033186019073, 1); - _liquidationERC20PoolHandler.bucketTake(0, 0, false, 2876997751); + _liquidationERC20PoolHandler.removeQuoteToken(7033457611004217223271238592369692530886316746601644, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.addQuoteToken(1, 20033186019073, 1, 0); + _liquidationERC20PoolHandler.bucketTake(0, 0, false, 2876997751, 0); - invariant_Lps_B1_B4(); + invariant_Lps_B1(); } function test_regression_interest_rate() external { - _liquidationERC20PoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769); + _liquidationERC20PoolHandler.bucketTake(18065045387666484532028539614323078235438354477798625297386607289, 14629545458306, true, 1738460279262663206365845078188769, 0); invariant_interest_rate_I1(); } function test_regression_incorrect_no_of_borrowers() external { - _liquidationERC20PoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513); - _liquidationERC20PoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271); - _liquidationERC20PoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294); - _liquidationERC20PoolHandler.moveQuoteToken(47205392335275917691737183012282140599753693978176314740917, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 164043848691337333691028718232); - _liquidationERC20PoolHandler.kickAuction(184206711567329609153924955630229148705869686378631519380021040314, 78351, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC20PoolHandler.kickAuction(3, 199726916764352560035199423206927461876998880387108455962754538835220966553, 3); - _liquidationERC20PoolHandler.removeQuoteToken(999999991828440064944955196599190431639924811, 2781559202773230142346489450532860130, 3000000005240421579956496007310960085855569344); - _liquidationERC20PoolHandler.pullCollateral(48768502867710912107594904694036421700, 275047566877984818806178837359260100); - _liquidationERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, false, 8154570107391684241724530527782571978369827827856399749867491880); - _liquidationERC20PoolHandler.removeCollateral(43733538637150108518954934566131291302796656384802361118757432084573, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.addQuoteToken(1, 2, 2); - _liquidationERC20PoolHandler.repayDebt(647805461526201272, 0); - _liquidationERC20PoolHandler.kickAuction(1019259585194528028904148545812353964867041444572537077023497678982801, 58796345025472936970320, 131319002678489819637546489086162345032717166507611595521); - _liquidationERC20PoolHandler.moveQuoteToken(2, 2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationERC20PoolHandler.moveQuoteToken(6164937621056362865643346803975636714, 4, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 315548939052682258); - _liquidationERC20PoolHandler.repayDebt(2987067394366841692658, 170206016570563384086766968869520628); - _liquidationERC20PoolHandler.pledgeCollateral(3558446182295495994762049031, 0); - _liquidationERC20PoolHandler.drawDebt(4525700839008283200312069904720925039, 3000000000753374912785563581177665475703155339); - _liquidationERC20PoolHandler.kickAuction(1, 3559779948348618822016735773117619950447774, 218801416747720); - _liquidationERC20PoolHandler.addQuoteToken(1469716416900282992357252011629715552, 13037214114647887147246343731476169800, 984665637618013480616943810604306792); - _liquidationERC20PoolHandler.pullCollateral(438961419917818200942534689247815826455600131, 64633474453314038763068322072915580384442279897841981); + _liquidationERC20PoolHandler.moveQuoteToken(18178450611611937161732340858718395124120481640398450530303803, 0, 93537843531612826457318744802930982491, 15596313608676556633725998020226886686244513, 0); + _liquidationERC20PoolHandler.addCollateral(2208149704044082902772911545020934265, 340235628931125711729099234105522626267587665393753030264689924088, 2997844437211835697043096396926932785920355866486893005710984415271, 0); + _liquidationERC20PoolHandler.moveQuoteToken(56944009718062971164908977784993293, 737882204379007468599822110965749781465, 1488100463155679769353095066686506252, 11960033727528802202227468733333727294, 0); + _liquidationERC20PoolHandler.moveQuoteToken(47205392335275917691737183012282140599753693978176314740917, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 164043848691337333691028718232, 0); + _liquidationERC20PoolHandler.kickAuction(184206711567329609153924955630229148705869686378631519380021040314, 78351, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.kickAuction(3, 199726916764352560035199423206927461876998880387108455962754538835220966553, 3, 0); + _liquidationERC20PoolHandler.removeQuoteToken(999999991828440064944955196599190431639924811, 2781559202773230142346489450532860130, 3000000005240421579956496007310960085855569344, 0); + _liquidationERC20PoolHandler.pullCollateral(48768502867710912107594904694036421700, 275047566877984818806178837359260100, 0); + _liquidationERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, false, 8154570107391684241724530527782571978369827827856399749867491880, 0); + _liquidationERC20PoolHandler.removeCollateral(43733538637150108518954934566131291302796656384802361118757432084573, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.addQuoteToken(1, 2, 2, 0); + _liquidationERC20PoolHandler.repayDebt(647805461526201272, 0, 0); + _liquidationERC20PoolHandler.kickAuction(1019259585194528028904148545812353964867041444572537077023497678982801, 58796345025472936970320, 131319002678489819637546489086162345032717166507611595521, 0); + _liquidationERC20PoolHandler.moveQuoteToken(2, 2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _liquidationERC20PoolHandler.moveQuoteToken(6164937621056362865643346803975636714, 4, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 315548939052682258, 0); + _liquidationERC20PoolHandler.repayDebt(2987067394366841692658, 170206016570563384086766968869520628, 0); + _liquidationERC20PoolHandler.pledgeCollateral(3558446182295495994762049031, 0, 0); + _liquidationERC20PoolHandler.drawDebt(4525700839008283200312069904720925039, 3000000000753374912785563581177665475703155339, 0); + _liquidationERC20PoolHandler.kickAuction(1, 3559779948348618822016735773117619950447774, 218801416747720, 0); + _liquidationERC20PoolHandler.addQuoteToken(1469716416900282992357252011629715552, 13037214114647887147246343731476169800, 984665637618013480616943810604306792, 0); + _liquidationERC20PoolHandler.pullCollateral(438961419917818200942534689247815826455600131, 64633474453314038763068322072915580384442279897841981, 0); invariant_auctions_A3_A4(); } @@ -66,218 +66,218 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { // test was failing due to deposit time update even if kicker lp reward is 0. // resolved with PR: https://github.com/ajna-finance/contracts/pull/674 function test_regression_bucket_deposit_time() external { - _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 2079356830967144967054363629631641573895835179323954988585146991431, 233005625580787863707944); - _liquidationERC20PoolHandler.bucketTake(21616, 1047473235778002354, false, 1062098588952039043823357); - _liquidationERC20PoolHandler.bucketTake(1673497622984405133414814181152, 94526073941076989987362055170246, false, 1462); + _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 2079356830967144967054363629631641573895835179323954988585146991431, 233005625580787863707944, 0); + _liquidationERC20PoolHandler.bucketTake(21616, 1047473235778002354, false, 1062098588952039043823357, 0); + _liquidationERC20PoolHandler.bucketTake(1673497622984405133414814181152, 94526073941076989987362055170246, false, 1462, 0); invariant_Bucket_deposit_time_B5_B6_B7(); } function test_regression_transfer_taker_lps_bucket_deposit_time() external { - _liquidationERC20PoolHandler.settleAuction(3637866246331061119113494215, 0, 6163485280468362485998190762304829820899757798629605592174295845105660515); - _liquidationERC20PoolHandler.transferLps(1610, 1000000000018496758270674070884, 168395863093969200027183125335, 2799494920515362640996160058); - _liquidationERC20PoolHandler.bucketTake(0, 10619296457595008969473693936299982020664977642271808785891719078511288, true, 1681500683437506364426133778273769573223975355182845498494263153646356302); + _liquidationERC20PoolHandler.settleAuction(3637866246331061119113494215, 0, 6163485280468362485998190762304829820899757798629605592174295845105660515, 0); + _liquidationERC20PoolHandler.transferLps(1610, 1000000000018496758270674070884, 168395863093969200027183125335, 2799494920515362640996160058, 0); + _liquidationERC20PoolHandler.bucketTake(0, 10619296457595008969473693936299982020664977642271808785891719078511288, true, 1681500683437506364426133778273769573223975355182845498494263153646356302, 0); invariant_Bucket_deposit_time_B5_B6_B7(); } function test_regression_invariant_fenwick_depositAtIndex_F1() external { - _liquidationERC20PoolHandler.moveQuoteToken(4058, 2725046678043704335543997294802562, 16226066, 4284); + _liquidationERC20PoolHandler.moveQuoteToken(4058, 2725046678043704335543997294802562, 16226066, 4284, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_depositKick() external { - _liquidationERC20PoolHandler.repayDebt(13418, 1160); - _liquidationERC20PoolHandler.kickWithDeposit(143703836638834364678, 470133688850921941603); + _liquidationERC20PoolHandler.repayDebt(13418, 1160, 0); + _liquidationERC20PoolHandler.kickWithDeposit(143703836638834364678, 470133688850921941603, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_incorrect_take_2() external { - _liquidationERC20PoolHandler.kickAuction(13452, 7198, 11328); - _liquidationERC20PoolHandler.takeAuction(6772, 18720, 6668); - _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1666258487708695528254610529989951, 490873240291829575083322665078478117042861655783753); + _liquidationERC20PoolHandler.kickAuction(13452, 7198, 11328, 0); + _liquidationERC20PoolHandler.takeAuction(6772, 18720, 6668, 0); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1666258487708695528254610529989951, 490873240291829575083322665078478117042861655783753, 0); invariant_auction_taken_A6(); } function test_regression_invariant_exchange_rate_bucket_take_1() external { - _liquidationERC20PoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671); - _liquidationERC20PoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825); - _liquidationERC20PoolHandler.bucketTake(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.bucketTake(183325863789657771277097526117552930424549597961930161, 34356261125910963886574176318851973698031483479551872234291832833800, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.settleAuction(52219427432114632, 2227306986719506048214107429, 154672727048162052261854237547755782166311596848556350861587480089015671, 0); + _liquidationERC20PoolHandler.removeQuoteToken(1999999999999999943017433781133248199223345020, 9070, 3519433319314336634208412746825, 0); + _liquidationERC20PoolHandler.bucketTake(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_invariant_exchange_rate_bucket_take_2() external { - _liquidationERC20PoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101); - _liquidationERC20PoolHandler.settleAuction(186120755740, 2, 59199623628501455128); - _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344); - _liquidationERC20PoolHandler.bucketTake(2, 259574184, true, 248534890472324170412180243783490514876275); + _liquidationERC20PoolHandler.moveQuoteToken(1676213736466301051643762607860, 1344, 2018879446031241805536743752775, 4101, 0); + _liquidationERC20PoolHandler.settleAuction(186120755740, 2, 59199623628501455128, 0); + _liquidationERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 29888344, 0); + _liquidationERC20PoolHandler.bucketTake(2, 259574184, true, 248534890472324170412180243783490514876275, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_quote_token_2() external { - _liquidationERC20PoolHandler.kickAuction(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC20PoolHandler.kickAuction(416882035302092397436677640325827, 7379, 253058086367250264569525665396366); - _liquidationERC20PoolHandler.kickAuction(95740057146806695735694068330212313517380414204596464841344800376300745, 15462030827034, 17811087070659573835739283446817); - _liquidationERC20PoolHandler.drawDebt(91685640224888183606335500279, 3284161781338443742266950748717011); - _liquidationERC20PoolHandler.settleAuction(366366807138151363686, 2, 39227118695514892784493088788799944161631371060); + _liquidationERC20PoolHandler.kickAuction(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.kickAuction(416882035302092397436677640325827, 7379, 253058086367250264569525665396366, 0); + _liquidationERC20PoolHandler.kickAuction(95740057146806695735694068330212313517380414204596464841344800376300745, 15462030827034, 17811087070659573835739283446817, 0); + _liquidationERC20PoolHandler.drawDebt(91685640224888183606335500279, 3284161781338443742266950748717011, 0); + _liquidationERC20PoolHandler.settleAuction(366366807138151363686, 2, 39227118695514892784493088788799944161631371060, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_invariant_settle_F1_1() external { - _liquidationERC20PoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980); - _liquidationERC20PoolHandler.kickAuction(5135, 1752, 6350); - _liquidationERC20PoolHandler.kickAuction(142699, 4496, 4356); - _liquidationERC20PoolHandler.moveQuoteToken(1173, 1445, 792325212, 447); - _liquidationERC20PoolHandler.settleAuction(18308, 3145, 947); + _liquidationERC20PoolHandler.moveQuoteToken(950842133422927133350903963095785051820046356616, 12698007000117331615195178867, 28462469898, 3434419004419233872687259780980, 0); + _liquidationERC20PoolHandler.kickAuction(5135, 1752, 6350, 0); + _liquidationERC20PoolHandler.kickAuction(142699, 4496, 4356, 0); + _liquidationERC20PoolHandler.moveQuoteToken(1173, 1445, 792325212, 447, 0); + _liquidationERC20PoolHandler.settleAuction(18308, 3145, 947, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_settle_F1_2() external { - _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); - _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); - _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031, 0); + _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486, 0); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370, 0); + _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_settle_F1_3() external { - _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); - _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240, 0); + _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705, 0); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_settle_F2_1() external { - _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031); - _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486); - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370); - _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1); + _liquidationERC20PoolHandler.kickAuction(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.takeAuction(166780275301665520376512760721506, 1999999999999999999999999999999999999999997110, 2558901617183837697153566056202031, 0); + _liquidationERC20PoolHandler.settleAuction(33663580470110889117800273608260215520117498607286850968631643620668, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 376647916322842326327814305437229315203341777076993910570400198695301486, 0); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 25553353095446, 4576944944764318279058650381557372220045541635899392217977105401448189236370, 0); + _liquidationERC20PoolHandler.settleAuction(1124188319925967896480196098633929774470471695473649161072280, 2, 1, 0); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_invariant_settle_F2_2() external { - _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240); - _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705); - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689); + _liquidationERC20PoolHandler.kickAuction(0, 3945558181153878030177, 4183257860938847260218679701589682740098170267658022767240, 0); + _liquidationERC20PoolHandler.drawDebt(4462122177274869820804814924250, 18446744073709551705, 0); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, 80620507131699866090869932155783811264689, 0); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_invariant_settle_F1_4() external { - _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); - _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); - _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); - _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); - _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); - _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183, 0); + _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987, 0); + _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190, 0); + _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382, 0); + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319, 0); + _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_settle_F2_3() external { - _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183); - _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987); - _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190); - _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382); - _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319); - _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.transferLps(1746372434893174899659975954487250106508989011, 2872040610940802546486007303, 3744, 12183, 0); + _liquidationERC20PoolHandler.takeAuction(1901516289100290457836604652380130002299311381, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 5028305687421043987719245987, 0); + _liquidationERC20PoolHandler.removeQuoteToken(20368511603587868045081284330731, 489921429793913961108335952, 2190, 0); + _liquidationERC20PoolHandler.settleAuction(9999999993177259514653978780, 2827825980613220278546740955, 31863690252499070408500382, 0); + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 19234747283271867319, 0); + _liquidationERC20PoolHandler.kickAuction(309236557489990485667503759172591, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_invariant_F3_1() external { - _liquidationERC20PoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3731592205777443374190, 2); - _liquidationERC20PoolHandler.takeAuction(3554599780774102176805971372130467746, 140835031537485528703906318530162192, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC20PoolHandler.repayDebt(2692074105646752292572533908391, 1968526964305399089154844418825); - _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 4553829); - _liquidationERC20PoolHandler.bucketTake(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 0); - _liquidationERC20PoolHandler.drawDebt(626971501456142588551128155365, 816763288150043968438676); - _liquidationERC20PoolHandler.pullCollateral(381299861468989210101433912, 999999999999997998400442008957368645662570165); + _liquidationERC20PoolHandler.bucketTake(2935665707632064617811462067363503938617565993411989637, 3, false, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.moveQuoteToken(13019605457845697172279618365097597238993925, 1, 3994854914, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _liquidationERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3731592205777443374190, 2, 0); + _liquidationERC20PoolHandler.takeAuction(3554599780774102176805971372130467746, 140835031537485528703906318530162192, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.repayDebt(2692074105646752292572533908391, 1968526964305399089154844418825, 0); + _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 4553829, 0); + _liquidationERC20PoolHandler.bucketTake(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 0, 0); + _liquidationERC20PoolHandler.drawDebt(626971501456142588551128155365, 816763288150043968438676, 0); + _liquidationERC20PoolHandler.pullCollateral(381299861468989210101433912, 999999999999997998400442008957368645662570165, 0); invariant_fenwick_bucket_index_F3(); } function test_regression_invariant_F3_2() external { - _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); - _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); - _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); - _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344, 0); + _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256, 0); + _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917, 0); + _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); invariant_fenwick_bucket_index_F3(); } function test_regression_invariant_F4_1() external { - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 127546297848367334892478587751, 723921922395815633171615243621131242188407029895233162931857565302); - _liquidationERC20PoolHandler.removeQuoteToken(2, 2, 7361820555); - _liquidationERC20PoolHandler.takeAuction(85885591922376805486065427318859822458293427950603, 8526258315228761831408142393759013524255378290706574861831877477, 1267004887455971938409309909682740381503049590444968840223); - _liquidationERC20PoolHandler.drawDebt(663777721413606329209923101072, 946300054291644291801213511570); - _liquidationERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2); - _liquidationERC20PoolHandler.addQuoteToken(9360900796482582322800, 694431436637841996793959397509, 553923154643858021986449189292); - _liquidationERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 34469655866078951331675076928366708920312931751567797); - _liquidationERC20PoolHandler.bucketTake(0, 1, false, 3); - _liquidationERC20PoolHandler.bucketTake(1190209291225920034207711400729307351194726, 2492241351445208059551299524117408972943752042954, false, 3385052658235853990473420226123930971); - _liquidationERC20PoolHandler.settleAuction(2693191148227658159823862814074, 44032195641927234172430384447, 2992758194960713897487381207167); - _liquidationERC20PoolHandler.removeQuoteToken(3, 34308174710409047450205135565, 2); - _liquidationERC20PoolHandler.takeAuction(235062105582030911119033338, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 127546297848367334892478587751, 723921922395815633171615243621131242188407029895233162931857565302, 0); + _liquidationERC20PoolHandler.removeQuoteToken(2, 2, 7361820555, 0); + _liquidationERC20PoolHandler.takeAuction(85885591922376805486065427318859822458293427950603, 8526258315228761831408142393759013524255378290706574861831877477, 1267004887455971938409309909682740381503049590444968840223, 0); + _liquidationERC20PoolHandler.drawDebt(663777721413606329209923101072, 946300054291644291801213511570, 0); + _liquidationERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 0); + _liquidationERC20PoolHandler.addQuoteToken(9360900796482582322800, 694431436637841996793959397509, 553923154643858021986449189292, 0); + _liquidationERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 34469655866078951331675076928366708920312931751567797, 0); + _liquidationERC20PoolHandler.bucketTake(0, 1, false, 3, 0); + _liquidationERC20PoolHandler.bucketTake(1190209291225920034207711400729307351194726, 2492241351445208059551299524117408972943752042954, false, 3385052658235853990473420226123930971, 0); + _liquidationERC20PoolHandler.settleAuction(2693191148227658159823862814074, 44032195641927234172430384447, 2992758194960713897487381207167, 0); + _liquidationERC20PoolHandler.removeQuoteToken(3, 34308174710409047450205135565, 2, 0); + _liquidationERC20PoolHandler.takeAuction(235062105582030911119033338, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_invariant_F4_2() external { - _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344); - _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256); - _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917); - _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _liquidationERC20PoolHandler.moveQuoteToken(15218560385591477289472131001881316985183680418957988997639810360709, 3836, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.kickAuction(1999999999999999999998790777810985454371631707, 730, 1154341805189495974830690344, 0); + _liquidationERC20PoolHandler.repayDebt(1000015272050180687, 58527020436006764365179004256, 0); + _liquidationERC20PoolHandler.transferLps(5732870987391656458983245, 12598011738672933544107229257061, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 144447650651692188788340246700695325628363284377395442919761780917, 0); + _liquidationERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3019024412741293564051936001315350655350, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_invariant_F4_3() external { - _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 88); - _liquidationERC20PoolHandler.kickWithDeposit(454046303796091226235, 1); - _liquidationERC20PoolHandler.addQuoteToken(22366532024867500041595597535594488494092956872779970834638, 2056702511, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.takeAuction(7409458575819003489055485098, 19999999999999999999998047232, 160427188541373972791114); - _liquidationERC20PoolHandler.drawDebt(54, 1078707919809097500728008); - _liquidationERC20PoolHandler.takeAuction(2, 11014481, 0); - _liquidationERC20PoolHandler.kickWithDeposit(6261145081390052923416, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC20PoolHandler.repayDebt(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC20PoolHandler.repayDebt(19522111312004366551699434321235702562902449, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.removeQuoteToken(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.kickAuction(1, 2109173590696846176713716365608775182694735853511202473079, 1); - _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 88, 0); + _liquidationERC20PoolHandler.kickWithDeposit(454046303796091226235, 1, 0); + _liquidationERC20PoolHandler.addQuoteToken(22366532024867500041595597535594488494092956872779970834638, 2056702511, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.takeAuction(7409458575819003489055485098, 19999999999999999999998047232, 160427188541373972791114, 0); + _liquidationERC20PoolHandler.drawDebt(54, 1078707919809097500728008, 0); + _liquidationERC20PoolHandler.takeAuction(2, 11014481, 0, 0); + _liquidationERC20PoolHandler.kickWithDeposit(6261145081390052923416, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.repayDebt(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC20PoolHandler.repayDebt(19522111312004366551699434321235702562902449, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.removeQuoteToken(2, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.kickAuction(1, 2109173590696846176713716365608775182694735853511202473079, 1, 0); + _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_invariant_F4_4() external { - _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC20PoolHandler.kickAuction(2, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_invariant_bucketlps_B2_B3() external { - _liquidationERC20PoolHandler.takeAuction(267050932349, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3887647445238399127687813856507958874); - _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 103646259621272362812910538669334394369354710213939195837836110291707517186914, 22729901925249217583); - _liquidationERC20PoolHandler.takeAuction(100662313874952447676789537887446294, 36755077739534085766246321257993, 20000000001077187985112900413); - _liquidationERC20PoolHandler.settleAuction(999999999999999970610520171679024221920138860, 4339, 19021013243589608614756959415948670046791); - _liquidationERC20PoolHandler.removeQuoteToken(7393406237507791712904627, 1097992169037390343, 30); + _liquidationERC20PoolHandler.takeAuction(267050932349, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3887647445238399127687813856507958874, 0); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 103646259621272362812910538669334394369354710213939195837836110291707517186914, 22729901925249217583, 0); + _liquidationERC20PoolHandler.takeAuction(100662313874952447676789537887446294, 36755077739534085766246321257993, 20000000001077187985112900413, 0); + _liquidationERC20PoolHandler.settleAuction(999999999999999970610520171679024221920138860, 4339, 19021013243589608614756959415948670046791, 0); + _liquidationERC20PoolHandler.removeQuoteToken(7393406237507791712904627, 1097992169037390343, 30, 0); invariant_Buckets_B2_B3(); } @@ -287,41 +287,41 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { Fixed by making changes in increaseInReserve calculation in case of kicker penalty in 'bucketTake' handler */ function test_regression_evm_revert_1() external { - _liquidationERC20PoolHandler.settleAuction(91509897037395202876797812344977844707030753189520454312427981040645023300162, 2439649222, 11529); - _liquidationERC20PoolHandler.bucketTake(6611, 46752666614331262781920, false, 2023645493297626462000000); + _liquidationERC20PoolHandler.settleAuction(91509897037395202876797812344977844707030753189520454312427981040645023300162, 2439649222, 11529, 0); + _liquidationERC20PoolHandler.bucketTake(6611, 46752666614331262781920, false, 2023645493297626462000000, 0); } // Test reverting with overflow error in dwatp calculation in _meaningfulDeposit in updateInterestState function _test_regression_evm_revert_2() external { - _liquidationERC20PoolHandler.drawDebt(13141077791835967310451371165744721774, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC20PoolHandler.kickAuction(3, 53758605435723729358784, 3); - _liquidationERC20PoolHandler.repayDebt(668608315443216098571064749198163965820, 18932325376258851353179065817321260901); - _liquidationERC20PoolHandler.pullCollateral(1660943750216, 7613674427701330576720241); - _liquidationERC20PoolHandler.removeQuoteToken(1900819281467749758886813834006, 976636367449728350520609392573, 4111426995539375716119348324981); - _liquidationERC20PoolHandler.bucketTake(1125834907286324, 3, false, 1); - _liquidationERC20PoolHandler.repayDebt(275298270790660321974310940, 2799); - _liquidationERC20PoolHandler.pullCollateral(0, 9824); - _liquidationERC20PoolHandler.takeAuction(8838, 14328, 1104); - _liquidationERC20PoolHandler.pullCollateral(96246301775147975236686390, 1999999999999999999999999999999999999999999994); - _liquidationERC20PoolHandler.drawDebt(1799754463155649601, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.kickAuction(226, 13555, 999999999999999999929986989979848322131950166); - _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 326707485783, 5838136568597409883953276821351359808349885898573251821); - _liquidationERC20PoolHandler.bucketTake(3690337820519065642096193886544, 3089359908022049919104337883638, false, 2000000000000000000000000000000000000000744145); - _liquidationERC20PoolHandler.withdrawBonds(117305399704678010034227969424174482909936628260540487, 3984313975337); - _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 26153396219420651226355270050749728507266848485707520383); - _liquidationERC20PoolHandler.removeCollateral(1999999999999997567145382180442515092966583434, 2757, 245342563594047897888988147512); - _liquidationERC20PoolHandler.moveQuoteToken(298628345, 6995466652341859760028193450571035, 212241589093381072912741572164, 1961348773154021480632696081492); - _liquidationERC20PoolHandler.moveQuoteToken(302104028381701071862379310831040316417001692505762, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 60362684677889414469182462544723956045205775540295406540387737, 2); - _liquidationERC20PoolHandler.bucketTake(2, 75288272353, true, 1); - _liquidationERC20PoolHandler.addCollateral(5005, 147331, 401909674630078222417654); - _liquidationERC20PoolHandler.transferLps(1029198447942867385425606035931054127523339423727036067, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); - _liquidationERC20PoolHandler.kickWithDeposit(293745346792645466008958291, 16403); - _liquidationERC20PoolHandler.withdrawBonds(2, 2); - _liquidationERC20PoolHandler.moveQuoteToken(32224, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6849415706965240690489344969517578351041775953402620986, 2148695314889429664161453614417855608352221); - _liquidationERC20PoolHandler.takeAuction(94448273410401913677340426062, 725811011514389123331573619988789182755239580450547667740684, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC20PoolHandler.kickAuction(741587127707329942048624377800, 636948139808655918956339428997, 1638145676855893651071922500909); - _liquidationERC20PoolHandler.removeCollateral(1, 5658503566441554287849, 301974090866276112427896384335355); + _liquidationERC20PoolHandler.drawDebt(13141077791835967310451371165744721774, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC20PoolHandler.kickAuction(3, 53758605435723729358784, 3, 0); + _liquidationERC20PoolHandler.repayDebt(668608315443216098571064749198163965820, 18932325376258851353179065817321260901, 0); + _liquidationERC20PoolHandler.pullCollateral(1660943750216, 7613674427701330576720241, 0); + _liquidationERC20PoolHandler.removeQuoteToken(1900819281467749758886813834006, 976636367449728350520609392573, 4111426995539375716119348324981, 0); + _liquidationERC20PoolHandler.bucketTake(1125834907286324, 3, false, 1, 0); + _liquidationERC20PoolHandler.repayDebt(275298270790660321974310940, 2799, 0); + _liquidationERC20PoolHandler.pullCollateral(0, 9824, 0); + _liquidationERC20PoolHandler.takeAuction(8838, 14328, 1104, 0); + _liquidationERC20PoolHandler.pullCollateral(96246301775147975236686390, 1999999999999999999999999999999999999999999994, 0); + _liquidationERC20PoolHandler.drawDebt(1799754463155649601, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.kickAuction(226, 13555, 999999999999999999929986989979848322131950166, 0); + _liquidationERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 326707485783, 5838136568597409883953276821351359808349885898573251821, 0); + _liquidationERC20PoolHandler.bucketTake(3690337820519065642096193886544, 3089359908022049919104337883638, false, 2000000000000000000000000000000000000000744145, 0); + _liquidationERC20PoolHandler.withdrawBonds(117305399704678010034227969424174482909936628260540487, 3984313975337, 0); + _liquidationERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 26153396219420651226355270050749728507266848485707520383, 0); + _liquidationERC20PoolHandler.removeCollateral(1999999999999997567145382180442515092966583434, 2757, 245342563594047897888988147512, 0); + _liquidationERC20PoolHandler.moveQuoteToken(298628345, 6995466652341859760028193450571035, 212241589093381072912741572164, 1961348773154021480632696081492, 0); + _liquidationERC20PoolHandler.moveQuoteToken(302104028381701071862379310831040316417001692505762, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 60362684677889414469182462544723956045205775540295406540387737, 2, 0); + _liquidationERC20PoolHandler.bucketTake(2, 75288272353, true, 1, 0); + _liquidationERC20PoolHandler.addCollateral(5005, 147331, 401909674630078222417654, 0); + _liquidationERC20PoolHandler.transferLps(1029198447942867385425606035931054127523339423727036067, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0, 0); + _liquidationERC20PoolHandler.kickWithDeposit(293745346792645466008958291, 16403, 0); + _liquidationERC20PoolHandler.withdrawBonds(2, 2, 0); + _liquidationERC20PoolHandler.moveQuoteToken(32224, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 6849415706965240690489344969517578351041775953402620986, 2148695314889429664161453614417855608352221, 0); + _liquidationERC20PoolHandler.takeAuction(94448273410401913677340426062, 725811011514389123331573619988789182755239580450547667740684, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC20PoolHandler.kickAuction(741587127707329942048624377800, 636948139808655918956339428997, 1638145676855893651071922500909, 0); + _liquidationERC20PoolHandler.removeCollateral(1, 5658503566441554287849, 301974090866276112427896384335355, 0); /* Logs for t0Debt2ToCollateral, dwatp calculation in `drawDebt(AmountToBorrow: 0 ,collateralPledged: 1)` Time skipped after previous t0Debt2ToCollateral update = 2 hours @@ -337,7 +337,7 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { t0Debt2ToCollateral_ = 731968601380048024642438741062243744889194172031621955814909 t0Debt_ = 1783948099616302823918411453377 */ - _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + _liquidationERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 0); } } \ No newline at end of file diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 4b2bb7898..73a2688e5 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -11,108 +11,108 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { } function test_regression_reserve_1() external { - _reserveERC20PoolHandler.kickAuction(3833, 15167, 15812); - _reserveERC20PoolHandler.removeQuoteToken(3841, 5339, 3672); + _reserveERC20PoolHandler.kickAuction(3833, 15167, 15812, 0); + _reserveERC20PoolHandler.removeQuoteToken(3841, 5339, 3672, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } // test was failing due to error in local fenwickAccureInterest method function test_regression_reserve_2() external { - _reserveERC20PoolHandler.bucketTake(19730, 10740, false, 15745); + _reserveERC20PoolHandler.bucketTake(19730, 10740, false, 15745, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - _reserveERC20PoolHandler.addCollateral(14982, 18415, 2079); + _reserveERC20PoolHandler.addCollateral(14982, 18415, 2079, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_3() external { - _reserveERC20PoolHandler.repayDebt(404759030515771436961484, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.repayDebt(404759030515771436961484, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); - _reserveERC20PoolHandler.removeQuoteToken(1, 48462143332689486187207611220503504, 3016379223696706064676286307759709760607418884028758142005949880337746); + _reserveERC20PoolHandler.removeQuoteToken(1, 48462143332689486187207611220503504, 3016379223696706064676286307759709760607418884028758142005949880337746, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_4() external { - _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_5() external { - _reserveERC20PoolHandler.addQuoteToken(16175599156223678030374425049208907710, 7790130564765920091364739351727, 3); - _reserveERC20PoolHandler.takeReserves(5189, 15843); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, false, 32141946615464); + _reserveERC20PoolHandler.addQuoteToken(16175599156223678030374425049208907710, 7790130564765920091364739351727, 3, 0); + _reserveERC20PoolHandler.takeReserves(5189, 15843, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, false, 32141946615464, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_6() external { - _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.removeQuoteToken(3, 76598848420614737624527356706527, 0); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.removeQuoteToken(3, 76598848420614737624527356706527, 0, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_7() external { - _reserveERC20PoolHandler.addQuoteToken(3457, 669447918254181815570046125126321316, 999999999837564549363536522206516458546098684); - _reserveERC20PoolHandler.takeReserves(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.takeAuction(1340780, 50855928079819281347583122859151761721081932621621575848930363902528865907253, 1955849966715168052511460257792969975295827229642304100359774335664); + _reserveERC20PoolHandler.addQuoteToken(3457, 669447918254181815570046125126321316, 999999999837564549363536522206516458546098684, 0); + _reserveERC20PoolHandler.takeReserves(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.takeAuction(1340780, 50855928079819281347583122859151761721081932621621575848930363902528865907253, 1955849966715168052511460257792969975295827229642304100359774335664, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_8() external { - _reserveERC20PoolHandler.addQuoteToken(0, 16517235514828622102184417372650002297563613398679232953, 3); - _reserveERC20PoolHandler.takeReserves(1, 824651); - _reserveERC20PoolHandler.kickAuction(353274873012743605831170677893, 0, 297442424590491337560428021161844134441441035247561757); + _reserveERC20PoolHandler.addQuoteToken(0, 16517235514828622102184417372650002297563613398679232953, 3, 0); + _reserveERC20PoolHandler.takeReserves(1, 824651, 0); + _reserveERC20PoolHandler.kickAuction(353274873012743605831170677893, 0, 297442424590491337560428021161844134441441035247561757, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_9() external { - _reserveERC20PoolHandler.addQuoteToken(8167, 13910, 6572); - _reserveERC20PoolHandler.removeQuoteToken(450224344766393467188006446127940623592343232978, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3); - _reserveERC20PoolHandler.addQuoteToken(1338758958425242459263005073411197235389119160018038412507867175716953081924, 0, 3); - _reserveERC20PoolHandler.removeQuoteToken(13684, 7152374202712184607581797, 37874588407625287908455929174); + _reserveERC20PoolHandler.addQuoteToken(8167, 13910, 6572, 0); + _reserveERC20PoolHandler.removeQuoteToken(450224344766393467188006446127940623592343232978, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3, 0); + _reserveERC20PoolHandler.addQuoteToken(1338758958425242459263005073411197235389119160018038412507867175716953081924, 0, 3, 0); + _reserveERC20PoolHandler.removeQuoteToken(13684, 7152374202712184607581797, 37874588407625287908455929174, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_10() external { - _reserveERC20PoolHandler.drawDebt(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.takeAuction(57952503477150200455919212210202824, 59396836510148646246120666527, 253313800651499290076173012431766464943796699909751081638812681630219); + _reserveERC20PoolHandler.drawDebt(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.takeAuction(57952503477150200455919212210202824, 59396836510148646246120666527, 253313800651499290076173012431766464943796699909751081638812681630219, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_11() external { - _reserveERC20PoolHandler.drawDebt(121976811044722028186086534321386307, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reserveERC20PoolHandler.removeQuoteToken(22099, 75368688232971077945057, 1089607217901154741924938851595); + _reserveERC20PoolHandler.drawDebt(121976811044722028186086534321386307, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _reserveERC20PoolHandler.removeQuoteToken(22099, 75368688232971077945057, 1089607217901154741924938851595, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_12() external { - _reserveERC20PoolHandler.drawDebt(7201, 13634); - _reserveERC20PoolHandler.kickReserveAuction(4584); + _reserveERC20PoolHandler.drawDebt(7201, 13634, 0); + _reserveERC20PoolHandler.kickReserveAuction(4584, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_13() external { - _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 540213858694280098848655811354140073005); - _reserveERC20PoolHandler.takeAuction(0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 16744276840254269931315148200783781329474); - _reserveERC20PoolHandler.settleAuction(1052055081946638635908683442568, 2, 3); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 540213858694280098848655811354140073005, 0); + _reserveERC20PoolHandler.takeAuction(0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 16744276840254269931315148200783781329474, 0); + _reserveERC20PoolHandler.settleAuction(1052055081946638635908683442568, 2, 3, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_14() external { - _reserveERC20PoolHandler.settleAuction(437841947740231831335707997666789355668988087441752683415964733126988332082, 147808166723925302409649247274, 115792089237316195423570985008687907853269984665640564039457584007913129639934); + _reserveERC20PoolHandler.settleAuction(437841947740231831335707997666789355668988087441752683415964733126988332082, 147808166723925302409649247274, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } @@ -122,177 +122,177 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { Fixed by separating kicker and taker condition in 'bucketTake' handler */ function test_regression_reserve_15() external { - _reserveERC20PoolHandler.settleAuction(412239579320, 197270, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.bucketTake(2751494394076294863634, 102579647081354295527475262786, false, 20636732314060673687564); + _reserveERC20PoolHandler.settleAuction(412239579320, 197270, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.bucketTake(2751494394076294863634, 102579647081354295527475262786, false, 20636732314060673687564, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_reserve_16() external { - _reserveERC20PoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991); - _reserveERC20PoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133); - _reserveERC20PoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231); - _reserveERC20PoolHandler.takeReserves(3052809529665022333893308239466671666604242469878272137069, 2); - _reserveERC20PoolHandler.settleAuction(56829802927206056542134152487104, 1, 16551256); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 4559892907266199616760, true, 92132592320410512639572628067656882480659844625060229234412683145); - _reserveERC20PoolHandler.addQuoteToken(26659, 27252796304289191617124780530313880584663397025838797405583704016009646047240, 8174069071114126926049883726727); - _reserveERC20PoolHandler.settleAuction(7416752279321695807446009676282848840713503167567654621163487831711306738, 42429259698839522507819580090756, 4353185348715295869540288672); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.takeReserves(7414584624540108578389380660398591567646816233407392320795021351932076518, 119186585263660671065239170291646549528129172578); - _reserveERC20PoolHandler.takeReserves(14604452466686952199052773378, 15308); - _reserveERC20PoolHandler.moveQuoteToken(2, 7113439765, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 101839127799233627783); - _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); - _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 175006273713916823228319530732179, 3); - _reserveERC20PoolHandler.kickAuction(999999999999999989948035804259829580593704779, 2999999999999999995605838724439103323477035837, 567178035339127142779327214); - _reserveERC20PoolHandler.kickWithDeposit(17028734043909648834002499445, 9578925065330517200577552073309); - _reserveERC20PoolHandler.addQuoteToken(6672165, 3776221923932077947607417775990788567, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.kickWithDeposit(24364934041550678417946191455, 52607039466540426076659653665991, 0); + _reserveERC20PoolHandler.moveQuoteToken(12701858085177571414571267592, 42692775850651681314985098497603, 999999999999999997089137720115121650200233243, 110756792431977317946585133, 0); + _reserveERC20PoolHandler.takeReserves(1000000005297961791, 4169814726576748738687746199368099036929520400874217254297794929654231, 0); + _reserveERC20PoolHandler.takeReserves(3052809529665022333893308239466671666604242469878272137069, 2, 0); + _reserveERC20PoolHandler.settleAuction(56829802927206056542134152487104, 1, 16551256, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 4559892907266199616760, true, 92132592320410512639572628067656882480659844625060229234412683145, 0); + _reserveERC20PoolHandler.addQuoteToken(26659, 27252796304289191617124780530313880584663397025838797405583704016009646047240, 8174069071114126926049883726727, 0); + _reserveERC20PoolHandler.settleAuction(7416752279321695807446009676282848840713503167567654621163487831711306738, 42429259698839522507819580090756, 4353185348715295869540288672, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.takeReserves(7414584624540108578389380660398591567646816233407392320795021351932076518, 119186585263660671065239170291646549528129172578, 0); + _reserveERC20PoolHandler.takeReserves(14604452466686952199052773378, 15308, 0); + _reserveERC20PoolHandler.moveQuoteToken(2, 7113439765, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 101839127799233627783, 0); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 0); + _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 175006273713916823228319530732179, 3, 0); + _reserveERC20PoolHandler.kickAuction(999999999999999989948035804259829580593704779, 2999999999999999995605838724439103323477035837, 567178035339127142779327214, 0); + _reserveERC20PoolHandler.kickWithDeposit(17028734043909648834002499445, 9578925065330517200577552073309, 0); + _reserveERC20PoolHandler.addQuoteToken(6672165, 3776221923932077947607417775990788567, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_fenwick_deposits_1() external { - _reserveERC20PoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617); + _reserveERC20PoolHandler.pledgeCollateral(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 22181751645253101881254616597347234807617, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_incorrect_zero_deposit_buckets_1() external { - _reserveERC20PoolHandler.addQuoteToken(26716, 792071517553389595371632366275, 1999999999999999449873579333598595527312558403); + _reserveERC20PoolHandler.addQuoteToken(26716, 792071517553389595371632366275, 1999999999999999449873579333598595527312558403, 0); invariant_fenwick_prefixSumIndex_F4(); - _reserveERC20PoolHandler.takeAuction(3383098792294835418337099631478603398072656037191240558595006969488860, 23280466048203500609787983860018797249195596837096487660362732305, 999999999999999999999999012359); + _reserveERC20PoolHandler.takeAuction(3383098792294835418337099631478603398072656037191240558595006969488860, 23280466048203500609787983860018797249195596837096487660362732305, 999999999999999999999999012359, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_incorrect_zero_deposit_buckets_2() external { - _reserveERC20PoolHandler.addCollateral(9093188371345232280759885514931620, 736370925, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.removeQuoteToken(2, 743823342719479363729966668312423206558602, 6003791801508574660825548152233943700089469549364090309); - _reserveERC20PoolHandler.removeQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); - _reserveERC20PoolHandler.addQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995); - _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.addQuoteToken(22558, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 26798251134); - _reserveERC20PoolHandler.moveQuoteToken(699684583201376669946795465695023954383, 871337618071093223322748209250657757655686665685488924893819949988, 6856667370119202181100844692321254723509125063768335, 2); + _reserveERC20PoolHandler.addCollateral(9093188371345232280759885514931620, 736370925, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.removeQuoteToken(2, 743823342719479363729966668312423206558602, 6003791801508574660825548152233943700089469549364090309, 0); + _reserveERC20PoolHandler.removeQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995, 0); + _reserveERC20PoolHandler.addQuoteToken(261467129238591107899210386032213509797152237956889, 1034, 48028560549472995, 0); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.addQuoteToken(22558, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 26798251134, 0); + _reserveERC20PoolHandler.moveQuoteToken(699684583201376669946795465695023954383, 871337618071093223322748209250657757655686665685488924893819949988, 6856667370119202181100844692321254723509125063768335, 2, 0); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_incorrect_bond() external { - _reserveERC20PoolHandler.settleAuction(18129, 6125, 756); + _reserveERC20PoolHandler.settleAuction(18129, 6125, 756, 0); invariant_bond_A2(); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_reserves_fenwick_depositAtIndex_F1() external { - _reserveERC20PoolHandler.kickAuction(14062, 13380, 20332); - _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 250713412144308447525906089113510093407014793436690623); - _reserveERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.kickAuction(14062, 13380, 20332, 0); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 250713412144308447525906089113510093407014793436690623, 0); + _reserveERC20PoolHandler.bucketTake(2, 115792089237316195423570985008687907853269984665640564039457584007913129639933, true, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_fenwick_depositAtIndex_F1(); } function test_regression_invariant_reserves_settle_1() external { - _reserveERC20PoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886); - _reserveERC20PoolHandler.kickReserveAuction(18407833277983020451007887294192863287187933); - _reserveERC20PoolHandler.settleAuction(2720, 3319, 516); + _reserveERC20PoolHandler.settleAuction(2999999999999999543503680529282898884169444286, 999999999999999999999999, 6952, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0, false, 228076556654255348886, 0); + _reserveERC20PoolHandler.kickReserveAuction(18407833277983020451007887294192863287187933, 0); + _reserveERC20PoolHandler.settleAuction(2720, 3319, 516, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_reserves_settle_2() external { - _reserveERC20PoolHandler.takeAuction(3, 214198155653990209702223102757081411626927025, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.repayDebt(36, 19087); - _reserveERC20PoolHandler.drawDebt(2550145944163683156825587547113715005197220288637184, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.takeAuction(3, 214198155653990209702223102757081411626927025, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.repayDebt(36, 19087, 0); + _reserveERC20PoolHandler.drawDebt(2550145944163683156825587547113715005197220288637184, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_1() external { - _reserveERC20PoolHandler.kickAuction(22771, 1111716442170237736883602263032, 7068); - _reserveERC20PoolHandler.addCollateral(450013003559446434159001584489461823249847174057443177111241841181931, 312804075096415570730723645176181753809227168111076176815108, 0); - _reserveERC20PoolHandler.pledgeCollateral(1985831902099838153679635097394320832859625435, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); - _reserveERC20PoolHandler.takeAuction(159178586166894, 2, 2); - _reserveERC20PoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653); - _reserveERC20PoolHandler.kickReserveAuction(2162); - _reserveERC20PoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524); - _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); + _reserveERC20PoolHandler.kickAuction(22771, 1111716442170237736883602263032, 7068, 0); + _reserveERC20PoolHandler.addCollateral(450013003559446434159001584489461823249847174057443177111241841181931, 312804075096415570730723645176181753809227168111076176815108, 0, 0); + _reserveERC20PoolHandler.pledgeCollateral(1985831902099838153679635097394320832859625435, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.transferLps(1, 11785568695658463091194696857966812287312218400594, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0, 0); + _reserveERC20PoolHandler.takeAuction(159178586166894, 2, 2, 0); + _reserveERC20PoolHandler.kickAuction(2, 2375789919282905103386504516485994899, 1289653, 0); + _reserveERC20PoolHandler.kickReserveAuction(2162, 0); + _reserveERC20PoolHandler.settleAuction(4612, 40708630701038224142448353799854069842509049093396550723073072047814079, 39027373949250548040512012762457247677933424051240699689883568078322057459524, 0); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_2() external { - _reserveERC20PoolHandler.drawDebt(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reserveERC20PoolHandler.pullCollateral(8213783947977569843117913236674123519747026, 26007879196259510050186964175498569516185804333067186877); - _reserveERC20PoolHandler.drawDebt(2301679051848045604, 2599238865); - _reserveERC20PoolHandler.addCollateral(4242066606167690018840733069974159, 2308657525655903223461843364795, 65478701235782653506998474972558); - _reserveERC20PoolHandler.kickAuction(69087967303211947138147234149237227681311399268590256122007, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 61509477439); - _reserveERC20PoolHandler.bucketTake(72107205250762587233492136850, 1244277915808615586782916545843, false, 39013151190969055659579687996); - _reserveERC20PoolHandler.transferLps(235427298074932216827475360756961, 2730975142229662626738653393718571, 1801094436838792863068211758488417, 879376648610435813515943108046); - _reserveERC20PoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208); - _reserveERC20PoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765); - _reserveERC20PoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383); - _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589); - _reserveERC20PoolHandler.drawDebt(3, 2); - _reserveERC20PoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855); - _reserveERC20PoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758); - _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reserveERC20PoolHandler.kickReserveAuction(0); - _reserveERC20PoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458); + _reserveERC20PoolHandler.drawDebt(1, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _reserveERC20PoolHandler.pullCollateral(8213783947977569843117913236674123519747026, 26007879196259510050186964175498569516185804333067186877, 0); + _reserveERC20PoolHandler.drawDebt(2301679051848045604, 2599238865, 0); + _reserveERC20PoolHandler.addCollateral(4242066606167690018840733069974159, 2308657525655903223461843364795, 65478701235782653506998474972558, 0); + _reserveERC20PoolHandler.kickAuction(69087967303211947138147234149237227681311399268590256122007, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 61509477439, 0); + _reserveERC20PoolHandler.bucketTake(72107205250762587233492136850, 1244277915808615586782916545843, false, 39013151190969055659579687996, 0); + _reserveERC20PoolHandler.transferLps(235427298074932216827475360756961, 2730975142229662626738653393718571, 1801094436838792863068211758488417, 879376648610435813515943108046, 0); + _reserveERC20PoolHandler.bucketTake(740590071845914415309602438961, 903524249678397461462482055179, false, 999387178588229710810342952208, 0); + _reserveERC20PoolHandler.settleAuction(1996, 648686406391068869253434465091, 1012371126513011680823527365765, 0); + _reserveERC20PoolHandler.kickAuction(2758621226294910077454620848, 1587186203667651966808515455274, 999999999999999766114657929326397241693634383, 0); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.addCollateral(860262795452324500467615408841617417042130132486395050948571309437624254, 88294053979131610681224002926017918012056109605052596771915843, 2509079085932223405093441153560904865353589, 0); + _reserveERC20PoolHandler.drawDebt(3, 2, 0); + _reserveERC20PoolHandler.bucketTake(1112272948946288199596319174059, 651469309530642638235774421, false, 2631651594321033821284801688396855, 0); + _reserveERC20PoolHandler.pullCollateral(1, 104099149887771887762252474591136544290691758, 0); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 3893316282729587584044696989905829964749218951828499823513945610388772348, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1079490131956486279124163833769398638737841713956621, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _reserveERC20PoolHandler.kickReserveAuction(0, 0); + _reserveERC20PoolHandler.settleAuction(1685708597792729438175883702650, 2952680495818774014078, 5097264761526793300787284458, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_3() external { - _reserveERC20PoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442); - _reserveERC20PoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189); - _reserveERC20PoolHandler.kickReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639); - _reserveERC20PoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804); - _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067); - _reserveERC20PoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2); - _reserveERC20PoolHandler.settleAuction(2475430586786710276861336070835, 2600907908657087816392951766665339, 618867463233346276220185869); - _reserveERC20PoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417); - _reserveERC20PoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615); - _reserveERC20PoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417); - _reserveERC20PoolHandler.kickReserveAuction(1); - _reserveERC20PoolHandler.bucketTake(3, 1, true, 2); - _reserveERC20PoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893); - _reserveERC20PoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reserveERC20PoolHandler.pullCollateral(2, 2); - _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 60110048782249025340, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _reserveERC20PoolHandler.addQuoteToken(2, 2, 306147942052277777154794038508061442, 0); + _reserveERC20PoolHandler.takeReserves(999999997592778230040335721194842507878613188, 617767166532412476599141189, 0); + _reserveERC20PoolHandler.kickReserveAuction(103210968180742388081044815736108888392928341723424194324988612249639, 0); + _reserveERC20PoolHandler.kickWithDeposit(571331675273077569870268525690, 3000000000000000153070529032047742375224439804, 0); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639935, 1, 2345974107770202992, 596944268880651135381308885897365469741047535828013376978854456255492067, 0); + _reserveERC20PoolHandler.kickAuction(249542131817080594576330466916380605939068941221926774088755, 1792443579171442237436215, 2, 0); + _reserveERC20PoolHandler.settleAuction(2475430586786710276861336070835, 2600907908657087816392951766665339, 618867463233346276220185869, 0); + _reserveERC20PoolHandler.bucketTake(288221154502730111886403777699180, 4013402100758707152779826705918182, false, 3000000000000000997154081605746206372402043417, 0); + _reserveERC20PoolHandler.addQuoteToken(9798212016992127202141315997364967680599055895, 3, 1072606682991056733959287049686598376179068454808322552897362615, 0); + _reserveERC20PoolHandler.pledgeCollateral(153445992298474361671974195535972272220394541157224893523804178985601, 53709221935782524388066885085801417, 0); + _reserveERC20PoolHandler.kickReserveAuction(1, 0); + _reserveERC20PoolHandler.bucketTake(3, 1, true, 2, 0); + _reserveERC20PoolHandler.settleAuction(2518428390102925899809538437634001, 351638851502181329392182678513150532940060325784767627878107695205, 3071611172974674710789364893, 0); + _reserveERC20PoolHandler.transferLps(28822226972612722036870301886639533933908463827921999334463168, 1, 314514798153750347019311, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _reserveERC20PoolHandler.pullCollateral(2, 2, 0); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 60110048782249025340, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_4() external { - _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.repayDebt(123785744463475277851, 431477); - _reserveERC20PoolHandler.transferLps(8349868629210939854344368826901611192, 2050523511941068426657597285533, 482178822629563486190079445656644, 113294184847064316812952522804); - _reserveERC20PoolHandler.kickWithDeposit(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1); - _reserveERC20PoolHandler.settleAuction(2, 60232917818899277216367937385395389606, 109871490879953029603376159938904259489696033217506136); - _reserveERC20PoolHandler.repayDebt(11000946587948121111587595267746251370302202324589596297423219199459160, 1640564753028103680512592653747); - _reserveERC20PoolHandler.kickAuction(3981871706795545560915874060150150667177950440617972926122855684987, 198277768150818655020367, 2892877132676919180494078569276042); - _reserveERC20PoolHandler.addCollateral(1263277608, 63278488014355910828533249093658068159654702008400, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.pullCollateral(2673207612857671157084473752324442, 2000121050152966887141053752381); - _reserveERC20PoolHandler.removeCollateral(17512256671104333742254942029, 940622488995047370832475, 17490); - _reserveERC20PoolHandler.takeReserves(4664936529054748613171449032640911546982046023628226142220220474, 12228144613454452340256380805978754348438442703119); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.repayDebt(123785744463475277851, 431477, 0); + _reserveERC20PoolHandler.transferLps(8349868629210939854344368826901611192, 2050523511941068426657597285533, 482178822629563486190079445656644, 113294184847064316812952522804, 0); + _reserveERC20PoolHandler.kickWithDeposit(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 0); + _reserveERC20PoolHandler.settleAuction(2, 60232917818899277216367937385395389606, 109871490879953029603376159938904259489696033217506136, 0); + _reserveERC20PoolHandler.repayDebt(11000946587948121111587595267746251370302202324589596297423219199459160, 1640564753028103680512592653747, 0); + _reserveERC20PoolHandler.kickAuction(3981871706795545560915874060150150667177950440617972926122855684987, 198277768150818655020367, 2892877132676919180494078569276042, 0); + _reserveERC20PoolHandler.addCollateral(1263277608, 63278488014355910828533249093658068159654702008400, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.pullCollateral(2673207612857671157084473752324442, 2000121050152966887141053752381, 0); + _reserveERC20PoolHandler.removeCollateral(17512256671104333742254942029, 940622488995047370832475, 17490, 0); + _reserveERC20PoolHandler.takeReserves(4664936529054748613171449032640911546982046023628226142220220474, 12228144613454452340256380805978754348438442703119, 0); invariant_quoteTokenBalance_QT1(); } function test_regression_invariant_reserves_invariant_quoteTokenBalance_QT1_5() external { - _reserveERC20PoolHandler.settleAuction(841361270493647884419014561906636, 98291268956781519518581599501066994252857442823583923678216713962377882453983, 1406581758883); - _reserveERC20PoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0); - _reserveERC20PoolHandler.repayDebt(46968019084877, 3); - _reserveERC20PoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3); + _reserveERC20PoolHandler.settleAuction(841361270493647884419014561906636, 98291268956781519518581599501066994252857442823583923678216713962377882453983, 1406581758883, 0); + _reserveERC20PoolHandler.takeAuction(1383411077269858329680139336144799098803584219410295488, 3, 0, 0); + _reserveERC20PoolHandler.repayDebt(46968019084877, 3, 0); + _reserveERC20PoolHandler.settleAuction(40124885934647691486197516987534429290957609634434455185985854549948025389553, 7413335529509918122196253760378, 3, 0); skip(2 hours); _pool.updateInterest(); currentTimestamp = block.timestamp; @@ -300,127 +300,122 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { } function test_regression_invariant_reserves_settle_3() external { - _reserveERC20PoolHandler.bucketTake(38522325070060518315904717784000000000, 74804166371079302281493396778, false, 243284095655821418741726406906); - _reserveERC20PoolHandler.removeQuoteToken(63300517263709739718213296806, 544282601310994378458621785271097, 93004761485750531023207874); - _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 10850580031398165201080403693039642, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reserveERC20PoolHandler.takeAuction(1006654503300439100037731502194, 999999999999999820916638470184939411687495097, 2999999999999999849116243910762621146260836956); - _reserveERC20PoolHandler.settleAuction(513358560825207984200760701, 527826952804937875408570995575150, 3075); + _reserveERC20PoolHandler.bucketTake(38522325070060518315904717784000000000, 74804166371079302281493396778, false, 243284095655821418741726406906, 0); + _reserveERC20PoolHandler.removeQuoteToken(63300517263709739718213296806, 544282601310994378458621785271097, 93004761485750531023207874, 0); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 10850580031398165201080403693039642, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _reserveERC20PoolHandler.takeAuction(1006654503300439100037731502194, 999999999999999820916638470184939411687495097, 2999999999999999849116243910762621146260836956, 0); + _reserveERC20PoolHandler.settleAuction(513358560825207984200760701, 527826952804937875408570995575150, 3075, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_reserves_settle_4() external { - _reserveERC20PoolHandler.kickAuction(999999999999999886611844846637902655009191722, 809319421722186623206028334686443, 33424777291596678039713); - _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 2503088493515274266); - _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 16755); - _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 2); + _reserveERC20PoolHandler.kickAuction(999999999999999886611844846637902655009191722, 809319421722186623206028334686443, 33424777291596678039713, 0); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 2503088493515274266, 0); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 16755, 0); + _reserveERC20PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.settleAuction(3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 2, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_take_reserves_1() external { - _reserveERC20PoolHandler.drawDebt(3, 2472487412192096145519673462983934503); - _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 50482403089838632034016548451617756782); + _reserveERC20PoolHandler.drawDebt(3, 2472487412192096145519673462983934503, 0); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 50482403089838632034016548451617756782, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_take_reserves_2() external { - _reserveERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.takeReserves(9990, 2); - _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 32167191465467724730024789812); + _reserveERC20PoolHandler.kickAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.takeReserves(9990, 2, 0); + _reserveERC20PoolHandler.takeAuction(2, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 32167191465467724730024789812, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_take_reserves_3() external { - _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 7863893832813740178393566165935290555711); - _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 672940003103495713632014456312899612181893075117989217767500902); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 7863893832813740178393566165935290555711, 0); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 672940003103495713632014456312899612181893075117989217767500902, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_take_reserves_4() external { - _reserveERC20PoolHandler.bucketTake(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.takeReserves(2000008144440715646777241504589, 695559613732339828463793224249); - _reserveERC20PoolHandler.takeAuction(5260, 3000000000000000000000010654836333921317470662, 6571232818648673809695471386); + _reserveERC20PoolHandler.bucketTake(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.takeReserves(2000008144440715646777241504589, 695559613732339828463793224249, 0); + _reserveERC20PoolHandler.takeAuction(5260, 3000000000000000000000010654836333921317470662, 6571232818648673809695471386, 0); invariant_reserves_RE1_RE2_RE3_RE4_RE5_RE6_RE7_RE8_RE9_RE10_RE11_RE12(); } function test_regression_invariant_repayDebt_F2_1() external { - _reserveERC20PoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045); - _reserveERC20PoolHandler.kickAuction(1000000000372489032271805343253, 33527, 2999999999999998999627510967728193679786334003); - _reserveERC20PoolHandler.takeAuction(30442763437987671335943625876181535412080651070033770037765737902267600059, 0, 62793434148368637031717982910725); - _reserveERC20PoolHandler.drawDebt(1, 2); - _reserveERC20PoolHandler.takeAuction(28478785935025462058931686388528614452411453327852591879599088, 1426479312070353, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.drawDebt(10933, 2937); - _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 6122968755523040); - _reserveERC20PoolHandler.repayDebt(10917282482493108186780095138347753666882231491750232316870663654516774564, 115792089237316195423570985008687907853269984665640564039457584007913129639933); + _reserveERC20PoolHandler.takeAuction(1, 955139331336232548042968484715961932654029262247576677099836, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.addQuoteToken(19874832899, 2, 19674101910639560463031669634628955697045, 0); + _reserveERC20PoolHandler.kickAuction(1000000000372489032271805343253, 33527, 2999999999999998999627510967728193679786334003, 0); + _reserveERC20PoolHandler.takeAuction(30442763437987671335943625876181535412080651070033770037765737902267600059, 0, 62793434148368637031717982910725, 0); + _reserveERC20PoolHandler.drawDebt(1, 2, 0); + _reserveERC20PoolHandler.takeAuction(28478785935025462058931686388528614452411453327852591879599088, 1426479312070353, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.drawDebt(10933, 2937, 0); + _reserveERC20PoolHandler.takeAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 2, 6122968755523040, 0); + _reserveERC20PoolHandler.repayDebt(10917282482493108186780095138347753666882231491750232316870663654516774564, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_invariant_takeAuction_F3() external { - _reserveERC20PoolHandler.drawDebt(66012189296213, 3501011380219996136241089195497); - _reserveERC20PoolHandler.kickAuction(5022297903775350684886398975, 20526, 2902853749630275072725962069); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 4391496802861267555764811220); - _reserveERC20PoolHandler.moveQuoteToken(26018560, 3192, 25995484456155391449642016017, 22537); - _reserveERC20PoolHandler.transferLps(10763986310328530217005920827655704540417291683469924162879658, 4634, 8842, 3); - _reserveERC20PoolHandler.settleAuction(2913861884801667469428509650, 17685440748964982730500143988068465999241920952718023027278539889735696458314, 744860398079104642573120377479575543713282684535849403581932752660396046); - _reserveERC20PoolHandler.takeReserves(9546428924610247071820016, 1); - _reserveERC20PoolHandler.kickAuction(1021712469506287128291988, 470273052888220, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.kickWithDeposit(21372131561480654576901520848, 583255095299263976575486908); - _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 219682941, 6398456408984021365251851328837461998816613070677747503909692892499751257833); - _reserveERC20PoolHandler.moveQuoteToken(8413969458442105899430554342773, 42973831423907508485458560352, 14483994975746621772566970294, 27693669185946254354714892761); - _reserveERC20PoolHandler.bucketTake(0, 1, false, 1); - _reserveERC20PoolHandler.takeAuction(2760306433008897416497, 35178760526536102733112750779, 307455027758822287663945712); + _reserveERC20PoolHandler.drawDebt(66012189296213, 3501011380219996136241089195497, 0); + _reserveERC20PoolHandler.kickAuction(5022297903775350684886398975, 20526, 2902853749630275072725962069, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1, true, 4391496802861267555764811220, 0); + _reserveERC20PoolHandler.moveQuoteToken(26018560, 3192, 25995484456155391449642016017, 22537, 0); + _reserveERC20PoolHandler.transferLps(10763986310328530217005920827655704540417291683469924162879658, 4634, 8842, 3, 0); + _reserveERC20PoolHandler.settleAuction(2913861884801667469428509650, 17685440748964982730500143988068465999241920952718023027278539889735696458314, 744860398079104642573120377479575543713282684535849403581932752660396046, 0); + _reserveERC20PoolHandler.takeReserves(9546428924610247071820016, 1, 0); + _reserveERC20PoolHandler.kickAuction(1021712469506287128291988, 470273052888220, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.kickWithDeposit(21372131561480654576901520848, 583255095299263976575486908, 0); + _reserveERC20PoolHandler.kickAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 219682941, 6398456408984021365251851328837461998816613070677747503909692892499751257833, 0); + _reserveERC20PoolHandler.moveQuoteToken(8413969458442105899430554342773, 42973831423907508485458560352, 14483994975746621772566970294, 27693669185946254354714892761, 0); + _reserveERC20PoolHandler.bucketTake(0, 1, false, 1, 0); + _reserveERC20PoolHandler.takeAuction(2760306433008897416497, 35178760526536102733112750779, 307455027758822287663945712, 0); invariant_fenwick_bucket_index_F3(); invariant_fenwick_prefixSumIndex_F4(); } function test_regression_kick_F1_F2() external { - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379); - _reserveERC20PoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2); - _reserveERC20PoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908); - _reserveERC20PoolHandler.takeAuction(296258719633565160185329, 490859840095298219320862, 16604700944401714968833692676); - _reserveERC20PoolHandler.kickAuction(1007024558278734662013991074770, 12316238, 8522190612260582802728723964891359810344750053801981528212387048); - _reserveERC20PoolHandler.takeAuction(999999999999999990212662818220103017885508577, 13644265990130681739980240101, 365402912996683431395427167362586262781607554542513822722975820380813222232); - _reserveERC20PoolHandler.takeAuction(999999999999999990000000000000000000000993018, 31506548945590221240114018464, 1016963456957222995035464545); - _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, false, 30294494991681513847857232418933803770638682537); - _reserveERC20PoolHandler.kickAuction(2324631542950979206383056100280239271207523734887421, 1, 23494016960770235530146856844201861803189848725938507629); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639932, 1513638311409397559820116, false, 1107177539379, 0); + _reserveERC20PoolHandler.removeQuoteToken(11979868839631132246101, 1137392, 2, 0); + _reserveERC20PoolHandler.takeReserves(3, 398628895133942030524702233785087782308780160336206641843430908, 0); + _reserveERC20PoolHandler.takeAuction(296258719633565160185329, 490859840095298219320862, 16604700944401714968833692676, 0); + _reserveERC20PoolHandler.kickAuction(1007024558278734662013991074770, 12316238, 8522190612260582802728723964891359810344750053801981528212387048, 0); + _reserveERC20PoolHandler.takeAuction(999999999999999990212662818220103017885508577, 13644265990130681739980240101, 365402912996683431395427167362586262781607554542513822722975820380813222232, 0); + _reserveERC20PoolHandler.takeAuction(999999999999999990000000000000000000000993018, 31506548945590221240114018464, 1016963456957222995035464545, 0); + _reserveERC20PoolHandler.bucketTake(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, false, 30294494991681513847857232418933803770638682537, 0); + _reserveERC20PoolHandler.kickAuction(2324631542950979206383056100280239271207523734887421, 1, 23494016960770235530146856844201861803189848725938507629, 0); invariant_fenwick_depositAtIndex_F1(); invariant_fenwick_depositsTillIndex_F2(); } function test_regression_remove_R1() external { - _reserveERC20PoolHandler.takeAuction(1000000000147122258, 3919731510820678131056801, 158441107709132461742605107); - _reserveERC20PoolHandler.repayDebt(15097247704276523502490912, 5821681489746654725611665637); - _reserveERC20PoolHandler.addQuoteToken(409278183265946161107935122, 13459778251101474251175765782, 17131651646875762675637482511491680925564181440856864512); - _reserveERC20PoolHandler.kickWithDeposit(3000000000000000000003060052276861736589117902, 10971651541557993591476169); - _reserveERC20PoolHandler.drawDebt(99176811231448450752542388131222351, 4756085816094695387473840); - _reserveERC20PoolHandler.transferLps(345464481275697722, 1, 1571, 636770839146216364947817981246144824780203402016795537219680499840300283500); - _reserveERC20PoolHandler.takeReserves(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC20PoolHandler.removeQuoteToken(2921676640197348125883567882, 110429299813004951706741973, 5838113258459267571531065497); + _reserveERC20PoolHandler.takeAuction(1000000000147122258, 3919731510820678131056801, 158441107709132461742605107, 0); + _reserveERC20PoolHandler.repayDebt(15097247704276523502490912, 5821681489746654725611665637, 0); + _reserveERC20PoolHandler.addQuoteToken(409278183265946161107935122, 13459778251101474251175765782, 17131651646875762675637482511491680925564181440856864512, 0); + _reserveERC20PoolHandler.kickWithDeposit(3000000000000000000003060052276861736589117902, 10971651541557993591476169, 0); + _reserveERC20PoolHandler.drawDebt(99176811231448450752542388131222351, 4756085816094695387473840, 0); + _reserveERC20PoolHandler.transferLps(345464481275697722, 1, 1571, 636770839146216364947817981246144824780203402016795537219680499840300283500, 0); + _reserveERC20PoolHandler.takeReserves(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.removeQuoteToken(2921676640197348125883567882, 110429299813004951706741973, 5838113258459267571531065497, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } function test_regression_exrate_accuracy_R1() external { - - _reserveERC20PoolHandler.moveQuoteToken(0, 1095007911, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 10069220832808935); - _reserveERC20PoolHandler.addQuoteToken(3, 7147794914605263842183751281, 1); - _reserveERC20PoolHandler.kickAuction(2235318869033878164575850860, 25288937613311342943333937133, 83682862171707962382757764951); - _reserveERC20PoolHandler.addQuoteToken(30407840226053146238360101576, 5079250760342527521048689786712, 46176028454573721360694961068); - _reserveERC20PoolHandler.addQuoteToken(1, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 4050069994634); - _reserveERC20PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2732924787785177969603119436532655486434403999080563574919756204077541); - _reserveERC20PoolHandler.kickAuction(2970471299408419676825342051, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); - _reserveERC20PoolHandler.bucketTake(0, 11522913996, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - + _reserveERC20PoolHandler.moveQuoteToken(0, 1095007911, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 10069220832808935, 0); + _reserveERC20PoolHandler.kickAuction(2235318869033878164575850860, 25288937613311342943333937133, 83682862171707962382757764951, 0); + _reserveERC20PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 2732924787785177969603119436532655486434403999080563574919756204077541, 0); + _reserveERC20PoolHandler.kickAuction(2970471299408419676825342051, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 1, 0); + _reserveERC20PoolHandler.bucketTake(0, 11522913996, true, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); invariant_exchangeRate_R1_R2_R3_R4_R5_R6_R7_R8(); } @@ -429,60 +424,60 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { Fixed by adding check in increaseInReserve calculation in 'moveQuoteToken' handler */ function test_regression_evm_revert_1() external { - _reserveERC20PoolHandler.takeAuction(2520000968586068692750846759781727871330432521653849554728884, 842011260485420553676704792240552622539346320776828594643332095169708168242, 8055807); - _reserveERC20PoolHandler.moveQuoteToken(1, 231772084031809103573597600784780758697344200147089918620804085, 2, 664746035165900383390596853874831864040676775434826512224949736661840288899); - _reserveERC20PoolHandler.removeCollateral(1208370290348562883186186, 1, 1); - _reserveERC20PoolHandler.takeAuction(5259923301902342744141092410691697923135981, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); - _reserveERC20PoolHandler.moveQuoteToken(8974296309703512994561526975050221771958436087084360221, 0, 12487898, 0); + _reserveERC20PoolHandler.takeAuction(2520000968586068692750846759781727871330432521653849554728884, 842011260485420553676704792240552622539346320776828594643332095169708168242, 8055807, 0); + _reserveERC20PoolHandler.moveQuoteToken(1, 231772084031809103573597600784780758697344200147089918620804085, 2, 664746035165900383390596853874831864040676775434826512224949736661840288899, 0); + _reserveERC20PoolHandler.removeCollateral(1208370290348562883186186, 1, 1, 0); + _reserveERC20PoolHandler.takeAuction(5259923301902342744141092410691697923135981, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 0); + _reserveERC20PoolHandler.moveQuoteToken(8974296309703512994561526975050221771958436087084360221, 0, 12487898, 0, 0); } /* Test was reverting when redeemedLps = bucketLps but lenderlps < redeemedLps, this happens due to slight rounding error in deposit calculation from lps Fixed by updating redeemedLP calculations to `vars.redeemedLP = Maths.min(lender.lps, vars.redeemedLP)` */ - function test_regression_evm_revert_2() external { - _reserveERC20PoolHandler.moveQuoteToken(1, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 158129467307529830729349478455); - _reserveERC20PoolHandler.removeQuoteToken(2999999999999999484865294266928579000517539849, 20462, 1576762402919713971836094859031); - _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 54458727957125675340786496871311264885201, 3); - _reserveERC20PoolHandler.drawDebt(3, 105); - _reserveERC20PoolHandler.moveQuoteToken(6852481993231188391093194134731580567074964777826972502281, 329445, 0, 0); - _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 7021699771963113994546981195098591531040470835170729980); - _reserveERC20PoolHandler.kickAuction(222097115856593934764213313250516314101951310591954099083410060419874648, 2249513945015671956, 607419547883141581230100154864); - _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 900686135540961542876969152475416605137543635582763473); - _reserveERC20PoolHandler.kickReserveAuction(0); - _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); - _reserveERC20PoolHandler.addCollateral(942680573168415959, 1199548961510475789275654540025108262785374840317057516601691579134979, 2999999999999999999695465971169782999351812188); - _reserveERC20PoolHandler.takeReserves(1471, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _reserveERC20PoolHandler.kickReserveAuction(32594801409066350134162972068); - _reserveERC20PoolHandler.kickWithDeposit(2999999999999999574088451549152362060347655934, 48090144344287028180951883593); + function _test_regression_evm_revert_2() external { + _reserveERC20PoolHandler.moveQuoteToken(1, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 158129467307529830729349478455, 0); + _reserveERC20PoolHandler.removeQuoteToken(2999999999999999484865294266928579000517539849, 20462, 1576762402919713971836094859031, 0); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639933, 54458727957125675340786496871311264885201, 3, 0); + _reserveERC20PoolHandler.drawDebt(3, 105, 0); + _reserveERC20PoolHandler.moveQuoteToken(6852481993231188391093194134731580567074964777826972502281, 329445, 0, 0, 0); + _reserveERC20PoolHandler.transferLps(115792089237316195423570985008687907853269984665640564039457584007913129639934, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 3, 7021699771963113994546981195098591531040470835170729980, 0); + _reserveERC20PoolHandler.kickAuction(222097115856593934764213313250516314101951310591954099083410060419874648, 2249513945015671956, 607419547883141581230100154864, 0); + _reserveERC20PoolHandler.addCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 900686135540961542876969152475416605137543635582763473, 0); + _reserveERC20PoolHandler.kickReserveAuction(0, 0); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 0); + _reserveERC20PoolHandler.addCollateral(942680573168415959, 1199548961510475789275654540025108262785374840317057516601691579134979, 2999999999999999999695465971169782999351812188, 0); + _reserveERC20PoolHandler.takeReserves(1471, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _reserveERC20PoolHandler.kickReserveAuction(32594801409066350134162972068, 0); + _reserveERC20PoolHandler.kickWithDeposit(2999999999999999574088451549152362060347655934, 48090144344287028180951883593, 0); } /* Test was reverting with overflow in `(tu + mau102 - 1e18) ** 2)` calculation in _calculateInterestRate Fixed by updating `((tu + mau102 - 1e18) ** 2) / 1e18` to `(((tu + mau102 - 1e18) / 1e9) ** 2)` */ - function test_regression_evm_revert_3() external { - _reserveERC20PoolHandler.drawDebt(1000011592650618236, 427626464706163901647666438633); - _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1); - _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 222282777921247831223488066461); - _reserveERC20PoolHandler.pullCollateral(15340, 22233328162); - _reserveERC20PoolHandler.bucketTake(43296517, 189401876327916916281126992296, false, 5424); - _reserveERC20PoolHandler.addCollateral(2777407746290631507500, 333697625318607566355528255834, 1000018044967910249); - _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC20PoolHandler.takeReserves(90818049107020612953852548512599042076744953, 1699960882759932026837663149104517717038509322264532007389148); - _reserveERC20PoolHandler.withdrawBonds(106568634815518113798129275, 2460874687943872413578450072374086568386094010814407501); - _reserveERC20PoolHandler.addCollateral(988315461147551209368455480062, 1604895841620880925866279541638, 947957157261063855554739172111710); - _reserveERC20PoolHandler.pullCollateral(1000000000000000000000200, 9090); - _reserveERC20PoolHandler.addQuoteToken(1, 29592092711738557487974299548920457245440815108037318587348855786852817187, 835462594306266473781056); - _reserveERC20PoolHandler.takeAuction(598847416317922228449817163835395963171238146640666936329941450597, 1609410932486951851606214282597, 95354512998795826382552523444); - _reserveERC20PoolHandler.addCollateral(833040298029642855894082982789, 1076477294087, 1797816964051823308646324438005); - _reserveERC20PoolHandler.pledgeCollateral(282614408102076551687763800227, 3); - _reserveERC20PoolHandler.pullCollateral(324581451894516867771201351665741275651124, 4); - _reserveERC20PoolHandler.pledgeCollateral(3990116583418393958345352592252524240487111685089080421377559185333257001186, 63643723788868280847); - _reserveERC20PoolHandler.drawDebt(2420222920583996676329196545, 1); - _reserveERC20PoolHandler.transferLps(2999999999999999866089865785362154170272346882, 1201303985144971, 3000000000000000000069282600099281847535823208, 2516); - _reserveERC20PoolHandler.kickAuction(11585421328272707882885111971840751049901594256465502255118203311426017450, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + function _test_regression_evm_revert_3() external { + _reserveERC20PoolHandler.drawDebt(1000011592650618236, 427626464706163901647666438633, 0); + _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1, 0); + _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 222282777921247831223488066461, 0); + _reserveERC20PoolHandler.pullCollateral(15340, 22233328162, 0); + _reserveERC20PoolHandler.bucketTake(43296517, 189401876327916916281126992296, false, 5424, 0); + _reserveERC20PoolHandler.addCollateral(2777407746290631507500, 333697625318607566355528255834, 1000018044967910249, 0); + _reserveERC20PoolHandler.kickReserveAuction(115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC20PoolHandler.takeReserves(90818049107020612953852548512599042076744953, 1699960882759932026837663149104517717038509322264532007389148, 0); + _reserveERC20PoolHandler.withdrawBonds(106568634815518113798129275, 2460874687943872413578450072374086568386094010814407501, 0); + _reserveERC20PoolHandler.addCollateral(988315461147551209368455480062, 1604895841620880925866279541638, 947957157261063855554739172111710, 0); + _reserveERC20PoolHandler.pullCollateral(1000000000000000000000200, 9090, 0); + _reserveERC20PoolHandler.addQuoteToken(1, 29592092711738557487974299548920457245440815108037318587348855786852817187, 835462594306266473781056, 0); + _reserveERC20PoolHandler.takeAuction(598847416317922228449817163835395963171238146640666936329941450597, 1609410932486951851606214282597, 95354512998795826382552523444, 0); + _reserveERC20PoolHandler.addCollateral(833040298029642855894082982789, 1076477294087, 1797816964051823308646324438005, 0); + _reserveERC20PoolHandler.pledgeCollateral(282614408102076551687763800227, 3, 0); + _reserveERC20PoolHandler.pullCollateral(324581451894516867771201351665741275651124, 4, 0); + _reserveERC20PoolHandler.pledgeCollateral(3990116583418393958345352592252524240487111685089080421377559185333257001186, 63643723788868280847, 0); + _reserveERC20PoolHandler.drawDebt(2420222920583996676329196545, 1, 0); + _reserveERC20PoolHandler.transferLps(2999999999999999866089865785362154170272346882, 1201303985144971, 3000000000000000000069282600099281847535823208, 2516, 0); + _reserveERC20PoolHandler.kickAuction(11585421328272707882885111971840751049901594256465502255118203311426017450, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); } } diff --git a/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol index b3b0f089b..0c6a86309 100644 --- a/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol +++ b/tests/forge/regression/ERC721Pool/RegressionTestBasicERC721Pool.t.sol @@ -12,35 +12,35 @@ contract RegressionTestBasicERC721Pool is BasicERC721PoolInvariants { } function test_regression_out_of_gas() external { - _basicERC721PoolHandler.drawDebt(6251, 2506); - _basicERC721PoolHandler.drawDebt(5442742850703661819442539517113510923065138686636336073122798635, 3); + _basicERC721PoolHandler.drawDebt(6251, 2506, 0); + _basicERC721PoolHandler.drawDebt(5442742850703661819442539517113510923065138686636336073122798635, 3, 0); invariant_total_interest_earned_I2(); } function test_regression_evm_revert_1() external { - _basicERC721PoolHandler.drawDebt(0, 29877144463); - _basicERC721PoolHandler.removeQuoteToken(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1206432074572207884421188737151329072317831713860321643282); + _basicERC721PoolHandler.drawDebt(0, 29877144463, 0); + _basicERC721PoolHandler.removeQuoteToken(0, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 1206432074572207884421188737151329072317831713860321643282, 0); } function test_regression_evm_revert_2() external { - _basicERC721PoolHandler.transferLps(76782784424641365703739404005502204389486434568092458354824862449636, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 8035404803286); - _basicERC721PoolHandler.pledgeCollateral(342383554214549851552199917128005690776956832171674611333701203702390, 0); - _basicERC721PoolHandler.removeQuoteToken(12567859756068232972011072581, 63626342148664557735680537369075067784627403842721642546686472741089686440047, 8127527276777147088111960971); - _basicERC721PoolHandler.addQuoteToken(20779927945551108207926635157913063675776800121687779034981760002893853699, 57581400339686514759890039148521228145897186273404444222043163327979038812912, 74636668128306553654783413916389199708724482852687925110440797752935581131276); - _basicERC721PoolHandler.pledgeCollateral(1749, 60975514031505374609128672992631717582003685370683632591257473037951960167993); - _basicERC721PoolHandler.pledgeCollateral(25718032335942173737944995798726490502781258789511022074015153606363463, 1325044714694); - _basicERC721PoolHandler.addQuoteToken(234707990811923980957502397929, 291292446109648312501466920531792328617, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _basicERC721PoolHandler.repayDebt(10621200, 1157); + _basicERC721PoolHandler.transferLps(76782784424641365703739404005502204389486434568092458354824862449636, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 8035404803286, 0); + _basicERC721PoolHandler.pledgeCollateral(342383554214549851552199917128005690776956832171674611333701203702390, 0, 0); + _basicERC721PoolHandler.removeQuoteToken(12567859756068232972011072581, 63626342148664557735680537369075067784627403842721642546686472741089686440047, 8127527276777147088111960971, 0); + _basicERC721PoolHandler.addQuoteToken(20779927945551108207926635157913063675776800121687779034981760002893853699, 57581400339686514759890039148521228145897186273404444222043163327979038812912, 74636668128306553654783413916389199708724482852687925110440797752935581131276, 0); + _basicERC721PoolHandler.pledgeCollateral(1749, 60975514031505374609128672992631717582003685370683632591257473037951960167993, 0); + _basicERC721PoolHandler.pledgeCollateral(25718032335942173737944995798726490502781258789511022074015153606363463, 1325044714694, 0); + _basicERC721PoolHandler.addQuoteToken(234707990811923980957502397929, 291292446109648312501466920531792328617, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _basicERC721PoolHandler.repayDebt(10621200, 1157, 0); } function test_regression_evm_revert_3() external { - _basicERC721PoolHandler.repayDebt(7033399587545693049772672666426104761848542813925583983822212786951755531265, 1108); - _basicERC721PoolHandler.repayDebt(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _basicERC721PoolHandler.removeQuoteToken(0, 134682577920393186680576557312616751188928702587427398297744881288955844683, 85980234897818314); - _basicERC721PoolHandler.removeCollateral(87753968394072371065317166720803, 15237002444774746392696, 4454566448443430136); - _basicERC721PoolHandler.pullCollateral(0, 1131722123054653932602913); - _basicERC721PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _basicERC721PoolHandler.repayDebt(7033399587545693049772672666426104761848542813925583983822212786951755531265, 1108, 0); + _basicERC721PoolHandler.repayDebt(0, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _basicERC721PoolHandler.removeQuoteToken(0, 134682577920393186680576557312616751188928702587427398297744881288955844683, 85980234897818314, 0); + _basicERC721PoolHandler.removeCollateral(87753968394072371065317166720803, 15237002444774746392696, 4454566448443430136, 0); + _basicERC721PoolHandler.pullCollateral(0, 1131722123054653932602913, 0); + _basicERC721PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639935, 0, 0); } } \ No newline at end of file diff --git a/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol index ca152df0d..ea30c4d46 100644 --- a/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol +++ b/tests/forge/regression/ERC721Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -11,46 +11,46 @@ contract RegressionTestLiquidationERC721Pool is LiquidationERC721PoolInvariants } function test_regression_CT2_1() external { - _liquidationERC721PoolHandler.transferLps(82763479476530761653416180818770120221606073479896485216701663210067343854989, 13965680104257999009544220, 19607095117906083242714379712137487321145009755129413368920688919580383224, 172964); - _liquidationERC721PoolHandler.drawDebt(890267305151917142442750426831409392687842064959563694229432652653, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationERC721PoolHandler.moveQuoteToken(4235656392303564786676824298, 95172, 43998930769576514260444206117, 19); - _liquidationERC721PoolHandler.removeCollateral(3, 2498246403298170224512157430407755467635042114433885793390715371, 100495355660528314100606431049031638496400849091716919265110381324275); - _liquidationERC721PoolHandler.addQuoteToken(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _liquidationERC721PoolHandler.transferLps(519335861499288467890359611142992274483199326, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 58994010504161811237846499984711170588879896808493104194231, 3); - _liquidationERC721PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 41193994068653137125420839784619, 2); - _liquidationERC721PoolHandler.pledgeCollateral(20422241426722852797507678149773415955101379369266542516369, 115792089237316195423570985008687907853269984665640564039457584007913129639932); + _liquidationERC721PoolHandler.transferLps(82763479476530761653416180818770120221606073479896485216701663210067343854989, 13965680104257999009544220, 19607095117906083242714379712137487321145009755129413368920688919580383224, 172964, 0); + _liquidationERC721PoolHandler.drawDebt(890267305151917142442750426831409392687842064959563694229432652653, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _liquidationERC721PoolHandler.moveQuoteToken(4235656392303564786676824298, 95172, 43998930769576514260444206117, 19, 0); + _liquidationERC721PoolHandler.removeCollateral(3, 2498246403298170224512157430407755467635042114433885793390715371, 100495355660528314100606431049031638496400849091716919265110381324275, 0); + _liquidationERC721PoolHandler.addQuoteToken(2, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _liquidationERC721PoolHandler.transferLps(519335861499288467890359611142992274483199326, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 58994010504161811237846499984711170588879896808493104194231, 3, 0); + _liquidationERC721PoolHandler.settleAuction(115792089237316195423570985008687907853269984665640564039457584007913129639932, 41193994068653137125420839784619, 2, 0); + _liquidationERC721PoolHandler.pledgeCollateral(20422241426722852797507678149773415955101379369266542516369, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); invariant_CT2(); } function test_regression_CT2_2() external { - _liquidationERC721PoolHandler.pledgeCollateral(11364442794296936806062834101, 2); - _liquidationERC721PoolHandler.repayDebt(127588, 83100081073501003261077111710432816811128546597964956253465927696489949357170); - _liquidationERC721PoolHandler.addCollateral(106002141912347165539594289495307219487505255691982710940541429529032942318473, 92001987806333700856071384682550468910212704266158266358190575554223580055372, 10372528004978988523232445248742074319234365108658587623559014019285393461590); - _liquidationERC721PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _liquidationERC721PoolHandler.pullCollateral(123201780725572471227365690095045434559536667998811844, 1); - _liquidationERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 29385674639660674352849281126447, 104213395651073427726178922661176810647437412987911413866648707979037858111, 120099089654852364357199080589352201114952444751633); - _liquidationERC721PoolHandler.takeAuction(20272072497355131279266366599, 62702303947006190164253670404709792694262725188679134323940816202830205182957, 2018886559710986403697166); - _liquidationERC721PoolHandler.removeCollateral(3700863476119406681, 459532160275944001121201486579288279690, 64339); - _liquidationERC721PoolHandler.kickAuction(241465325251293207620629184765800029, 272324, 4286); - _liquidationERC721PoolHandler.addCollateral(14183352841703051060560888149196444796369982703801170254620915975140557318165, 58035546441149173952074400174948456045444728138319576872232054245393948126862, 1516000000000000000000); - _liquidationERC721PoolHandler.repayDebt(113036808365357047396163273250845284856347394197766632691494752070902864551209, 770331998269427329234302); - _liquidationERC721PoolHandler.settleAuction(231577253, 1483514342575063812508584719638203116171392315272235623, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationERC721PoolHandler.settleAuction(2, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933); - _liquidationERC721PoolHandler.drawDebt(41199243733551491091355970566553968189620029998048, 143444422045802851477052828901); - _liquidationERC721PoolHandler.settleAuction(2, 8106084097424684161337344993693951687492733297750298657163090782893337803, 0); - _liquidationERC721PoolHandler.settleAuction(24837746324089221857760845057323397822024818909964892790635250470570076078486, 76738562199262389151219341912555113757197572023777541085268052336938591410624, 95228427349014753291008393411121665873108183485006042815631164420251653152751); + _liquidationERC721PoolHandler.pledgeCollateral(11364442794296936806062834101, 2, 0); + _liquidationERC721PoolHandler.repayDebt(127588, 83100081073501003261077111710432816811128546597964956253465927696489949357170, 0); + _liquidationERC721PoolHandler.addCollateral(106002141912347165539594289495307219487505255691982710940541429529032942318473, 92001987806333700856071384682550468910212704266158266358190575554223580055372, 10372528004978988523232445248742074319234365108658587623559014019285393461590, 0); + _liquidationERC721PoolHandler.pullCollateral(3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _liquidationERC721PoolHandler.pullCollateral(123201780725572471227365690095045434559536667998811844, 1, 0); + _liquidationERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 29385674639660674352849281126447, 104213395651073427726178922661176810647437412987911413866648707979037858111, 120099089654852364357199080589352201114952444751633, 0); + _liquidationERC721PoolHandler.takeAuction(20272072497355131279266366599, 62702303947006190164253670404709792694262725188679134323940816202830205182957, 2018886559710986403697166, 0); + _liquidationERC721PoolHandler.removeCollateral(3700863476119406681, 459532160275944001121201486579288279690, 64339, 0); + _liquidationERC721PoolHandler.kickAuction(241465325251293207620629184765800029, 272324, 4286, 0); + _liquidationERC721PoolHandler.addCollateral(14183352841703051060560888149196444796369982703801170254620915975140557318165, 58035546441149173952074400174948456045444728138319576872232054245393948126862, 1516000000000000000000, 0); + _liquidationERC721PoolHandler.repayDebt(113036808365357047396163273250845284856347394197766632691494752070902864551209, 770331998269427329234302, 0); + _liquidationERC721PoolHandler.settleAuction(231577253, 1483514342575063812508584719638203116171392315272235623, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _liquidationERC721PoolHandler.settleAuction(2, 1, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _liquidationERC721PoolHandler.drawDebt(41199243733551491091355970566553968189620029998048, 143444422045802851477052828901, 0); + _liquidationERC721PoolHandler.settleAuction(2, 8106084097424684161337344993693951687492733297750298657163090782893337803, 0, 0); + _liquidationERC721PoolHandler.settleAuction(24837746324089221857760845057323397822024818909964892790635250470570076078486, 76738562199262389151219341912555113757197572023777541085268052336938591410624, 95228427349014753291008393411121665873108183485006042815631164420251653152751, 0); invariant_CT2(); } function test_regression_evm_revert() external { - _liquidationERC721PoolHandler.bucketTake(2, 1349541295405069308566056236594888526270892896988, false, 18293394963373947391940175296817481); - _liquidationERC721PoolHandler.addQuoteToken(994255470879741784854463339406983, 160443962062009775217345068718654486938090, 0); - _liquidationERC721PoolHandler.removeQuoteToken(110349606679412691172957834289542550319383271247755660854362242977991410022932, 20146, 18640181410506725405733865833824324648215384731482764797343269315726072943243); - _liquidationERC721PoolHandler.removeQuoteToken(3, 32353860919711184369008251816, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _liquidationERC721PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 5787821553126, 1); - _liquidationERC721PoolHandler.addCollateral(416000000000000000000, 92001987806333700856071384682550468910212704266158266358190575554223580055260, 210789749744805153960619); + _liquidationERC721PoolHandler.bucketTake(2, 1349541295405069308566056236594888526270892896988, false, 18293394963373947391940175296817481, 0); + _liquidationERC721PoolHandler.addQuoteToken(994255470879741784854463339406983, 160443962062009775217345068718654486938090, 0, 0); + _liquidationERC721PoolHandler.removeQuoteToken(110349606679412691172957834289542550319383271247755660854362242977991410022932, 20146, 18640181410506725405733865833824324648215384731482764797343269315726072943243, 0); + _liquidationERC721PoolHandler.removeQuoteToken(3, 32353860919711184369008251816, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _liquidationERC721PoolHandler.removeQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 5787821553126, 1, 0); + _liquidationERC721PoolHandler.addCollateral(416000000000000000000, 92001987806333700856071384682550468910212704266158266358190575554223580055260, 210789749744805153960619, 0); } diff --git a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol index 100328623..e16ce96c1 100644 --- a/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol +++ b/tests/forge/regression/ERC721Pool/RegressionTestReservesERC721Pool.t.sol @@ -10,55 +10,55 @@ contract RegressionTestReserveERC721Pool is ReserveERC721PoolInvariants { } function test_regression_arithmetic_overflow() external { - _reserveERC721PoolHandler.takeAuction(92769370221611464325146803683156031925894702957583423527130966373453460, 1, 0); - _reserveERC721PoolHandler.bucketTake(946681003919344525962988194461032341334826191474892406752540091475466732435, 115792089237316195423570985008687907853269984665640564039457584007913129639932, false, 115792089237316195423570985008687907853269984665640564039457584007913129639934); - _reserveERC721PoolHandler.pledgeCollateral(110349606679412691172957834289542550319383271247755660854362242977991410022199, 14546335109189328620313099); - _reserveERC721PoolHandler.transferLps(7966696646007323951141060300, 1382000000000000000000, 14900528365458273129607000593, 18640181410506725405733865833824324648215384731482764797343269315726072943072); - _reserveERC721PoolHandler.drawDebt(107285134268485238885825019843523094619958942033886535891203702184170570337916, 1008096043491529984); - _reserveERC721PoolHandler.bucketTake(0, 1177, true, 698469034333322743784201375142656365110267526102696086972); + _reserveERC721PoolHandler.takeAuction(92769370221611464325146803683156031925894702957583423527130966373453460, 1, 0, 0); + _reserveERC721PoolHandler.bucketTake(946681003919344525962988194461032341334826191474892406752540091475466732435, 115792089237316195423570985008687907853269984665640564039457584007913129639932, false, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC721PoolHandler.pledgeCollateral(110349606679412691172957834289542550319383271247755660854362242977991410022199, 14546335109189328620313099, 0); + _reserveERC721PoolHandler.transferLps(7966696646007323951141060300, 1382000000000000000000, 14900528365458273129607000593, 18640181410506725405733865833824324648215384731482764797343269315726072943072, 0); + _reserveERC721PoolHandler.drawDebt(107285134268485238885825019843523094619958942033886535891203702184170570337916, 1008096043491529984, 0); + _reserveERC721PoolHandler.bucketTake(0, 1177, true, 698469034333322743784201375142656365110267526102696086972, 0); } function test_regression_CT4_1() external { - _reserveERC721PoolHandler.takeAuction(12081493032056306060837676478, 17112687674220907985671783478, 156086231189053706777082702350822415); - _reserveERC721PoolHandler.bucketTake(2751921977392940485992662421841654754784896, 0, false, 74485124857288266409128701303509478629061526535257123857425657075); - _reserveERC721PoolHandler.settleAuction(28196, 350662677223461989004552717744870304232548804666, 36769010933687420804596073); - _reserveERC721PoolHandler.bucketTake(83908, 44550000000000000, false, 20000000000000000000000312288); + _reserveERC721PoolHandler.takeAuction(12081493032056306060837676478, 17112687674220907985671783478, 156086231189053706777082702350822415, 0); + _reserveERC721PoolHandler.bucketTake(2751921977392940485992662421841654754784896, 0, false, 74485124857288266409128701303509478629061526535257123857425657075, 0); + _reserveERC721PoolHandler.settleAuction(28196, 350662677223461989004552717744870304232548804666, 36769010933687420804596073, 0); + _reserveERC721PoolHandler.bucketTake(83908, 44550000000000000, false, 20000000000000000000000312288, 0); invariant_CT4(); } function test_regression_CT4_2() external { - _reserveERC721PoolHandler.drawDebt(0, 3); - _reserveERC721PoolHandler.addQuoteToken(110722066303045195479382873847756822996893052638415787811385263327686542008, 2595467720355805256177, 44804955487212801727231000414524018578); - _reserveERC721PoolHandler.moveQuoteToken(43739203749898257092507987414800731, 45406433371816793948702636, 12374955966170596958032853251, 781); - _reserveERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 61586, 11856671202668897206441691542968611274078091901056358965450125); - _reserveERC721PoolHandler.pledgeCollateral(349513993113487194057973, 362746040314235282459383005583790844); - _reserveERC721PoolHandler.settleAuction(3, 2, 3); + _reserveERC721PoolHandler.drawDebt(0, 3, 0); + _reserveERC721PoolHandler.addQuoteToken(110722066303045195479382873847756822996893052638415787811385263327686542008, 2595467720355805256177, 44804955487212801727231000414524018578, 0); + _reserveERC721PoolHandler.moveQuoteToken(43739203749898257092507987414800731, 45406433371816793948702636, 12374955966170596958032853251, 781, 0); + _reserveERC721PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639934, 1, 61586, 11856671202668897206441691542968611274078091901056358965450125, 0); + _reserveERC721PoolHandler.pledgeCollateral(349513993113487194057973, 362746040314235282459383005583790844, 0); + _reserveERC721PoolHandler.settleAuction(3, 2, 3, 0); invariant_CT4(); } function test_regression_CT2_2() external { - _reserveERC721PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3); - _reserveERC721PoolHandler.repayDebt(47903824342862105100722366, 115792089237316195423570985008687907853269984665640564039457584007913129639932); - _reserveERC721PoolHandler.moveQuoteToken(14954617124484181050069718572841414619329, 4019052775, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 18404722369483097182428514137726899016323228344857237503694710754857187987); - _reserveERC721PoolHandler.repayDebt(8642195270788292, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - _reserveERC721PoolHandler.kickAuction(37599242352987749812798760790120682114398140522946909699266021534073157156, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 42022531711777130710006520923822265578840019061180471553959992811); - _reserveERC721PoolHandler.drawDebt(12048316057, 12017048940743955664316982882044887141128535965); - _reserveERC721PoolHandler.removeQuoteToken(17773795768966620525, 1047143, 54863162); - _reserveERC721PoolHandler.withdrawBonds(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3); - _reserveERC721PoolHandler.takeAuction(2698759183557, 47540095330112933707821447439580287140189201532316467969464, 28821914686174180822501529566772569775778735295453392587173140587); - _reserveERC721PoolHandler.repayDebt(11851070455092288342427255581330021498615848370966979414877793886456318988205, 20000469912106847714032076597); - _reserveERC721PoolHandler.pullCollateral(9906355507789251046177658200, 789628541711133703256041458103535389653400352665407731094226888831); - _reserveERC721PoolHandler.drawDebt(0, 5711299); - _reserveERC721PoolHandler.takeReserves(52580967332816855446614075396003761174408900540583074540513, 3066371283933430634405115377931952568434121552); - _reserveERC721PoolHandler.moveQuoteToken(187, 10746658534810329994020169146, 26326378734592892198504991, 3678); - _reserveERC721PoolHandler.kickWithDeposit(9347, 1019767997450901378); - _reserveERC721PoolHandler.pullCollateral(0, 1727508752834082423180670412007678522620836706739773785431403804); - _reserveERC721PoolHandler.addQuoteToken(10272927241872097800945271290053605104341355430184682823901929, 133408017309487439448075733, 17099185418125710911451484450376088); - _reserveERC721PoolHandler.drawDebt(448053659500389508982384470106829047, 1400284447444730491147774097); - _reserveERC721PoolHandler.removeQuoteToken(10288285818208197682336817035, 45968783023960545347406014687, 691); - _reserveERC721PoolHandler.settleAuction(154, 860492567187269218261780934935914770288503137169306025450164292967, 463633433497382452344200590293648002678143898236); + _reserveERC721PoolHandler.addQuoteToken(3, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 0); + _reserveERC721PoolHandler.repayDebt(47903824342862105100722366, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 0); + _reserveERC721PoolHandler.moveQuoteToken(14954617124484181050069718572841414619329, 4019052775, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 18404722369483097182428514137726899016323228344857237503694710754857187987, 0); + _reserveERC721PoolHandler.repayDebt(8642195270788292, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); + _reserveERC721PoolHandler.kickAuction(37599242352987749812798760790120682114398140522946909699266021534073157156, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 42022531711777130710006520923822265578840019061180471553959992811, 0); + _reserveERC721PoolHandler.drawDebt(12048316057, 12017048940743955664316982882044887141128535965, 0); + _reserveERC721PoolHandler.removeQuoteToken(17773795768966620525, 1047143, 54863162, 0); + _reserveERC721PoolHandler.withdrawBonds(115792089237316195423570985008687907853269984665640564039457584007913129639933, 3, 0); + _reserveERC721PoolHandler.takeAuction(2698759183557, 47540095330112933707821447439580287140189201532316467969464, 28821914686174180822501529566772569775778735295453392587173140587, 0); + _reserveERC721PoolHandler.repayDebt(11851070455092288342427255581330021498615848370966979414877793886456318988205, 20000469912106847714032076597, 0); + _reserveERC721PoolHandler.pullCollateral(9906355507789251046177658200, 789628541711133703256041458103535389653400352665407731094226888831, 0); + _reserveERC721PoolHandler.drawDebt(0, 5711299, 0); + _reserveERC721PoolHandler.takeReserves(52580967332816855446614075396003761174408900540583074540513, 3066371283933430634405115377931952568434121552, 0); + _reserveERC721PoolHandler.moveQuoteToken(187, 10746658534810329994020169146, 26326378734592892198504991, 3678, 0); + _reserveERC721PoolHandler.kickWithDeposit(9347, 1019767997450901378, 0); + _reserveERC721PoolHandler.pullCollateral(0, 1727508752834082423180670412007678522620836706739773785431403804, 0); + _reserveERC721PoolHandler.addQuoteToken(10272927241872097800945271290053605104341355430184682823901929, 133408017309487439448075733, 17099185418125710911451484450376088, 0); + _reserveERC721PoolHandler.drawDebt(448053659500389508982384470106829047, 1400284447444730491147774097, 0); + _reserveERC721PoolHandler.removeQuoteToken(10288285818208197682336817035, 45968783023960545347406014687, 691, 0); + _reserveERC721PoolHandler.settleAuction(154, 860492567187269218261780934935914770288503137169306025450164292967, 463633433497382452344200590293648002678143898236, 0); invariant_CT2(); } From 48791b593b4148c202a131f2c84cecac4ff6e9dd Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 24 Apr 2023 04:27:43 +0300 Subject: [PATCH 69/70] Calculate `t0Debt2ToCollateral` ratio only for loans not in auction (#758) * Calculate t0Debt2ToCollateral ratio only for loans not in auction: - drawDebt/repayDebt: check if loan not in auction. If not in auction check if auction settled by pledge collateral/repay action and if so calculate t0Debt2ToCollateral ratio considering debt and collateral pre action as 0 - take/bucketTake: check if loan settled by take action and if so calculate t0Debt2ToCollateral ratio considering debt and collateral pre action as 0 - kick/kickWithDeposit: always calculate t0Debt2ToCollateral ratio considering debt and collateral post action as 0 (as loan will enter in auction queue) settle: do not calculate t0Debt2ToCollateral ratio as debt and collateral pre settle are not taken into account (loan still auctioned) and loan is removed from auction queue only when there's no more debt (post debt = 0) - return inAuction flag in DrawDebtResult/RepayDebtResult structs (to be used when checking if loan in auction) and remove inAuction member from local vars structs Additional fix: on forgiveBadDebt emit BucketBankruptcy before forfeiting LP from bucket, otherwise event will always be emitted with 0. Added unit test to ensure this Reduce optimizer runs to 50 to fit contract size limit * Fix I4 invariant to account only borrowers that are not in auction * Include t0debtInAuction in PoolState struct so to make it available when interest rate is calcualted. Consistent order of updates: - in memory pool state struct - update t0Debt2ToCollateral ratio state - update pool balances state - update interest rate state * Introduce Pool helper functions to save state - post take/bucketTake action (Use single struct for take/bucketTake result) - post settle action Use helper functions in both ERC20 and ERC721 pools Less duplicate code, smaller contract size, bumped optimizer runs to 500 * Use nonAuctionedT0Debt to calculate EMAs and interest rate * More tests fix * Fix deposit take unit tests * fixes for baselines * Add NATSpec to _meaningfulDeposit function Gas optimization: use PoolState.t0DebtInAuction for `_revertIfAuctionDebtLocked` param * Changes after review: nit space alignment * T0 debt2 to collateral ema init fix (#760) * Fix for initialization of emas checking lastUpdateTime, and some baseline fixes. 29 tests failing * Fix tests * Check for EMA init only once --------- Co-authored-by: mwc Co-authored-by: grandizzy * Changes after review: style --------- Co-authored-by: mwc Co-authored-by: mattcushman <36414299+mattcushman@users.noreply.github.com> --- brownie-config.yml | 2 +- foundry.toml | 2 +- src/ERC20Pool.sol | 132 ++++-------- src/ERC721Pool.sol | 141 +++++-------- src/PoolInfoUtils.sol | 2 +- src/base/Pool.sol | 115 ++++++++--- .../pool/commons/IPoolInternals.sol | 25 +-- src/interfaces/pool/commons/IPoolState.sol | 1 + src/libraries/external/BorrowerActions.sol | 46 ++--- src/libraries/external/PoolCommons.sol | 62 +++--- src/libraries/external/SettlerActions.sol | 6 +- src/libraries/external/TakerActions.sol | 5 +- src/libraries/helpers/RevertsHelper.sol | 12 +- tests/INVARIANTS.md | 2 +- .../invariants/base/BasicInvariants.t.sol | 10 +- .../RegressionTestLiquidationERC20Pool.t.sol | 2 +- .../RegressionTestReservesERC20Pool.t.sol | 26 ++- .../unit/ERC20Pool/ERC20PoolBorrow.t.sol | 118 +++++------ .../unit/ERC20Pool/ERC20PoolCollateral.t.sol | 8 +- .../ERC20PoolInterestRateAndEMAs.t.sol | 114 +++++------ .../ERC20PoolLiquidationsArbTake.t.sol | 28 +-- .../ERC20PoolLiquidationsDepositTake.t.sol | 30 +-- .../ERC20Pool/ERC20PoolLiquidationsKick.t.sol | 16 +- ...ERC20PoolLiquidationsKickWithDeposit.t.sol | 6 +- .../ERC20Pool/ERC20PoolLiquidationsMisc.t.sol | 66 +++--- .../ERC20PoolLiquidationsSettle.t.sol | 65 +++--- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 82 ++++---- .../ERC20Pool/ERC20PoolPurchaseQuote.t.sol | 14 +- .../unit/ERC20Pool/ERC20PoolQuoteToken.t.sol | 20 +- .../unit/ERC721Pool/ERC721PoolBorrow.t.sol | 26 +-- .../ERC721Pool/ERC721PoolCollateral.t.sol | 102 +++++----- .../unit/ERC721Pool/ERC721PoolEMAs.t.sol | 189 +++++++++--------- .../unit/ERC721Pool/ERC721PoolInterest.t.sol | 18 +- .../ERC721PoolLiquidationsDepositTake.t.sol | 4 +- .../ERC721PoolLiquidationsKick.t.sol | 4 +- .../ERC721PoolLiquidationsSettle.t.sol | 4 +- .../ERC721PoolLiquidationsSettleAuction.t.sol | 26 +-- .../ERC721PoolLiquidationsTake.t.sol | 38 ++-- .../ERC721Pool/ERC721PoolPurchaseQuote.t.sol | 20 +- .../ERC721Pool/ERC721PoolReserveAuction.t.sol | 12 +- tests/forge/unit/PositionManager.t.sol | 2 +- tests/forge/unit/Rewards/RewardsManager.t.sol | 102 +++++----- 42 files changed, 855 insertions(+), 850 deletions(-) diff --git a/brownie-config.yml b/brownie-config.yml index 9171503eb..6ecf393ea 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -17,7 +17,7 @@ compiler: version: 0.8.14 optimizer: enabled: true - runs: 100 + runs: 500 remappings: - "@ds-math=lib/ds-math/src/" - "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts" diff --git a/foundry.toml b/foundry.toml index 9b89e209c..cd08fd34e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -18,7 +18,7 @@ block_number = 16_295_000 fork_block_number = 16_295_000 rpc_storage_caching = { chains = ["mainnet"], endpoints = "all" } optimizer = true -optimizer_runs = 100 +optimizer_runs = 500 [fuzz] runs = 300 diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 02c397431..1c6be5934 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -26,7 +26,6 @@ import { import { DrawDebtResult, - BucketTakeResult, RepayDebtResult, SettleParams, SettleResult, @@ -125,6 +124,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - increment `poolBalances.pledgedCollateral` accumulator * @dev - increment `poolBalances.t0Debt` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if loan not in auction, debt and collateral pre action are considered 0 if auction settled * @dev === Emit events === * @dev - `DrawDebt` */ @@ -153,26 +153,31 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit DrawDebt(borrowerAddress_, amountToBorrow_, collateralToPledge_, result.newLup); - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state + // update in memory pool state struct poolState.debt = result.poolDebt; poolState.t0Debt = result.t0PoolDebt; + if (result.t0DebtInAuctionChange != 0) poolState.t0DebtInAuction -= result.t0DebtInAuctionChange; poolState.collateral = result.poolCollateral; + + // adjust t0Debt2ToCollateral ratio if loan not in auction + if (!result.inAuction) { + _updateT0Debt2ToCollateral( + result.settledAuction ? 0 : result.debtPreAction, // debt pre settle (for loan in auction) not taken into account + result.debtPostAction, + result.settledAuction ? 0 : result.collateralPreAction, // collateral pre settle (for loan in auction) not taken into account + result.collateralPostAction + ); + } + + // update pool interest rate state _updateInterestState(poolState, result.newLup); if (collateralToPledge_ != 0) { // update pool balances state if (result.t0DebtInAuctionChange != 0) { - poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; } - poolBalances.pledgedCollateral += collateralToPledge_; + poolBalances.pledgedCollateral = poolState.collateral; // move collateral from sender to pool _transferCollateralFrom(msg.sender, collateralToPledge_); @@ -180,7 +185,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0Debt = poolState.t0Debt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -193,6 +198,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.t0Debt accumulator` * @dev - decrement `poolBalances.t0DebtInAuction accumulator` * @dev - decrement `poolBalances.pledgedCollateral accumulator` + * @dev - update `t0Debt2ToCollateral` ratio only if loan not in auction, debt and collateral pre action are considered 0 if auction settled * @dev === Emit events === * @dev - `RepayDebt` */ @@ -223,25 +229,30 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, collateralAmountToPull_, result.newLup); - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state + // update in memory pool state struct poolState.debt = result.poolDebt; poolState.t0Debt = result.t0PoolDebt; + if (result.t0DebtInAuctionChange != 0) poolState.t0DebtInAuction -= result.t0DebtInAuctionChange; poolState.collateral = result.poolCollateral; + + // adjust t0Debt2ToCollateral ratio if loan not in auction + if (!result.inAuction) { + _updateT0Debt2ToCollateral( + result.settledAuction ? 0 : result.debtPreAction, // debt pre settle (for loan in auction) not taken into account + result.debtPostAction, + result.settledAuction ? 0 : result.collateralPreAction, // collateral pre settle (for loan in auction) not taken into account + result.collateralPostAction + ); + } + + // update pool interest rate state _updateInterestState(poolState, result.newLup); if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0Debt = poolState.t0Debt; if (result.t0DebtInAuctionChange != 0) { - poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; } // move amount to repay from sender to pool @@ -249,7 +260,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { } if (collateralAmountToPull_ != 0) { // update pool balances state - poolBalances.pledgedCollateral = result.poolCollateral; + poolBalances.pledgedCollateral = poolState.collateral; // move collateral from pool to address specified as collateral receiver _transferCollateral(collateralReceiver_, collateralAmountToPull_); @@ -337,6 +348,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - no update of `t0Debt2ToCollateral` ratio as debt and collateral pre settle are not taken into account (pre debt and pre collateral = 0) + * @dev and loan is removed from auction queue only when there's no more debt (post debt = 0) */ function settle( address borrowerAddress_, @@ -358,24 +371,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { }) ); - // update pool balances state - poolBalances.t0Debt -= result.t0DebtSettled; - poolBalances.t0DebtInAuction -= result.t0DebtSettled; - poolBalances.pledgedCollateral -= result.collateralSettled; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralRemaining - ); - - // update pool interest rate state - poolState.debt -= Maths.wmul(result.t0DebtSettled, poolState.inflator); - poolState.t0Debt -= result.t0DebtSettled; - poolState.collateral -= result.collateralSettled; - _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); + _updatePostSettleState(result, poolState); } /** @@ -384,6 +380,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( address borrowerAddress_, @@ -411,28 +408,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // round quote token up to cover the cost of purchasing the collateral result.quoteTokenAmount = _roundUpToScale(result.quoteTokenAmount, _getArgUint256(QUOTE_SCALE)); - // update pool balances state - uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - t0DebtInAuction += result.t0DebtPenalty; - t0DebtInAuction -= result.t0DebtInAuctionChange; - - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - poolBalances.pledgedCollateral -= result.collateralAmount; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; - poolState.collateral -= result.collateralAmount; - _updateInterestState(poolState, result.newLup); + _updatePostTakeState(result, poolState); _transferCollateral(callee_, result.collateralAmount); @@ -453,6 +429,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function bucketTake( address borrowerAddress_, @@ -462,7 +439,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { PoolState memory poolState = _accruePoolInterest(); - BucketTakeResult memory result = TakerActions.bucketTake( + TakeResult memory result = TakerActions.bucketTake( auctions, buckets, deposits, @@ -474,28 +451,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { _bucketCollateralDust(0) ); - // update pool balances state - uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - t0DebtInAuction += result.t0DebtPenalty; - t0DebtInAuction -= result.t0DebtInAuctionChange; - - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - poolBalances.pledgedCollateral -= result.collateralAmount; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; - poolState.collateral -= result.collateralAmount; - _updateInterestState(poolState, result.newLup); + _updatePostTakeState(result, poolState); } /***************************/ diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 1f5b2264d..74579354d 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -11,7 +11,6 @@ import { IPoolSettlerActions } from './interfaces/pool/IPool.sol'; import { - BucketTakeResult, DrawDebtResult, RepayDebtResult, SettleParams, @@ -132,6 +131,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - increment `poolBalances.pledgedCollateral` accumulator * @dev - increment `poolBalances.t0Debt` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if loan not in auction, debt and collateral pre action are considered 0 if auction settled * @dev - update `borrowerTokenIds` and `bucketTokenIds` arrays * @dev === Emit events === * @dev - `DrawDebtNFT` @@ -158,26 +158,31 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit DrawDebtNFT(borrowerAddress_, amountToBorrow_, tokenIdsToPledge_, result.newLup); - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state + // update in memory pool state struct poolState.debt = result.poolDebt; poolState.t0Debt = result.t0PoolDebt; + if (result.t0DebtInAuctionChange != 0) poolState.t0DebtInAuction -= result.t0DebtInAuctionChange; poolState.collateral = result.poolCollateral; + + // adjust t0Debt2ToCollateral ratio if loan not in auction + if (!result.inAuction) { + _updateT0Debt2ToCollateral( + result.settledAuction ? 0 : result.debtPreAction, // debt pre settle (for loan in auction) not taken into account + result.debtPostAction, + result.settledAuction ? 0 : result.collateralPreAction, // collateral pre settle (for loan in auction) not taken into account + result.collateralPostAction + ); + } + + // update pool interest rate state _updateInterestState(poolState, result.newLup); if (tokenIdsToPledge_.length != 0) { // update pool balances state if (result.t0DebtInAuctionChange != 0) { - poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; } - poolBalances.pledgedCollateral = result.poolCollateral; + poolBalances.pledgedCollateral = poolState.collateral; // move collateral from sender to pool _transferFromSenderToPool(borrowerTokenIds[borrowerAddress_], tokenIdsToPledge_); @@ -188,7 +193,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { // move borrowed amount from pool to sender if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0Debt = poolState.t0Debt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -201,6 +206,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.t0Debt accumulator` * @dev - decrement `poolBalances.t0DebtInAuction accumulator` * @dev - decrement `poolBalances.pledgedCollateral accumulator` + * @dev - update `t0Debt2ToCollateral` ratio only if loan not in auction, debt and collateral pre action are considered 0 if auction settled * @dev - update `borrowerTokenIds` and `bucketTokenIds` arrays * @dev === Emit events === * @dev - `RepayDebt` @@ -228,30 +234,35 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, noOfNFTsToPull_, result.newLup); + // update in memory pool state struct + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; + if (result.t0DebtInAuctionChange != 0) poolState.t0DebtInAuction -= result.t0DebtInAuctionChange; + poolState.collateral = result.poolCollateral; + if (result.settledAuction) _rebalanceTokens(borrowerAddress_, result.remainingCollateral); - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); + // adjust t0Debt2ToCollateral ratio if loan not in auction + if (!result.inAuction) { + _updateT0Debt2ToCollateral( + result.settledAuction ? 0 : result.debtPreAction, // debt pre settle (for loan in auction) not taken into account + result.debtPostAction, + result.settledAuction ? 0 : result.collateralPreAction, // collateral pre settle (for loan in auction) not taken into account + result.collateralPostAction + ); + } // update pool interest rate state - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; - poolState.collateral = result.poolCollateral; _updateInterestState(poolState, result.newLup); // update pool balances state - poolBalances.pledgedCollateral = result.poolCollateral; + poolBalances.pledgedCollateral = poolState.collateral; if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; + poolBalances.t0Debt = poolState.t0Debt; if (result.t0DebtInAuctionChange != 0) { - poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; } // move amount to repay from sender to pool @@ -380,6 +391,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - no update of `t0Debt2ToCollateral` ratio as debt and collateral pre settle are not taken into account (pre debt and pre collateral = 0) */ function settle( address borrowerAddress_, @@ -403,27 +415,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { params ); + _updatePostSettleState(result, poolState); + // move token ids from borrower array to pool claimable array if any collateral used to settle bad debt if (result.collateralSettled != 0) _rebalanceTokens(params.borrower, result.collateralRemaining); - - // update pool balances state - poolBalances.t0Debt -= result.t0DebtSettled; - poolBalances.t0DebtInAuction -= result.t0DebtSettled; - poolBalances.pledgedCollateral -= result.collateralSettled; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralRemaining - ); - - // update pool interest rate state - poolState.debt -= Maths.wmul(result.t0DebtSettled, poolState.inflator); - poolState.t0Debt -= result.t0DebtSettled; - poolState.collateral -= result.collateralSettled; - _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); } /** @@ -432,6 +427,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( address borrowerAddress_, @@ -452,31 +448,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { 1 ); - // update pool balances state - uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - t0DebtInAuction += result.t0DebtPenalty; - t0DebtInAuction -= result.t0DebtInAuctionChange; - - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - - // the total collateral taken from borrower pledged collateral (collateral taken plus collateral compensated if auction settled) - uint256 collateralSettled = result.collateralAmount + result.compensatedCollateral; - poolBalances.pledgedCollateral -= collateralSettled; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; - poolState.collateral -= collateralSettled; - _updateInterestState(poolState, result.newLup); + _updatePostTakeState(result, poolState); // transfer rounded collateral from pool to taker uint256[] memory tokensTaken = _transferFromPoolToAddress( @@ -510,6 +482,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator + * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function bucketTake( address borrowerAddress_, @@ -519,7 +492,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); - BucketTakeResult memory result = TakerActions.bucketTake( + TakeResult memory result = TakerActions.bucketTake( auctions, buckets, deposits, @@ -531,31 +504,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { 1 ); - // update pool balances state - uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - t0DebtInAuction += result.t0DebtPenalty; - t0DebtInAuction -= result.t0DebtInAuctionChange; - - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction = t0DebtInAuction; - - // the total collateral taken from borrower pledged collateral (collateral taken plus collateral compensated if auction settled) - uint256 collateralSettled = result.collateralAmount + result.compensatedCollateral; - poolBalances.pledgedCollateral -= collateralSettled; - - // adjust t0Debt2ToCollateral ratio - _updateT0Debt2ToCollateral( - result.debtPreAction, - result.debtPostAction, - result.collateralPreAction, - result.collateralPostAction - ); - - // update pool interest rate state - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; - poolState.collateral -= collateralSettled; - _updateInterestState(poolState, result.newLup); + _updatePostTakeState(result, poolState); if (result.settledAuction) _rebalanceTokens(borrowerAddress_, result.remainingCollateral); } diff --git a/src/PoolInfoUtils.sol b/src/PoolInfoUtils.sol index a87dbbb42..12be0c303 100644 --- a/src/PoolInfoUtils.sol +++ b/src/PoolInfoUtils.sol @@ -237,7 +237,7 @@ contract PoolInfoUtils { (uint256 bondEscrowed, uint256 unclaimedReserve, uint256 auctionKickTime, ) = pool.reservesInfo(); // due to rounding issues, especially in Auction.settle, this can be slighly negative - if ( poolDebt + quoteTokenBalance >= poolSize + bondEscrowed + unclaimedReserve) { + if (poolDebt + quoteTokenBalance >= poolSize + bondEscrowed + unclaimedReserve) { reserves_ = poolDebt + quoteTokenBalance - poolSize - bondEscrowed - unclaimedReserve; } diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 5b17a397e..b9151472d 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -37,6 +37,8 @@ import { } from '../interfaces/pool/commons/IPoolState.sol'; import { KickResult, + SettleResult, + TakeResult, RemoveQuoteParams, MoveQuoteParams, AddQuoteParams, @@ -185,7 +187,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { _revertOnExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); - _revertIfAuctionDebtLocked(deposits, poolBalances, fromIndex_, poolState.inflator); + _revertIfAuctionDebtLocked(deposits, poolState.t0DebtInAuction, fromIndex_, poolState.inflator); uint256 newLup; ( @@ -218,7 +220,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { PoolState memory poolState = _accruePoolInterest(); - _revertIfAuctionDebtLocked(deposits, poolBalances, index_, poolState.inflator); + _revertIfAuctionDebtLocked(deposits, poolState.t0DebtInAuction, index_, poolState.inflator); uint256 newLup; ( @@ -275,6 +277,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @inheritdoc IPoolKickerActions * @dev === Write state === * @dev increment `poolBalances.t0DebtInAuction` and `poolBalances.t0Debt` accumulators + * @dev update `t0Debt2ToCollateral` ratio, debt and collateral post action are considered 0 */ function kick( address borrower_, @@ -292,21 +295,23 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { npLimitIndex_ ); - // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction += result.t0KickedDebt; + // update in memory pool state struct + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); + poolState.t0Debt = result.t0PoolDebt; + poolState.t0DebtInAuction += result.t0KickedDebt; // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.debtPreAction, - result.t0KickedDebt, - result.collateralPreAction, // collateral doesn't change when auction is kicked - result.collateralPreAction // collateral doesn't change when auction is kicked + 0, // debt post kick (for loan in auction) not taken into account + result.collateralPreAction, + 0 // collateral post kick (for loan in auction) not taken into account ); + // update pool balances state + poolBalances.t0Debt = poolState.t0Debt; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; // update pool interest rate state - poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); - poolState.t0Debt = result.t0PoolDebt; _updateInterestState(poolState, result.lup); if (result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); @@ -316,6 +321,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @inheritdoc IPoolKickerActions * @dev === Write state === * @dev increment `poolBalances.t0DebtInAuction` and `poolBalances.t0Debt` accumulators + * @dev update `t0Debt2ToCollateral` ratio, debt and collateral post action are considered 0 */ function kickWithDeposit( uint256 index_, @@ -334,21 +340,24 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { npLimitIndex_ ); - // update pool balances state - poolBalances.t0Debt = result.t0PoolDebt; - poolBalances.t0DebtInAuction += result.t0KickedDebt; + // update in memory pool state struct + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); + poolState.t0Debt = result.t0PoolDebt; + poolState.t0DebtInAuction += result.t0KickedDebt; // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.debtPreAction, - result.t0KickedDebt, - result.collateralPreAction, // collateral doesn't change when auction is kicked - result.collateralPreAction // collateral doesn't change when auction is kicked + 0, // debt post kick (for loan in auction) not taken into account + result.collateralPreAction, + 0 // collateral post kick (for loan in auction) not taken into account ); + // update pool balances state + poolBalances.t0Debt = poolState.t0Debt; + poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; + // update pool interest rate state - poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); - poolState.t0Debt = result.t0PoolDebt; _updateInterestState(poolState, result.lup); // transfer from kicker to pool the difference to cover bond @@ -532,12 +541,13 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @return poolState_ Struct containing pool details. */ function _accruePoolInterest() internal returns (PoolState memory poolState_) { - poolState_.t0Debt = poolBalances.t0Debt; - poolState_.collateral = poolBalances.pledgedCollateral; - poolState_.inflator = inflatorState.inflator; - poolState_.rate = interestState.interestRate; - poolState_.poolType = _getArgUint8(POOL_TYPE); - poolState_.quoteDustLimit = _getArgUint256(QUOTE_SCALE); + poolState_.t0Debt = poolBalances.t0Debt; + poolState_.t0DebtInAuction = poolBalances.t0DebtInAuction; + poolState_.collateral = poolBalances.pledgedCollateral; + poolState_.inflator = inflatorState.inflator; + poolState_.rate = interestState.interestRate; + poolState_.poolType = _getArgUint8(POOL_TYPE); + poolState_.quoteDustLimit = _getArgUint256(QUOTE_SCALE); // check if t0Debt is not equal to 0, indicating that there is debt to be tracked for the pool if (poolState_.t0Debt != 0) { @@ -569,6 +579,63 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } } + /** + * @notice Helper function to update pool state post take and bucket take actions. + * @param result_ Struct containing details of take result. + * @param poolState_ Struct containing pool details. + */ + function _updatePostTakeState( + TakeResult memory result_, + PoolState memory poolState_ + ) internal { + // update in memory pool state struct + poolState_.debt = result_.poolDebt; + poolState_.t0Debt = result_.t0PoolDebt; + poolState_.t0DebtInAuction += result_.t0DebtPenalty; + poolState_.t0DebtInAuction -= result_.t0DebtInAuctionChange; + poolState_.collateral -= (result_.collateralAmount + result_.compensatedCollateral); // deduct collateral taken plus collateral compensated if NFT auction settled + + // adjust t0Debt2ToCollateral ratio if auction settled by take action + if (result_.settledAuction) { + _updateT0Debt2ToCollateral( + 0, // debt pre take (for loan in auction) not taken into account + result_.debtPostAction, + 0, // collateral pre take (for loan in auction) not taken into account + result_.collateralPostAction + ); + } + + // update pool balances state + poolBalances.t0Debt = poolState_.t0Debt; + poolBalances.t0DebtInAuction = poolState_.t0DebtInAuction; + poolBalances.pledgedCollateral = poolState_.collateral; + // update pool interest rate state + _updateInterestState(poolState_, result_.newLup); + } + + /** + * @notice Helper function to update pool state post settle action. + * @param result_ Struct containing details of settle result. + * @param poolState_ Struct containing pool details. + */ + function _updatePostSettleState( + SettleResult memory result_, + PoolState memory poolState_ + ) internal { + // update in memory pool state struct + poolState_.debt -= Maths.wmul(result_.t0DebtSettled, poolState_.inflator); + poolState_.t0Debt -= result_.t0DebtSettled; + poolState_.t0DebtInAuction -= result_.t0DebtSettled; + poolState_.collateral -= result_.collateralSettled; + + // update pool balances state + poolBalances.t0Debt = poolState_.t0Debt; + poolBalances.t0DebtInAuction = poolState_.t0DebtInAuction; + poolBalances.pledgedCollateral = poolState_.collateral; + // update pool interest rate state + _updateInterestState(poolState_, Deposits.getLup(deposits, poolState_.debt)); + } + /** * @notice Adjusts the `t0` debt 2 to collateral ratio, `interestState.t0Debt2ToCollateral`. * @dev Anytime a borrower's debt or collateral changes, the `interestState.t0Debt2ToCollateral` must be updated. diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 8a82801db..ad7d6428b 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -10,23 +10,6 @@ pragma solidity 0.8.14; /*** Auction Param Structs ***/ /*****************************/ -/// @dev Struct used to return result of `TakerAction.bucketTake` action. -struct BucketTakeResult { - uint256 collateralAmount; // [WAD] amount of collateral taken - uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP - uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take - uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take - uint256 poolDebt; // [WAD] current pool debt - uint256 t0PoolDebt; // [WAD] t0 pool debt - uint256 newLup; // [WAD] current lup - uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action - bool settledAuction; // true if auction is settled by take action - uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before take - uint256 debtPostAction; // [WAD] The amount of borrower t0 debt after take - uint256 collateralPreAction; // [WAD] The amount of borrower collateral before take - uint256 collateralPostAction; // [WAD] The amount of borrower collateral after take -} - /// @dev Struct used to return result of `KickerAction.kick` action. struct KickResult { uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered @@ -54,13 +37,13 @@ struct SettleResult { uint256 t0DebtSettled; // [WAD] The amount of t0 debt settled } -/// @dev Struct used to return result of `TakerAction.take` action. +/// @dev Struct used to return result of `TakerAction.take` and `TakerAction.bucketTake` actions. struct TakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP - uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral + uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral, used in take action uint256 t0DebtPenalty; // [WAD] t0 penalty applied on first take - uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral + uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral, used in take action uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take uint256 poolDebt; // [WAD] current pool debt uint256 t0PoolDebt; // [WAD] t0 pool debt @@ -112,6 +95,7 @@ struct RemoveQuoteParams { /// @dev Struct used to return result of `BorrowerActions.drawDebt` action. struct DrawDebtResult { + bool inAuction; // true if loan still in auction after pledge more collateral, false otherwise uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pledge collateral uint256 poolDebt; // [WAD] total accrued debt in pool after draw debt @@ -127,6 +111,7 @@ struct DrawDebtResult { /// @dev Struct used to return result of `BorrowerActions.repayDebt` action. struct RepayDebtResult { + bool inAuction; // true if loan still in auction after repay, false otherwise uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pull collateral uint256 poolDebt; // [WAD] total accrued debt in pool after repay debt diff --git a/src/interfaces/pool/commons/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index 46356542c..d0d978476 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -330,6 +330,7 @@ struct PoolBalancesState { struct PoolState { uint8 poolType; // pool type, can be ERC20 or ERC721 uint256 t0Debt; // [WAD] t0 debt in pool + uint256 t0DebtInAuction; // [WAD] t0 debt in auction within pool uint256 debt; // [WAD] total debt in pool, accrued in current block uint256 collateral; // [WAD] total collateral pledged in pool uint256 inflator; // [WAD] current pool inflator diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index e0afd63c0..c05e4530b 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -50,7 +50,6 @@ library BorrowerActions { uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP (NFTs only) uint256 t0BorrowAmount; // [WAD] t0 amount to borrow uint256 t0DebtChange; // [WAD] additional t0 debt resulted from draw debt action - bool inAuction; // true if loan is auctioned bool pledge; // true if pledge action bool stampT0Np; // true if loan's t0 neutral price should be restamped (when drawing debt or pledge settles auction) } @@ -59,7 +58,6 @@ library BorrowerActions { struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP (NFTs only) - bool inAuction; // true if loan still in auction after repay, false otherwise bool pull; // true if pull action bool repay; // true if repay action bool stampT0Np; // true if loan's t0 neutral price should be restamped (when repay settles auction or pull collateral) @@ -134,8 +132,8 @@ library BorrowerActions { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - vars.inAuction = _inAuction(auctions_, borrowerAddress_); + result_.inAuction = _inAuction(auctions_, borrowerAddress_); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; @@ -152,15 +150,15 @@ library BorrowerActions { // if loan is auctioned and becomes collateralized by newly pledged collateral then settle auction if ( - vars.inAuction && + result_.inAuction && _isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType) ) { - // borrower becomes collateralized - vars.inAuction = false; - vars.stampT0Np = true; // stamp borrower t0Np when exiting from auction + // stamp borrower t0Np when exiting from auction + vars.stampT0Np = true; - result_.settledAuction = true; - // remove debt from pool accumulator and settle auction + // borrower becomes re-collateralized, entire borrower debt is removed from pool auctions debt accumulator + result_.inAuction = false; + result_.settledAuction = true; result_.t0DebtInAuctionChange = borrower.t0Debt; // settle auction and update borrower's collateral with value after settlement @@ -175,10 +173,9 @@ library BorrowerActions { borrower.collateral, poolState_.poolType ); - - borrower.collateral = result_.remainingCollateral; - result_.poolCollateral -= vars.compensatedCollateral; + + borrower.collateral = result_.remainingCollateral; } // add new amount of collateral to pledge to pool balance @@ -190,7 +187,7 @@ library BorrowerActions { if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); // an auctioned borrower in not allowed to draw more debt (even if collateralized at the new LUP) if auction is not settled - if (vars.inAuction) revert AuctionActive(); + if (result_.inAuction) revert AuctionActive(); vars.t0BorrowAmount = Maths.wdiv(amountToBorrow_, poolState_.inflator); @@ -238,7 +235,7 @@ library BorrowerActions { result_.poolDebt, poolState_.rate, result_.newLup, - vars.inAuction, + result_.inAuction, vars.stampT0Np ); @@ -290,8 +287,8 @@ library BorrowerActions { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - vars.inAuction = _inAuction(auctions_, borrowerAddress_); + result_.inAuction = _inAuction(auctions_, borrowerAddress_); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; @@ -328,14 +325,14 @@ library BorrowerActions { result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // if loan is auctioned and becomes collateralized by repaying debt then settle auction - if (vars.inAuction) { + if (result_.inAuction) { if (_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) { - // borrower becomes re-collateralized - vars.inAuction = false; - vars.stampT0Np = true; // stamp borrower t0Np when exiting from auction + // stamp borrower t0Np when exiting from auction + vars.stampT0Np = true; - result_.settledAuction = true; - // remove entire borrower debt from pool auctions debt accumulator + // borrower becomes re-collateralized, entire borrower debt is removed from pool auctions debt accumulator + result_.inAuction = false; + result_.settledAuction = true; result_.t0DebtInAuctionChange = borrower.t0Debt; // settle auction and update borrower's collateral with value after settlement @@ -350,10 +347,9 @@ library BorrowerActions { borrower.collateral, poolState_.poolType ); + result_.poolCollateral -= vars.compensatedCollateral; borrower.collateral = result_.remainingCollateral; - - result_.poolCollateral -= vars.compensatedCollateral; } else { // partial repay, remove only the paid debt from pool auctions debt accumulator result_.t0DebtInAuctionChange = vars.t0RepaidDebt; @@ -368,7 +364,7 @@ library BorrowerActions { if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); // an auctioned borrower in not allowed to pull collateral (even if collateralized at the new LUP) if auction is not settled - if (vars.inAuction) revert AuctionActive(); + if (result_.inAuction) revert AuctionActive(); // calculate LUP only if it wasn't calculated in repay action if (!vars.repay) result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); @@ -400,7 +396,7 @@ library BorrowerActions { result_.poolDebt, poolState_.rate, result_.newLup, - vars.inAuction, + result_.inAuction, vars.stampT0Np ); diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 82bda1f20..c3dea3b5e 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -63,6 +63,7 @@ library PoolCommons { int256 weightMau; int256 weightTu; uint256 newInterestRate; + uint256 nonAuctionedT0Debt; } /**************************/ @@ -96,56 +97,60 @@ library PoolCommons { vars.t0Debt2ToCollateral = interestParams_.t0Debt2ToCollateral; // calculate new interest params - vars.newDebt = poolState_.debt; + vars.nonAuctionedT0Debt = poolState_.t0Debt - poolState_.t0DebtInAuction; + vars.newDebt = Maths.wmul(vars.nonAuctionedT0Debt, poolState_.inflator); // new meaningful deposit cannot be less than pool's debt vars.newMeaningfulDeposit = Maths.max( _meaningfulDeposit( deposits_, - poolState_.t0Debt, + poolState_.t0DebtInAuction, + vars.nonAuctionedT0Debt, poolState_.inflator, vars.t0Debt2ToCollateral ), vars.newDebt ); vars.newDebtCol = Maths.wmul(poolState_.inflator, vars.t0Debt2ToCollateral); - vars.newLupt0Debt = Maths.wmul(lup_, poolState_.t0Debt); + vars.newLupt0Debt = Maths.wmul(lup_, vars.nonAuctionedT0Debt); // update EMAs only once per block if (vars.lastEmaUpdate != block.timestamp) { - // We do not need to calculate these during initialization, - // but the conditional to check each time would be more expensive thereafter. - vars.elapsed = int256(Maths.wdiv(block.timestamp - vars.lastEmaUpdate, 1 hours)); - vars.weightMau = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_MAU_HOURS, vars.elapsed)); - vars.weightTu = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_TU_HOURS, vars.elapsed)); - - // calculate the t0 debt EMA, used for MAU - vars.debtEma = vars.debtEma == 0 ? vars.newDebt : - uint256( + // first time EMAs are updated, initialize EMAs + if (vars.lastEmaUpdate == 0) { + vars.debtEma = vars.newDebt; + vars.depositEma = vars.newMeaningfulDeposit; + vars.debtColEma = vars.newDebtCol; + vars.lupt0DebtEma = vars.newLupt0Debt; + } else { + vars.elapsed = int256(Maths.wdiv(block.timestamp - vars.lastEmaUpdate, 1 hours)); + vars.weightMau = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_MAU_HOURS, vars.elapsed)); + vars.weightTu = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_TU_HOURS, vars.elapsed)); + + // calculate the t0 debt EMA, used for MAU + vars.debtEma = uint256( PRBMathSD59x18.mul(vars.weightMau, int256(vars.debtEma)) + PRBMathSD59x18.mul(1e18 - vars.weightMau, int256(interestParams_.debt)) ); - // update the meaningful deposit EMA, used for MAU - vars.depositEma = vars.depositEma == 0 ? vars.newMeaningfulDeposit : - uint256( + // update the meaningful deposit EMA, used for MAU + vars.depositEma = uint256( PRBMathSD59x18.mul(vars.weightMau, int256(vars.depositEma)) + PRBMathSD59x18.mul(1e18 - vars.weightMau, int256(interestParams_.meaningfulDeposit)) ); - // calculate the debt squared to collateral EMA, used for TU - vars.debtColEma = vars.debtColEma == 0 ? vars.newDebtCol : - uint256( + // calculate the debt squared to collateral EMA, used for TU + vars.debtColEma = uint256( PRBMathSD59x18.mul(vars.weightTu, int256(vars.debtColEma)) + PRBMathSD59x18.mul(1e18 - vars.weightTu, int256(interestParams_.debtCol)) ); - // calculate the EMA of LUP * t0 debt - vars.lupt0DebtEma = vars.lupt0DebtEma == 0 ? vars.newLupt0Debt : - uint256( + // calculate the EMA of LUP * t0 debt + vars.lupt0DebtEma = uint256( PRBMathSD59x18.mul(vars.weightTu, int256(vars.lupt0DebtEma)) + PRBMathSD59x18.mul(1e18 - vars.weightTu, int256(interestParams_.lupt0Debt)) ); + } // save EMAs in storage emaParams_.debtEma = vars.debtEma; @@ -335,13 +340,23 @@ library PoolCommons { } } + /** + * @notice Calculates pool's meaningful deposit. + * @param deposits_ Struct for pool deposits state. + * @param t0DebtInAuction_ Value of pool's t0 debt currently in auction. + * @param nonAuctionedT0Debt_ Value of pool's t0 debt that is not in auction. + * @param inflator_ Pool's current inflator. + * @param t0Debt2ToCollateral_ `t0Debt2ToCollateral` ratio. + * @return meaningfulDeposit_ Pool's meaningful deposit. + */ function _meaningfulDeposit( DepositsState storage deposits_, - uint256 t0Debt_, + uint256 t0DebtInAuction_, + uint256 nonAuctionedT0Debt_, uint256 inflator_, uint256 t0Debt2ToCollateral_ ) internal view returns (uint256 meaningfulDeposit_) { - uint256 dwatp = _dwatp(t0Debt_, inflator_, t0Debt2ToCollateral_); + uint256 dwatp = _dwatp(nonAuctionedT0Debt_, inflator_, t0Debt2ToCollateral_); if (dwatp == 0) { meaningfulDeposit_ = Deposits.treeSum(deposits_); } else { @@ -349,6 +364,7 @@ library PoolCommons { else if (dwatp >= MIN_PRICE) meaningfulDeposit_ = Deposits.prefixSum(deposits_, _indexOf(dwatp)); else meaningfulDeposit_ = Deposits.treeSum(deposits_); } + meaningfulDeposit_ -= Maths.min(meaningfulDeposit_, t0DebtInAuction_); } /**********************/ diff --git a/src/libraries/external/SettlerActions.sol b/src/libraries/external/SettlerActions.sol index 2075d668d..0393ea8b0 100644 --- a/src/libraries/external/SettlerActions.sol +++ b/src/libraries/external/SettlerActions.sol @@ -466,13 +466,13 @@ library SettlerActions { Bucket storage hpbBucket = buckets_[index]; if (hpbBucket.collateral == 0) { // existing LP for the bucket shall become unclaimable - hpbBucket.lps = 0; - hpbBucket.bankruptcyTime = block.timestamp; - emit BucketBankruptcy( index, hpbBucket.lps ); + + hpbBucket.lps = 0; + hpbBucket.bankruptcyTime = block.timestamp; } } diff --git a/src/libraries/external/TakerActions.sol b/src/libraries/external/TakerActions.sol index eb59d6705..44b5ed1fb 100644 --- a/src/libraries/external/TakerActions.sol +++ b/src/libraries/external/TakerActions.sol @@ -18,7 +18,6 @@ import { ReserveAuctionState } from '../../interfaces/pool/commons/IPoolState.sol'; import { - BucketTakeResult, TakeResult } from '../../interfaces/pool/commons/IPoolInternals.sol'; @@ -128,7 +127,7 @@ library TakerActions { * @notice Performs bucket take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). * @dev === Reverts on === * @dev not enough collateral to take `InsufficientCollateral()` - * @return result_ `BucketTakeResult` struct containing details of bucket take result. + * @return result_ `TakeResult` struct containing details of bucket take result. */ function bucketTake( AuctionsState storage auctions_, @@ -140,7 +139,7 @@ library TakerActions { bool depositTake_, uint256 index_, uint256 collateralScale_ - ) external returns (BucketTakeResult memory result_) { + ) external returns (TakeResult memory result_) { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; if (borrower.collateral == 0) revert InsufficientCollateral(); // revert if borrower's collateral is 0 diff --git a/src/libraries/helpers/RevertsHelper.sol b/src/libraries/helpers/RevertsHelper.sol index fa04283bf..e51b2ba70 100644 --- a/src/libraries/helpers/RevertsHelper.sol +++ b/src/libraries/helpers/RevertsHelper.sol @@ -27,19 +27,19 @@ import { Maths } from '../internal/Maths.sol'; /** * @notice Called by `LP` removal functions assess whether or not `LP` is locked. * @dev Reverts with `RemoveDepositLockedByAuctionDebt` if debt locked. - * @param index_ The deposit index from which `LP` is attempting to be removed. - * @param inflator_ The pool inflator used to properly assess t0 debt in auctions. + * @param t0DebtInAuction_ Pool's t0 debt currently in auction. + * @param index_ The deposit index from which `LP` is attempting to be removed. + * @param inflator_ The pool inflator used to properly assess t0 debt in auctions. */ function _revertIfAuctionDebtLocked( DepositsState storage deposits_, - PoolBalancesState storage poolBalances_, + uint256 t0DebtInAuction_, uint256 index_, uint256 inflator_ ) view { - uint256 t0AuctionDebt = poolBalances_.t0DebtInAuction; - if (t0AuctionDebt != 0 ) { + if (t0DebtInAuction_ != 0 ) { // deposit in buckets within liquidation debt from the top-of-book down are frozen. - if (index_ <= Deposits.findIndexOfSum(deposits_, Maths.wmul(t0AuctionDebt, inflator_))) revert RemoveDepositLockedByAuctionDebt(); + if (index_ <= Deposits.findIndexOfSum(deposits_, Maths.wmul(t0DebtInAuction_, inflator_))) revert RemoveDepositLockedByAuctionDebt(); } } diff --git a/tests/INVARIANTS.md b/tests/INVARIANTS.md index 652ffa667..de5573e49 100644 --- a/tests/INVARIANTS.md +++ b/tests/INVARIANTS.md @@ -42,7 +42,7 @@ - **I1**: interest rate (`InterestState.interestRate`) cannot be updated more than once in a 12 hours period of time (`InterestState.interestRateUpdate`) - **I2**: reserve interest (`ReserveAuctionState.totalInterestEarned`) accrues only once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and only if there's debt in the pool (`PoolBalancesState.t0Debt != 0`) - **I3**: pool inflator (`InflatorState.inflator`) cannot be updated more than once per block (`block.timestamp - InflatorState.inflatorUpdate != 0`) and equals `1e18` if there's no debt in the pool (`PoolBalancesState.t0Debt != 0`) -- **I4**: for all borrowers where (`borrower.collateral != 0`) the sum of borrower debt squared divided by borrower collateral (`borrower.debt^2 / borrower.collateral`) should equal borrower collateralization accumulator (`t0Debt2ToCollateral`) +- **I4**: for all borrowers that are not auctioned and (`borrower.collateral != 0`) the sum of borrower debt squared divided by borrower collateral (`borrower.debt^2 / borrower.collateral`) should equal borrower collateralization accumulator (`t0Debt2ToCollateral`) ## Fenwick tree - **F1**: Value represented at index `i` (`Deposits.valueAt(i)`) is equal to the accumulation of scaled values incremented or decremented from index `i` diff --git a/tests/forge/invariants/base/BasicInvariants.t.sol b/tests/forge/invariants/base/BasicInvariants.t.sol index afbc23517..4b428a7af 100644 --- a/tests/forge/invariants/base/BasicInvariants.t.sol +++ b/tests/forge/invariants/base/BasicInvariants.t.sol @@ -239,11 +239,15 @@ abstract contract BasicInvariants is BaseInvariants { for (uint256 i = 0; i < actorCount; i++) { address borrower = IBaseHandler(_handler).actors(i); - (uint256 borrowerT0Debt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); - uint256 weight = borrowerCollateral != 0 ? borrowerT0Debt ** 2 / borrowerCollateral : 0; + (, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower); - manualDebt2ToCollateral += weight; + if (kickTime == 0) { + (uint256 borrowerT0Debt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower); + uint256 weight = borrowerCollateral != 0 ? borrowerT0Debt ** 2 / borrowerCollateral : 0; + + manualDebt2ToCollateral += weight; + } } (,,, uint256 t0Debt2ToCollateral) = _pool.debtInfo(); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol index 7637d31be..007ee6ca9 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestLiquidationERC20Pool.t.sol @@ -292,7 +292,7 @@ contract RegressionTestLiquidationERC20Pool is LiquidationERC20PoolInvariants { } // Test reverting with overflow error in dwatp calculation in _meaningfulDeposit in updateInterestState - function _test_regression_evm_revert_2() external { + function test_regression_evm_revert_2() external { _liquidationERC20PoolHandler.drawDebt(13141077791835967310451371165744721774, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); _liquidationERC20PoolHandler.kickAuction(3, 53758605435723729358784, 3, 0); _liquidationERC20PoolHandler.repayDebt(668608315443216098571064749198163965820, 18932325376258851353179065817321260901, 0); diff --git a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol index 73a2688e5..46aff4464 100644 --- a/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol +++ b/tests/forge/regression/ERC20Pool/RegressionTestReservesERC20Pool.t.sol @@ -435,7 +435,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { Test was reverting when redeemedLps = bucketLps but lenderlps < redeemedLps, this happens due to slight rounding error in deposit calculation from lps Fixed by updating redeemedLP calculations to `vars.redeemedLP = Maths.min(lender.lps, vars.redeemedLP)` */ - function _test_regression_evm_revert_2() external { + function test_regression_evm_revert_2() external { _reserveERC20PoolHandler.moveQuoteToken(1, 2, 115792089237316195423570985008687907853269984665640564039457584007913129639932, 158129467307529830729349478455, 0); _reserveERC20PoolHandler.removeQuoteToken(2999999999999999484865294266928579000517539849, 20462, 1576762402919713971836094859031, 0); _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); @@ -458,7 +458,7 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { Test was reverting with overflow in `(tu + mau102 - 1e18) ** 2)` calculation in _calculateInterestRate Fixed by updating `((tu + mau102 - 1e18) ** 2) / 1e18` to `(((tu + mau102 - 1e18) / 1e9) ** 2)` */ - function _test_regression_evm_revert_3() external { + function test_regression_evm_revert_3() external { _reserveERC20PoolHandler.drawDebt(1000011592650618236, 427626464706163901647666438633, 0); _reserveERC20PoolHandler.takeReserves(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1, 0); _reserveERC20PoolHandler.repayDebt(115792089237316195423570985008687907853269984665640564039457584007913129639934, 222282777921247831223488066461, 0); @@ -480,4 +480,26 @@ contract RegressionTestReserveERC20Pool is ReserveERC20PoolInvariants { _reserveERC20PoolHandler.transferLps(2999999999999999866089865785362154170272346882, 1201303985144971, 3000000000000000000069282600099281847535823208, 2516, 0); _reserveERC20PoolHandler.kickAuction(11585421328272707882885111971840751049901594256465502255118203311426017450, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 0); } + + // Test reverting with overflow error in updateInterestState, fixed with calculating t0Debt2ToCollateral only for loans not in auction + function test_regression_evm_revert_dwatp_calculation() external { + _reserveERC20PoolHandler.takeAuction(50273692421744377186963564518293270398423601950557187270240, 2548824763847568430445964332385997581867729, 2, 0); + _reserveERC20PoolHandler.drawDebt(115792089237316195423570985008687907853269984665640564039457584007913129639933, 2, 0); + _reserveERC20PoolHandler.bucketTake(2, 178384181005, false, 6392753955405727164548240817297990606823468910265481812293128708, 0); + _reserveERC20PoolHandler.removeQuoteToken(3782769546571580286491282490173426994, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 54687167, 0); + _reserveERC20PoolHandler.settleAuction(39578, 1, 6177214753589, 0); + _reserveERC20PoolHandler.repayDebt(1025386930210936368, 4259484542182884235320983165905627435543345198090251871456881749143615, 0); + _reserveERC20PoolHandler.addQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639932, 9515050199665708690603228437, 8324830839682368611784829482626590763014978434217970848520, 0); + _reserveERC20PoolHandler.pledgeCollateral(0, 61432102823246505619, 0); + _reserveERC20PoolHandler.kickReserveAuction(90632424972185451325878827725, 0); + _reserveERC20PoolHandler.moveQuoteToken(115792089237316195423570985008687907853269984665640564039457584007913129639933, 115792089237316195423570985008687907853269984665640564039457584007913129639935, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _reserveERC20PoolHandler.removeQuoteToken(662383544472518305626297967684, 417256349487250392122120746762550, 11019, 0); + _reserveERC20PoolHandler.settleAuction(818097782230726263131224806387432801887947892950801, 3, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 0); + _reserveERC20PoolHandler.settleAuction(1045174129353233337547, 14962, 490775706239416317157126137000, 0); + _reserveERC20PoolHandler.removeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639934, 0, 115792089237316195423570985008687907853269984665640564039457584007913129639933, 0); + _reserveERC20PoolHandler.removeQuoteToken(7138, 27260, 5841, 0); + _reserveERC20PoolHandler.bucketTake(130086595647608623733586123, 0, false, 2, 0); + _reserveERC20PoolHandler.pledgeCollateral(115792089237316195423570985008687907853269984665640564039457584007913129639933, 1, 0); + _reserveERC20PoolHandler.settleAuction(877519612062733, 115792089237316195423570985008687907853269984665640564039457584007913129639934, 3, 0); + } } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol index 42f74eaa1..3231a5bf5 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol @@ -329,7 +329,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { newLup: 2_981.007422784467321543 * 1e18 }); - uint256 expectedDebt = 21_051.890446235135648008 * 1e18; + uint256 expectedDebt = 21_046.123595032677924433 * 1e18; _assertPool( PoolParams({ @@ -337,14 +337,14 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { lup: 2_981.007422784467321543 * 1e18, poolSize: 50_000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.062005376213123432 * 1e18, + encumberedCollateral: 7.060070845235984474 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.420404166896771338 * 1e18, - targetUtilization: 0.141027440233999772 * 1e18, - minDebtAmount: 2_105.189044623513564801 * 1e18, + actualUtilization: 0.000000000000000000 * 1e18, + targetUtilization: 1.000000000000000000 * 1e18, + minDebtAmount: 2_104.612359503267792443 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.055 * 1e18, + interestRate: 0.045 * 1e18, interestRateUpdate: _startTime + 10 days }) ); @@ -353,7 +353,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 441.424038461538461742 * 1e18, - borrowerCollateralization: 7.080141877038845214 * 1e18 + borrowerCollateralization: 7.082081907682151400 * 1e18 }); skip(10 days); @@ -364,22 +364,22 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { amount: 10 * 1e18 }); - expectedDebt = 21_083.636385101213387311 * 1e18; + expectedDebt = 21_072.086872169016071672 * 1e18; _assertPool( PoolParams({ - htp: 351.393939751686889789 * 1e18, + htp: 351.201447869483601195 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_055.509494601600650000 * 1e18, + poolSize: 50_044.110379805202100000 * 1e18, pledgedCollateral: 60 * 1e18, - encumberedCollateral: 7.072654775682389039 * 1e18, + encumberedCollateral: 7.068780409975037237 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.420403846154152038 * 1e18, + actualUtilization: 0.420403445225801443 * 1e18, targetUtilization: 0.141027440233999772 * 1e18, - minDebtAmount: 2_108.363638510121338731 * 1e18, + minDebtAmount: 2_107.208687216901607167 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.0605 * 1e18, + interestRate: 0.0495 * 1e18, interestRateUpdate: _startTime + 10 days + 10 days }) ); @@ -388,9 +388,9 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 60 * 1e18, borrowert0Np: 441.424038461538461742 * 1e18, - borrowerCollateralization: 8.483377444958217435 * 1e18 + borrowerCollateralization: 8.488027144729466085 * 1e18 }); - _assertLenderInterest(liquidityAdded, 55.509494601600650000 * 1e18); + _assertLenderInterest(liquidityAdded, 44.110379805202100000 * 1e18); skip(10 days); @@ -402,22 +402,22 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { collateralToPull: 10 * 1e18 }); - expectedDebt = 21_118.612213260575680078 * 1e18; + expectedDebt = 21_100.683472334824303370 * 1e18; _assertPool( PoolParams({ - htp: 422.372244265211513602 * 1e18, + htp: 422.013669446696486067 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_086.111097974181050000 * 1e18, + poolSize: 50_069.130567554535450000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.084387664333398317 * 1e18, + encumberedCollateral: 7.078373341528054648 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421205109283081156 * 1e18, - targetUtilization: 0.121072519391563216 * 1e18, - minDebtAmount: 2_111.861221326057568008 * 1e18, + actualUtilization: 0.421070265420802644 * 1e18, + targetUtilization: 0.120628314442263426 * 1e18, + minDebtAmount: 2_110.068347233482430337 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.06655 * 1e18, + interestRate: 0.05445 * 1e18, interestRateUpdate: _startTime + 30 days }) ); @@ -425,10 +425,10 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, - borrowert0Np: 445.838278846153846359 * 1e18, - borrowerCollateralization: 7.057773002983275247 * 1e18 + borrowert0Np: 441.213836538461538664 * 1e18, + borrowerCollateralization: 7.063769822178689107 * 1e18 }); - _assertLenderInterest(liquidityAdded, 86.111097974181050000 * 1e18); + _assertLenderInterest(liquidityAdded, 69.130567554535450000 * 1e18); skip(10 days); @@ -437,22 +437,22 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { emit LoanStamped(_borrower); _pool.stampLoan(); - expectedDebt = 21_157.152643010853304038 * 1e18; + expectedDebt = 21_132.184557783880298440 * 1e18; _assertPool( PoolParams({ - htp: 423.143052860217066081 * 1e18, + htp: 422.643691155677605969 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_119.833720983122450000 * 1e18, + poolSize: 50_096.693504733499050000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.097316323771045135 * 1e18, + encumberedCollateral: 7.088940603188755848 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421646075713482024 * 1e18, - targetUtilization: 0.138842620340978204 * 1e18, - minDebtAmount: 2_115.715264301085330404 * 1e18, + actualUtilization: 0.421430993826609139 * 1e18, + targetUtilization: 0.138725200998853604 * 1e18, + minDebtAmount: 2_113.218455778388029844 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.073205 * 1e18, + interestRate: 0.059895 * 1e18, interestRateUpdate: _startTime + 40 days }) ); @@ -460,31 +460,31 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.044916376706357984 * 1e18 + borrowert0Np: 443.294835576923077127 * 1e18, + borrowerCollateralization: 7.053240081812639175 * 1e18 }); - _assertLenderInterest(liquidityAdded, 119.833720983122450000 * 1e18); + _assertLenderInterest(liquidityAdded, 96.693504733499050000 * 1e18); skip(10 days); _updateInterest(); - expectedDebt = 21_199.628356897284442294 * 1e18; + expectedDebt = 21_166.890071570436649845 * 1e18; _assertPool( PoolParams({ - htp: 423.992567137945688846 * 1e18, + htp: 423.337801431408732997 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_157.001040562732600000 * 1e18, + poolSize: 50_127.061165904636850000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.111565102073903530 * 1e18, + encumberedCollateral: 7.100582812973708010 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.422131341009898326 * 1e18, - targetUtilization: 0.141517980300850494 * 1e18, - minDebtAmount: 2_119.962835689728444229 * 1e18, + actualUtilization: 0.421827930356991768 * 1e18, + targetUtilization: 0.141358334848097873 * 1e18, + minDebtAmount: 2_116.689007157043664985 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.0805255 * 1e18, + interestRate: 0.0658845 * 1e18, interestRateUpdate: _startTime + 50 days }) ); @@ -492,29 +492,29 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.030801136225104190 * 1e18 + borrowert0Np: 443.294835576923077127 * 1e18, + borrowerCollateralization: 7.041675495797803839 * 1e18 }); - _assertLenderInterest(liquidityAdded, 157.001040562732600000 * 1e18); + _assertLenderInterest(liquidityAdded, 127.061165904636850000 * 1e18); skip(10 days); - expectedDebt = 21_246.450141935843866714 * 1e18; + expectedDebt = 21_205.131971958652447704 * 1e18; _assertPool( PoolParams({ - htp: 423.992567137945688846 * 1e18, + htp: 423.337801431408732997 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_157.001040562732600000 * 1e18, + poolSize: 50_127.061165904636850000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.127271800648583574 * 1e18, + encumberedCollateral: 7.113411328627820409 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.422131341009898326 * 1e18, - targetUtilization: 0.141517980300850494 * 1e18, - minDebtAmount: 2_124.645014193584386671 * 1e18, + actualUtilization: 0.421827930356991768 * 1e18, + targetUtilization: 0.141358334848097873 * 1e18, + minDebtAmount: 2_120.513197195865244770 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.0805255 * 1e18, + interestRate: 0.0658845 * 1e18, interestRateUpdate: _startTime + 50 days }) ); @@ -523,8 +523,8 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, - borrowert0Np: 448.381722115384615591 * 1e18, - borrowerCollateralization: 7.015307034516347067 * 1e18 + borrowert0Np: 443.294835576923077127 * 1e18, + borrowerCollateralization: 7.028976350457301320 * 1e18 }); } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol index 05f74af08..49528fa4c 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolCollateral.t.sol @@ -131,8 +131,8 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { pledgedCollateral: 50 * 1e18, encumberedCollateral: 7.061038044473493202 * 1e18, poolDebt: 21_049.0068231390029184310 * 1e18, - actualUtilization: 0.701634006858188002 * 1e18, - targetUtilization: 0.141220760889469864 * 1e18, + actualUtilization: 0.700672854184962757 * 1e18, + targetUtilization: 0.070513720116999886 * 1e18, minDebtAmount: 2_104.900682313900291843 * 1e18, loans: 1, maxBorrower: _borrower, @@ -167,8 +167,8 @@ contract ERC20PoolCollateralTest is ERC20HelperContract { pledgedCollateral: 7.061038044473493202 * 1e18, encumberedCollateral: 7.061038044473493202 * 1e18, poolDebt: 21_049.0068231390029184310 * 1e18, - actualUtilization: 0.701634006858188002 * 1e18, - targetUtilization: 0.141220760889469864 * 1e18, + actualUtilization: 0.700672854184962757 * 1e18, + targetUtilization: 0.070513720116999886 * 1e18, minDebtAmount: 2_104.900682313900291843 * 1e18, loans: 1, maxBorrower: _borrower, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index e787b6f2a..cc346b17d 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -82,7 +82,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { changePrank(_borrower); vm.expectEmit(true, true, false, true); - emit UpdateInterestRate(0.05 * 1e18, 0.055 * 1e18); + emit UpdateInterestRate(0.05 * 1e18, 0.045 * 1e18); _drawDebtNoLupCheck({ from: _borrower, borrower: _borrower, @@ -97,21 +97,21 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { lup: 2_981.007422784467321543 * 1e18, poolSize: 110_000 * 1e18, pledgedCollateral: 100 * 1e18, - encumberedCollateral: 15.469154633609698947 * 1e18, - poolDebt: 46_113.664786991249514684 * 1e18, - actualUtilization: 0.418584278986712558 * 1e18, - targetUtilization: 0.154458625018190226 * 1e18, - minDebtAmount: 4_611.366478699124951468 * 1e18, + encumberedCollateral: 15.464917089564537419 * 1e18, + poolDebt: 46_101.032636738246882092 * 1e18, + actualUtilization: 0.000000000000000000 * 1e18, + targetUtilization: 1.000000000000000000 * 1e18, + minDebtAmount: 4_610.103263673824688209 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.055 * 1e18, + interestRate: 0.045 * 1e18, interestRateUpdate: _startTime + 10 days }) ); _assertEMAs({ - debtColEma: 21_200_711.871301775167480082 * 1e18, - lupt0DebtEma: 137_258_193.699477886755027636 * 1e18, - debtEma: 46_044.230769230769252000 * 1e18, + debtColEma: 0, + lupt0DebtEma: 0, + debtEma: 0, depositEma: 109_999.904632568359400000 * 1e18 }); @@ -123,25 +123,25 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 461.177183352194672960 * 1e18, + htp: 461.043482411861952490 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 110_064.287293030035050000 * 1e18, + poolSize: 110_051.099851162112050000 * 1e18, pledgedCollateral: 100 * 1e18, - encumberedCollateral: 15.470514425000097931 * 1e18, - poolDebt: 46_117.718335219467295955 * 1e18, - actualUtilization: 0.600105299829839683 * 1e18, + encumberedCollateral: 15.466029332500467905 * 1e18, + poolDebt: 46_104.348241186195248997 * 1e18, + actualUtilization: 0.332788778646025592 * 1e18, targetUtilization: 0.154458625018190226 * 1e18, - minDebtAmount: 4_611.771833521946729596 * 1e18, + minDebtAmount: 4_610.434824118619524900 * 1e18, loans: 1, maxBorrower: _borrower, - interestRate: 0.0605 * 1e18, - interestRateUpdate: _startTime + 10 days + 14 hours + interestRate: 0.045 * 1e18, + interestRateUpdate: _startTime + 10 days }) ); _assertEMAs({ - debtColEma: 21_200_711.871301775167480082 * 1e18, - lupt0DebtEma: 137_258_193.699477886755027636 * 1e18, - debtEma: 46_044.230769230769252000 * 1e18, + debtColEma: 2_313_024.841496349382919830 * 1e18, + lupt0DebtEma: 14_975_044.878354639842735067 * 1e18, + debtEma: 25_533.857684197937318785 * 1e18, depositEma: 76_726.919062848880206510 * 1e18 }); @@ -154,7 +154,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { from: _borrower, borrower: _borrower, amountToRepay: 46_200 * 1e18, - amountRepaid: 46_117.718335219467295955 * 1e18, + amountRepaid: 46_104.348241186195248997 * 1e18, collateralToPull: 0, newLup: MAX_PRICE }); @@ -163,24 +163,24 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 110_064.287293030035050000 * 1e18, + poolSize: 110_051.099851162112050000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 0.600105299829839683 * 1e18, + actualUtilization: 0.332788778646025592 * 1e18, targetUtilization: 0.154458625018190226 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), - interestRate: 0.0495 * 1e18, + interestRate: 0.0405 * 1e18, interestRateUpdate: _startTime + 10 days + 14 hours }) ); _assertEMAs({ - debtColEma: 21_200_711.871301775167480082 * 1e18, - lupt0DebtEma: 137_258_193.699477886755027636 * 1e18, - debtEma: 46_044.230769230769252000 * 1e18, + debtColEma: 2_313_024.841496349382919830 * 1e18, + lupt0DebtEma: 14_975_044.878354639842735067 * 1e18, + debtEma: 25_533.857684197937318785 * 1e18, depositEma: 76_726.919062848880206510 * 1e18 }); } @@ -237,8 +237,8 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { pledgedCollateral: 1_500 * 1e18, encumberedCollateral: 9.927311124438159308 * 1e18, poolDebt: 996.030634410028283604 * 1e18, - actualUtilization: 0.996030634410028284 * 1e18, - targetUtilization: 0.006618207416292106 * 1e18, + actualUtilization: 0.525927743411473669 * 1e18, + targetUtilization: 0.006617716357476524 * 1e18, minDebtAmount: 99.603063441002828360 * 1e18, loans: 1, maxBorrower: _borrower, @@ -309,8 +309,8 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { pledgedCollateral: 10 * 1e18, encumberedCollateral: 8.324533534321699024 * 1e18, poolDebt: 5_005.150499094935086587 * 1e18, - actualUtilization: 0.500515049909493509 * 1e18, - targetUtilization: 0.832453353432169902 * 1e18, + actualUtilization: 0.250240384615384603 * 1e18, + targetUtilization: 0.832396338031799592 * 1e18, minDebtAmount: 500.515049909493508659 * 1e18, loans: 1, maxBorrower: _borrower, @@ -376,7 +376,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { encumberedCollateral: 0.000000006658700114 * 1e18, poolDebt: 0.000010023099759839 * 1e18, actualUtilization: 0.000000001002307157 * 1e18, - targetUtilization: 0.000665851626199244 * 1e18, + targetUtilization: 0.000665852030273875 * 1e18, minDebtAmount: 0.000001002309975984 * 1e18, loans: 1, maxBorrower: _borrower, @@ -653,9 +653,9 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { poolSize: 20_001.166301815699560000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 3.094069767562214045 * 1e18, - poolDebt: 1012.343273629329809307 * 1e18, - actualUtilization: 0.050617187817622486 * 1e18, - targetUtilization: 0.030943722563486265 * 1e18, + poolDebt: 1_012.343273629329809307 * 1e18, + actualUtilization: 0.050048053058282770 * 1e18, + targetUtilization: 0.030592832642066762 * 1e18, minDebtAmount: 50.617163681466490465 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -664,9 +664,9 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }) ); _assertEMAs({ - debtColEma: 10_235.360311264941798142 * 1e18, - lupt0DebtEma: 330_773.399686006553854142 * 1e18, - debtEma: 1_012.343273629329809307 * 1e18, + debtColEma: 8_636.472785378662688101 * 1e18, + lupt0DebtEma: 282_303.796004265917437005 * 1e18, + debtEma: 1_000.960583870227520994 * 1e18, depositEma: 19_999.990463256835940000 * 1e18 }); } @@ -800,8 +800,8 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { pledgedCollateral: 100 * 1e18, encumberedCollateral: 58.236654596124865142 * 1e18, poolDebt: 19_054.349122034189453676 * 1e18, - actualUtilization: 0.476358955196505217 * 1e18, - targetUtilization: 0.584010402015984926 * 1e18, + actualUtilization: 0.475456504053686315 * 1e18, + targetUtilization: 0.582873969285693044 * 1e18, minDebtAmount: 952.717456101709472684 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -810,9 +810,9 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }) ); _assertEMAs({ - debtColEma: 3635_946.432113250913630238 * 1e18, - lupt0DebtEma: 6225_824.779082841691329479 * 1e18, - debtEma: 19_054.349122034189453676 * 1e18, + debtColEma: 3_126_403.148307075893092949 * 1e18, + lupt0DebtEma: 5_363_772.124081052431303092 * 1e18, + debtEma: 19_018.251093534322898890 * 1e18, depositEma: 39_999.980926513671880000 * 1e18 }); @@ -834,12 +834,12 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 200.941998518054562716 * 1e18, lup: 327.188250324085203338 * 1e18, - poolSize: 40_045.121523671487080000 * 1e18, + poolSize: 40_045.119710562067080000 * 1e18, pledgedCollateral: 50_100 * 1e18, encumberedCollateral: 58.353196900686720280 * 1e18, poolDebt: 19_092.480394752519489737 * 1e18, - actualUtilization: 0.476094974846641824 * 1e18, - targetUtilization: 0.584010402015984926 * 1e18, + actualUtilization: 0.476094973986474284 * 1e18, + targetUtilization: 0.583872645876088990 * 1e18, minDebtAmount: 954.624019737625974487 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -848,9 +848,9 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }) ); _assertEMAs({ - debtColEma: 3_635_946.432113250913630238 * 1e18, - lupt0DebtEma: 6_225_824.779082841691329479 * 1e18, - debtEma: 19_054.349122034189453676 * 1e18, + debtColEma: 3_565_623.757561586266590548 * 1e18, + lupt0DebtEma: 6_106_851.867005073649467074 * 1e18, + debtEma: 19_054.349087608426800461 * 1e18, depositEma: 40_022.159713346932216568 * 1e18 }); @@ -866,12 +866,12 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 200.969526704670605713 * 1e18, lup: 327.188250324085203338 * 1e18, - poolSize: 40_047.420826509653000000 * 1e18, + poolSize: 40_047.419013400059920000 * 1e18, pledgedCollateral: 50_100 * 1e18, encumberedCollateral: 58.370797186283932430 * 1e18, poolDebt: 19_098.239001402275530018 * 1e18, - actualUtilization: 0.476604459561723992 * 1e18, - targetUtilization: 0.584213148132606714 * 1e18, // big col. deposit barely affects + actualUtilization: 0.476604475533389646 * 1e18, + targetUtilization: 0.584103777552299209 * 1e18, // big col. deposit barely affects minDebtAmount: 954.911950070113776501 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -880,10 +880,10 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { }) ); _assertEMAs({ - debtColEma: 3_637_620.071316321624676289 * 1e18, // big col. deposit barely affects - lupt0DebtEma: 6_226_528.935447105141859984 * 1e18, - debtEma: 19_082.947576572936979769 * 1e18, - depositEma: 40_039.381071090348363568 * 1e18 + debtColEma: 3_579_931.895052572545685505 * 1e18, // big col. deposit barely affects + lupt0DebtEma: 6_128_931.249262523887589290 * 1e18, + debtEma: 19_082.947567966496316465 * 1e18, + depositEma: 40_039.379711258283363568 * 1e18 }); } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol index a9f03311a..7263f1277 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsArbTake.t.sol @@ -229,7 +229,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { amount: 1 * 1e18, amountAdded: 0.999876712328767123 * 1e18, index: _i9_52, - lpAward: 0.999873539225944871 * 1e18, + lpAward: 0.999873539529846322 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -237,11 +237,11 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_010.436714496623126000 * 1e18, - exchangeRate: 1.005218357248311563 * 1e18 + deposit: 2_010.436713885571218000 * 1e18, + exchangeRate: 1.005218356942785609 * 1e18 }); _assertReserveAuction({ - reserves: 24.540805142364598539 * 1e18, + reserves: 24.540827358578637023 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -282,32 +282,32 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { quoteTokenAmount: 14.503461444385064128 * 1e18, bondChange: 0.145034614443850641 * 1e18, isReward: true, - lpAwardTaker: 5.303234072524906823 * 1e18, - lpAwardKicker: 0.144281700983723506 * 1e18 + lpAwardTaker: 5.303234074136771189 * 1e18, + lpAwardKicker: 0.144281701027576470 * 1e18 }); _assertLenderLpBalance({ lender: _taker, index: _i9_91, - lpBalance: 5.303234072524906823 * 1e18, + lpBalance: 5.303234074136771189 * 1e18, depositTime: _startTime + 100 days + 6.5 hours }); _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.144281700983723506 * 1e18, // rewarded with LP in bucket + lpBalance: 2_000.144281701027576470 * 1e18, // rewarded with LP in bucket depositTime: _startTime + 100 days + 6.5 hours }); _assertBucket({ index: _i9_91, - lpBalance: 2_005.447515773508630329 * 1e18, + lpBalance: 2_005.447515775164347659 * 1e18, collateral: 2 * 1e18, - deposit: 1_996.078287666681912514 * 1e18, - exchangeRate: 1.005218357248311563 * 1e18 + deposit: 1_996.078287055630004514 * 1e18, + exchangeRate: 1.005218356942785609 * 1e18 }); // reserves should remain the same after arb take _assertReserveAuction({ - reserves: 25.925343323521952869 * 1e18, + reserves: 25.925365539735991347 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -423,7 +423,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 26.111530884129153303 * 1e18, + reserves: 26.111547973458754923 * 1e18, claimableReserves : 0, claimableReservesRemaining: 0, auctionPrice: 0, @@ -615,7 +615,7 @@ contract ERC20PoolLiquidationsArbTakeTest is ERC20HelperContract { index: _i10016, lpBalance: 3_562.597355112798042 * 1e18, // LP balance in arbed bucket increased with LP awarded for arb taker collateral: 0.257950403803869741 * 1e18, // arbed collateral added to the arbed bucket - deposit: 978.836725452666849367 * 1e18, // quote token amount is diminished in arbed bucket + deposit: 978.836725452666849368 * 1e18, // quote token amount is diminished in arbed bucket exchangeRate: 1 * 1e18 }); _assertAuction( diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol index f8bd86971..6b35a4ef7 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsDepositTake.t.sol @@ -229,7 +229,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { amount: 1 * 1e18, amountAdded: 0.999876712328767123 * 1e18, index: _i9_52, - lpAward: 0.999873479213875107 * 1e18, + lpAward: 0.999873479995558788 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -237,12 +237,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_026.352753018872710000 * 1e18, - exchangeRate: 1.013176376509436355 * 1e18 + deposit: 2_026.352751434705402000 * 1e18, + exchangeRate: 1.013176375717352701 * 1e18 }); _assertReserveAuction({ - reserves: 49.824792135962495179 * 1e18, - claimableReserves : 8.392655686771763037 * 1e18, + reserves: 49.824849391649864823 * 1e18, + claimableReserves : 8.392712942459132681 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -283,7 +283,7 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { bondChange: 0.198343696868718241 * 1e18, isReward: true, lpAwardTaker: 0, - lpAwardKicker: 0.195764233619466887 * 1e18 + lpAwardKicker: 0.195764233772511957 * 1e18 }); _assertLenderLpBalance({ @@ -295,20 +295,20 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { _assertLenderLpBalance({ lender: _lender, index: _i9_91, - lpBalance: 2_000.195764233619466887 * 1e18, + lpBalance: 2_000.195764233772511957 * 1e18, depositTime: _startTime + 250 days + 6.5 hours }); _assertBucket({ index: _i9_91, - lpBalance: 2_000.195764233619466887 * 1e18, + lpBalance: 2_000.195764233772511957 * 1e18, collateral: 2 * 1e18, - deposit: 2_006.716727028869604094 * 1e18, - exchangeRate: 1.013176376509436355 * 1e18 + deposit: 2_006.716725444702296094 * 1e18, + exchangeRate: 1.013176375717352701 * 1e18 }); // reserves should remain the same after deposit take _assertReserveAuction({ - reserves: 51.238074032610772537 * 1e18, - claimableReserves : 9.897051303886814538 * 1e18, + reserves: 51.238131288298142182 * 1e18, + claimableReserves : 9.897108559574184183 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -426,12 +426,12 @@ contract ERC20PoolLiquidationsDepositTakeTest is ERC20HelperContract { index: _i1505_26, lpBalance: 25_000 * 1e18, collateral: 0.014351542794629452 * 1e18, - deposit: 24_978.397143183672680231 * 1e18, + deposit: 24_978.397143183672680230 * 1e18, exchangeRate: 1 * 1e18 }); _assertReserveAuction({ - reserves: 51.428137480706929986 * 1e18, - claimableReserves : 10.097268213810519088 * 1e18, + reserves: 51.428181523373734113 * 1e18, + claimableReserves : 10.097312256477323215 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol index 83a526a71..f3c3acd56 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKick.t.sol @@ -183,8 +183,8 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 835.035237319063220561 * 1e18, poolDebt: 8_117.624599705640061721 * 1e18, - actualUtilization: 0.111200336982269042 * 1e18, - targetUtilization: 0.833449668459897038 * 1e18, + actualUtilization: 0.109684131322444679 * 1e18, + targetUtilization: 0.822075127292417292 * 1e18, minDebtAmount: 811.762459970564006172 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -723,8 +723,8 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.290450922889736704 * 1e18, poolDebt: 9_996.315708608352095626 * 1e18, - actualUtilization: 0.555338663776728288 * 1e18, - targetUtilization: 1.026216314846201384 * 1e18, + actualUtilization: 0.541033613782051282 * 1e18, + targetUtilization: 0.999781133426980224 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -740,7 +740,7 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { from: _lender1, amount: 1 * 1e18, index: _i9_91, - lpAward: 0.993688275531219296 * 1e18, + lpAward: 0.993688287401017551 * 1e18, newLup: 9.721295865031779605 * 1e18 }); @@ -748,12 +748,12 @@ contract ERC20PoolLiquidationsKickTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 9.721295865031779605 * 1e18, - poolSize: 73_115.811578712752097363 * 1e18, + poolSize: 73_115.810705342225439101 * 1e18, pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_028.364405977643667984 * 1e18, poolDebt: 9_997.034647576329686631 * 1e18, - actualUtilization: 0.737099854301327286 * 1e18, - targetUtilization: 1.026218700245164092 * 1e18, + actualUtilization: 0.100661311571554831 * 1e18, + targetUtilization: 0.999781133426980224 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol index 3930c3189..1aac9eb10 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsKickWithDeposit.t.sol @@ -1240,12 +1240,12 @@ contract ERC20PoolLiquidationsKickWithDepositTest is ERC20HelperContract { pledgedCollateral: 3_978.965725315792902720 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 0.804376804158714743 * 1e18, - targetUtilization: 203_119_829.249967715714383718 * 1e18, + actualUtilization: 0, + targetUtilization: 1 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), - interestRate: 0.055 * 1e18, + interestRate: 0.045 * 1e18, interestRateUpdate: _startTime + 80 hours }) ); diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol index 21bc32f71..4c36ed68a 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsMisc.t.sol @@ -213,8 +213,8 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { index: _i9_72, lpBalance: 8_007.361474606956967925 * 1e18, collateral: 0, - deposit: 8_008.357922841256875798 * 1e18, - exchangeRate: 1.000124441520151158 * 1e18 + deposit: 8_008.356713096000609696 * 1e18, + exchangeRate: 1.000124290441014778 * 1e18 }); _assertAuction( AuctionParams({ @@ -317,12 +317,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 7.989580407145861718 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_008.830844290339398722 * 1e18, + poolSize: 63_008.829558890303235305 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 821.863722498661263922 * 1e18, poolDebt: 7_989.580407145861717463 * 1e18, - actualUtilization: 0.131818845026319233 * 1e18, - targetUtilization: 0.822394462551326847 * 1e18, + actualUtilization: 0.121389299635877703 * 1e18, + targetUtilization: 0.822758145478171949 * 1e18, minDebtAmount: 798.958040714586171746 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -333,7 +333,7 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _removeLiquidity({ from: _lender, - amount: 8_008.370021911110254226 * 1e18, + amount: 8_008.368802555852473042 * 1e18, index: _i9_72, newLup: 9.624807173121239337 * 1e18, lpRedeem: 8_007.361474606956967925 * 1e18 @@ -349,18 +349,18 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { _removeLiquidity({ from: _lender, - amount: 25_000.037756489769875000 * 1e18, + amount: 25_000.037740139097750000 * 1e18, index: _i9_62, newLup: 9.529276179422528643 * 1e18, - lpRedeem: 24_999.999986356481162701 * 1e18 + lpRedeem: 25_000.000000000000000000 * 1e18 }); _assertBucket({ index: _i9_62, - lpBalance: 0.000013643518837299 * 1e18, + lpBalance: 0.000000000000000000 * 1e18, collateral: 0, - deposit: 0.000013643539450000 * 1e18, - exchangeRate: 1.000001510805331514 * 1e18 + deposit: 0.000000000000000000 * 1e18, + exchangeRate: 1.000000000000000000 * 1e18 }); _removeLiquidity({ @@ -368,15 +368,15 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { amount: 22_000 * 1e18, index: _i9_52, newLup: 9.529276179422528643 * 1e18, - lpRedeem: 21_999.966762332903438685 * 1e18 + lpRedeem: 21_999.966788727729901403 * 1e18 }); _assertBucket({ index: _i9_52, - lpBalance: 8_000.033237667096561315 * 1e18, + lpBalance: 8_000.033211272270098597 * 1e18, collateral: 0, - deposit: 8_000.045324159971190000 * 1e18, - exchangeRate: 1.000001510805332373 * 1e18 + deposit: 8_000.045288166917300000 * 1e18, + exchangeRate: 1.000001509605563910 * 1e18 }); _assertRemoveAllLiquidityLupBelowHtpRevert({ @@ -390,12 +390,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 7.989580407145861718 * 1e18, lup: 9.529276179422528643 * 1e18, - poolSize: 8_000.423065889459267791 * 1e18, + poolSize: 8_000.423016195353009887 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 838.521600516187410670 * 1e18, poolDebt: 7_990.503913730158190391 * 1e18, - actualUtilization: 0.131818845026319233 * 1e18, - targetUtilization: 0.822394462551326847 * 1e18, + actualUtilization: 0.121389299635877703 * 1e18, + targetUtilization: 0.822758145478171949 * 1e18, minDebtAmount: 799.050391373015819039 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -418,12 +418,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 7.990503913730158191 * 1e18, lup: 9.529276179422528643 * 1e18, - poolSize: 8_001.214422208222835222 * 1e18, + poolSize: 8_001.213845441074207570 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 838.521600516187410670 * 1e18, poolDebt: 7_990.503913730158190391 * 1e18, - actualUtilization: 0.390940447746469293 * 1e18, - targetUtilization: 0.825329194875392075 * 1e18, + actualUtilization: 0.383638005890774049 * 1e18, + targetUtilization: 0.829403289225492236 * 1e18, minDebtAmount: 799.050391373015819039 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -450,12 +450,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 0.000000099836282890 * 1e18, - poolSize: 8_002.338364349478802466 * 1e18, + poolSize: 8_002.290256803028931336 * 1e18, pledgedCollateral: 1_001.742368450520005091 * 1e18, encumberedCollateral: 81_714_595_700.439346767851204401 * 1e18, poolDebt: 8_158.081492591040321202 * 1e18, - actualUtilization: 0.998661389645009827 * 1e18, - targetUtilization: 0.838521600515025555 * 1e18, + actualUtilization: 0.998661461633472237 * 1e18, + targetUtilization: 0.838521600515840801 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -510,12 +510,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: 0.000000099836282890 * 1e18, - poolSize: 8_002.705599795829394966 * 1e18, + poolSize: 8_002.657492360039013361 * 1e18, pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 82_124_923_660.837160770168974387 * 1e18, poolDebt: 8_199.047110922993196875 * 1e18, - actualUtilization: 0.999255137826161539 * 1e18, - targetUtilization: 0.912833017390738664 * 1e18, + actualUtilization: 0.998661461633472237 * 1e18, + targetUtilization: 0.838521600515840801 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -548,12 +548,12 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 470.466606572217578962 * 1e18, + poolSize: 470.467842278147479905 * 1e18, pledgedCollateral: 1.742368450520005091 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 0.999255137826161539 * 1e18, - targetUtilization: 0.912833017390738664 * 1e18, + actualUtilization: 0.998661461633472237 * 1e18, + targetUtilization: 0.838521600515840801 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -592,10 +592,10 @@ contract ERC20PoolLiquidationsMiscTest is ERC20HelperContract { // TODO: review, strange values _assertBucket({ index: _i9_52, - lpBalance: 7_920.016955825447655160 * 1e18, + lpBalance: 7_920.016453607093220646 * 1e18, collateral: 0, - deposit: 470.466606572217578831 * 1e18, - exchangeRate: 0.059402222141225726 * 1e18 + deposit: 470.467842278147479912 * 1e18, + exchangeRate: 0.059402381931148331 * 1e18 }); } } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol index 962473981..265f4ee26 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsSettle.t.sol @@ -288,22 +288,22 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_012.736630048845584000 * 1e18, - exchangeRate: 1.006368315024422792 * 1e18 + deposit: 2_012.736560735960384000 * 1e18, + exchangeRate: 1.006368280367980192 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, collateral: 0, - deposit: 5_031.841575122113960000 * 1e18, - exchangeRate: 1.006368315024422792 * 1e18 + deposit: 5_031.841401839900960000 * 1e18, + exchangeRate: 1.006368280367980192 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 11_070.051465268650712000 * 1e18, - exchangeRate: 1.006368315024422792 * 1e18 + deposit: 11_070.051084047782112000 * 1e18, + exchangeRate: 1.006368280367980192 * 1e18 }); _assertBucket({ index: _i9_62, @@ -327,8 +327,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_91, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_012.736630048845584000 * 1e18, - exchangeRate: 1.006368315024422792 * 1e18 + deposit: 2_012.736560735960384000 * 1e18, + exchangeRate: 1.006368280367980192 * 1e18 }); _settle({ @@ -379,7 +379,7 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_807.556879218687263263 * 1e18, + deposit: 8_807.556879218687263265 * 1e18, exchangeRate: 0.800686989019880660 * 1e18 }); _assertBucket({ @@ -393,16 +393,16 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { PoolParams({ htp: 9.771304290202671377 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 63_807.556879218687263263 * 1e18, + poolSize: 63_807.556879218687263265 * 1e18, pledgedCollateral: 2 * 1e18, encumberedCollateral: 2.010288427770370775 * 1e18, poolDebt: 19.542608580405342754 * 1e18, - actualUtilization: 0.993531222480240308 * 1e18, - targetUtilization: 2.940996143103583563 * 1e18, + actualUtilization: 0.593847771807726236 * 1e18, + targetUtilization: 0.999790809254532429 * 1e18, minDebtAmount: 1.954260858040534275 * 1e18, loans: 1, maxBorrower: address(_borrower), - interestRate: 0.0495 * 1e18, + interestRate: 0.0405 * 1e18, interestRateUpdate: _startTime + 83 hours + 100 days }) ); @@ -566,23 +566,23 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { _assertBucket({ index: _i9_91, lpBalance: 2_000 * 1e18, - collateral: 202.986535499495858324 * 1e18, + collateral: 202.986484470302055714 * 1e18, deposit: 0, - exchangeRate: 1.006527496638583021 * 1e18 + exchangeRate: 1.006527243605609345 * 1e18 }); _assertBucket({ index: _i9_81, lpBalance: 5_000 * 1e18, - collateral: 512.553688794695752468 * 1e18, + collateral: 512.553559942792076265 * 1e18, deposit: 0, - exchangeRate: 1.006527496638583021 * 1e18 + exchangeRate: 1.006527243605609345 * 1e18 }); _assertBucket({ index: _i9_72, lpBalance: 11_000 * 1e18, - collateral: 284.459775705808389208 * 1e18, + collateral: 284.459955586905868021 * 1e18, deposit: 8_290.291604705064327150 * 1e18, - exchangeRate: 1.005055386003800627 * 1e18 + exchangeRate: 1.005055544974470547 * 1e18 }); _assertBucket({ index: _i9_62, @@ -719,14 +719,14 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { from: _lender1, amount: 100 * 1e18, index: _i9_91, - lpAward: 99.367198377636894880 * 1e18, + lpAward: 99.367201799558744045 * 1e18, newLup: 9.721295865031779605 * 1e18 }); _assertLenderLpBalance({ lender: _lender1, index: _i9_91, - lpBalance: 99.367198377636894880 * 1e18, + lpBalance: 99.367201799558744045 * 1e18, depositTime: _startTime + 100 days + 10 hours }); @@ -752,6 +752,11 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { assertTrue(block.timestamp - kickTime < 72 hours); // assert auction was kicked less than 72 hours ago + // LP forfeited when forgive bad debt should be reflected in BucketBankruptcy event + vm.expectEmit(true, true, false, true); + emit BucketBankruptcy(_i9_91, 2_099.367201799558744045 * 1e18); + vm.expectEmit(true, true, false, true); + emit BucketBankruptcy(_i9_81, 5_000 * 1e18); _settle({ from: _lender, borrower: _borrower2, @@ -867,8 +872,8 @@ contract ERC20PoolLiquidationsSettleTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0 * 1e18, - deposit: 9_036.878038705612120074 * 1e18, - exchangeRate: 0.821534367155055647 * 1e18 + deposit: 9_036.878037285796943111 * 1e18, + exchangeRate: 0.821534367025981540 * 1e18 }); _pool.moveQuoteToken(10000000000 * 1e18, _i9_72, _i9_91, type(uint256).max); @@ -950,7 +955,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { assertEq(collateral, 0); assertEq(deposit, 2); (uint256 borrowerDebt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(actor1); - assertEq(borrowerDebt, 987909.179343464530923023 * 1e18); + assertEq(borrowerDebt, 985_735.245058880968054979 * 1e18); assertEq(borrowerCollateral, 10066231386838.450530455239517417 * 1e18); changePrank(actor8); @@ -964,7 +969,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { assertEq(collateral, 0); // no collateral added in bucket 2571 assertEq(deposit, 0); // entire deposit from bucket 2571 used to settle (borrowerDebt, borrowerCollateral, ) = _pool.borrowerInfo(actor1); - assertEq(borrowerDebt, 987909.179343464530923022 * 1e18); // decreased with 1 + assertEq(borrowerDebt, 985_735.245058880968054978 * 1e18); // decreased with 1 assertEq(borrowerCollateral, 10066231386838.450530455239517417 * 1e18); // same as before settle } @@ -996,7 +1001,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { _kick({ from: actor4, borrower: actor2, - debt: 58_824_226_156.322179644993506974 * 1e18, + debt: 58_679_160_247.182050965227801655 * 1e18, collateral: 21_009_851.171858165566322122 * 1e18, bond: 580_263_636.560514719062821277 * 1e18, transferAmount: 580_263_636.560514719062821277 * 1e18 @@ -1007,7 +1012,7 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).updateInterest(); _kickReserveAuction({ from: actor1, - remainingReserves: 785_271_398.552730383160590325 * 1e18, + remainingReserves: 642_374_224.754246627157382301 * 1e18, price: 1_000_000_000 * 1e18, epoch: 1 }); @@ -1031,17 +1036,17 @@ contract ERC20PoolLiquidationsSettleRegressionTest is ERC20HelperContract { ERC20Pool(address(_pool)).updateInterest(); (uint256 borrowerDebt, , ) = _poolUtils.borrowerInfo(address(_pool), actor2); - assertEq(borrowerDebt, 60_623_994_637.102713529810847735 * 1e18); + assertEq(borrowerDebt, 60_144_029_463.415046012797744619 * 1e18); (uint256 reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); - assertEq(reserves, 294_122_188.473918671419077285 * 1e18); + assertEq(reserves, 467_743_845.048670767464288715 * 1e18); // settle auction with reserves _settle({ from: actor6, borrower: actor2, maxDepth: 2, - settledDebt: 57_234_480_301.052435683555399515 * 1e18 + settledDebt: 57_093_334_850.248360626382636508 * 1e18 }); (reserves, , , ,) = _poolUtils.poolReservesInfo(address(_pool)); diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 1a10ed2fb..074c5d833 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -314,8 +314,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.791200431324241706 * 1e18, poolDebt: 19_119.759164133922414841 * 1e18, - actualUtilization: 0.230343095389734878 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.225749991311399444 * 1e18, + targetUtilization: 0.963953858271529038 * 1e18, minDebtAmount: 1_911.975916413392241484 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -352,9 +352,9 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxCollateral: 1_001 * 1e18, - bondChange: 3_598.602058071496018124* 1e18, + bondChange: 3_598.602058071496018124 * 1e18, givenAmount: 12_005.655124053999200000 * 1e18, - collateralTaken: 1000.0 * 1e18, + collateralTaken: 1_000 * 1e18, isReward: true }); @@ -388,12 +388,12 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281629747 * 1e18, + poolSize: 83_220.780269640882398489 * 1e18, pledgedCollateral: 1_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, - actualUtilization: 0.284378049198767977 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.175461408132427911 * 1e18, + targetUtilization: 0.962794543732489691 * 1e18, minDebtAmount: 1_140.895445842993783802 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -438,12 +438,12 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.155043929439064212 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 83_220.780619576281629747 * 1e18, + poolSize: 83_220.780269640882398489 * 1e18, pledgedCollateral: 2_002.0 * 1e18, encumberedCollateral: 1_150.422689356386608344 * 1e18, poolDebt: 11_408.954458429937838015 * 1e18, - actualUtilization: 0.284378049198767977 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.175461408132427911 * 1e18, + targetUtilization: 0.962794543732489691 * 1e18, minDebtAmount: 570.447722921496891901 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -591,8 +591,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, - actualUtilization: 0.230343095389734878 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.225749991311399444 * 1e18, + targetUtilization: 0.963953858271529038 * 1e18, minDebtAmount: 1_911.965003339991149544 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -799,8 +799,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, - actualUtilization: 0.230343095389734878 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.225749991311399444 * 1e18, + targetUtilization: 0.963953858271529038 * 1e18, minDebtAmount: 1_911.965003339991149544 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -836,10 +836,10 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { _take({ from: _lender, borrower: _borrower2, - maxCollateral: 577.0 * 1e18, + maxCollateral: 577 * 1e18, bondChange: 4_445.845186520884185062 * 1e18, givenAmount: 14_963.715748248473943872 * 1e18, - collateralTaken: 577.000000000000000000 * 1e18, + collateralTaken: 577 * 1e18, isReward: true }); @@ -961,8 +961,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 1_002 * 1e18, encumberedCollateral: 1_025.107650389722106875 * 1e18, poolDebt: 9_965.374762946048672276 * 1e18, - actualUtilization: 0.553626243304705638 * 1e18, - targetUtilization: 1.023051673698045482 * 1e18, + actualUtilization: 0.539365344551282052 * 1e18, + targetUtilization: 0.996698234880015451 * 1e18, minDebtAmount: 996.537476294604867228 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -1009,12 +1009,12 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { PoolParams({ htp: 9.767239336407496599 * 1e18, lup: 9.721295865031779605 * 1e18, - poolSize: 73_113.913540853182360000 * 1e18, + poolSize: 73_113.913417169507608000 * 1e18, pledgedCollateral: 992.0 * 1e18, encumberedCollateral: 925.265940856763249327 * 1e18, poolDebt: 8_994.783964905591719091 * 1e18, - actualUtilization: 0.581968058078338251 * 1e18, - targetUtilization: 1.023051673698045482 * 1e18, + actualUtilization: 0.539426554958980321 * 1e18, + targetUtilization: 0.996698499658312393 * 1e18, minDebtAmount: 449.739198245279585955 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -1187,8 +1187,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { pledgedCollateral: 2_002.000000000000000000 * 1e18, encumberedCollateral: 1_966.779974486190376300 * 1e18, poolDebt: 19_119.650033399911495436 * 1e18, - actualUtilization: 0.230343095389734878 * 1e18, - targetUtilization: 0.984033535061047652 * 1e18, + actualUtilization: 0.225749991311399444 * 1e18, + targetUtilization: 0.963953858271529038 * 1e18, minDebtAmount: 1_911.965003339991149544 * 1e18, loans: 1, maxBorrower: address(_borrower), @@ -1461,8 +1461,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581185156 * 1e18, - claimableReserves : 797.714616029940013190 * 1e18, + reserves: 851.125605070547985156 * 1e18, + claimableReserves : 797.715239845906813190 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1509,8 +1509,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); // reserves should increase after take action _assertReserveAuction({ - reserves: 851.124981254581184803 * 1e18, - claimableReserves : 800.882859687599489602 * 1e18, + reserves: 851.125605070547984803 * 1e18, + claimableReserves : 800.883483503566289602 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1530,8 +1530,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: 3_696, lpBalance: 2_000 * 1e18, collateral: 0, - deposit: 2_012.736630048845584000 * 1e18, - exchangeRate: 1.006368315024422792 * 1e18 + deposit: 2_012.736560735960384000 * 1e18, + exchangeRate: 1.006368280367980192 * 1e18 }); _settle({ @@ -1599,7 +1599,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { index: _i9_72, lpBalance: 11_000 * 1e18, collateral: 0, - deposit: 8_936.865619773958012093 * 1e18, + deposit: 8_936.865619773958012095 * 1e18, exchangeRate: 0.812442329070359819 * 1e18 }); _assertLenderLpBalance({ @@ -1638,8 +1638,8 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { vm.revertTo(postTakeSnapshot); _assertReserveAuction({ - reserves: 851.124981254581184803 * 1e18, - claimableReserves : 800.882859687599489602 * 1e18, + reserves: 851.125605070547984803 * 1e18, + claimableReserves : 800.883483503566289602 * 1e18, claimableReservesRemaining: 0, auctionPrice: 0, timeRemaining: 0 @@ -1649,7 +1649,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 0, - settledDebt: 839.502103169454585516 * 1e18 + settledDebt: 839.502718466652592026 * 1e18 }); _assertReserveAuction({ reserves: 0, @@ -1664,7 +1664,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 1, - settledDebt: 1_985.250898829861493554 * 1e18 + settledDebt: 1_985.250830463506159498 * 1e18 }); _assertAuction( @@ -1678,14 +1678,14 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { kickMomp: 9.818751856078723036 * 1e18, totalBondEscrowed: 105.065056948053351817 * 1e18, auctionPrice: 0.653111452826113536 * 1e18, - debtInAuction: 7_165.027420616806659906 * 1e18, + debtInAuction: 7_165.026866113725059905 * 1e18, thresholdPrice: 0, neutralPrice: 10.449783245217816340 * 1e18 }) ); _assertBorrower({ borrower: _borrower2, - borrowerDebt: 7_165.027420616806659906 * 1e18, + borrowerDebt: 7_165.026866113725059905 * 1e18, borrowerCollateral: 0, borrowert0Np: 10.307611531622595991 * 1e18, borrowerCollateralization: 0 @@ -1701,7 +1701,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 5, - settledDebt: 7_067.182518844961267852 * 1e18 + settledDebt: 7_067.181971914118595398 * 1e18 }); _assertAuction( @@ -1878,7 +1878,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); } - function testTakeAfterSettleReverts() external { + function testTakeAfterSettleReverts() external tearDown { // Borrower draws debt _borrow({ from: _borrower2, @@ -1905,7 +1905,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { borrower: _borrower2, maxCollateral: 1_000 * 1e18, bondChange: 6.531114528261135360 * 1e18, - givenAmount: 653.111452826113536000 * 1e18, + givenAmount: 653.1114528261135360 * 1e18, collateralTaken: 1_000 * 1e18, isReward: true }); @@ -1915,7 +1915,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { from: _lender, borrower: _borrower2, maxDepth: 1, - settledDebt: 2_824.753001999316079070 * 1e18 + settledDebt: 2_824.753548930158751524 * 1e18 }); // Borrower draws more debt @@ -2085,7 +2085,7 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { (uint256 reservesAfterTake, , , , ) = _poolUtils.poolReservesInfo(address(_pool)); // reserves should only increase by 7% of the borrower debt on first take and settle auction - assertEq(reservesAfterTake, reservesBeforeTake + Maths.wmul(borrowerDebt, 0.07 * 1e18)); + assertEq(reservesAfterTake, reservesBeforeTake + Maths.floorWmul(borrowerDebt, 0.07 * 1e18)); } } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol index 648f13482..ebdef9e4a 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPurchaseQuote.t.sol @@ -236,7 +236,7 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { // bidder purchases all quote from the highest bucket uint256 amountToPurchase = 10_100 * 1e18; assertGt(_quote.balanceOf(address(_pool)), amountToPurchase); - uint256 amountWithInterest = 10_001.282942146414260000 * 1e18; + uint256 amountWithInterest = 10_001.272716138919720000 * 1e18; // adding extra collateral to account for interest accumulation uint256 collateralToPurchaseWith = Maths.wmul(Maths.wdiv(amountToPurchase, p2550), 1.01 * 1e18); assertEq(collateralToPurchaseWith, 3.388032491631335842 * 1e18); @@ -256,17 +256,17 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { amount: amountWithInterest, index: 2550, newLup: _priceAt(2552), - lpRedeem: 10_000.353515519837848818 * 1e18 + lpRedeem: 10_000.348352446860839169 * 1e18 }); // bidder withdraws unused collateral - uint256 expectedCollateral = 0.066448947605530167 * 1e18; + uint256 expectedCollateral = 0.066450628927305146 * 1e18; _removeAllCollateral({ from: _bidder, amount: expectedCollateral, index: 2550, - lpRedeem: 200.052013519410424905 * 1e18 + lpRedeem: 200.057176592387434554 * 1e18 }); _assertLenderLpBalance({ @@ -279,7 +279,7 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { skip(7200); // lender exchanges their LP for collateral - expectedCollateral = 1.992950126415483406 * 1e18; + expectedCollateral = 1.992949117622418417 * 1e18; _removeAllCollateral({ from: _lender, @@ -298,13 +298,13 @@ contract ERC20PoolPurchaseQuoteTokenTest is ERC20HelperContract { skip(3600); // lender1 exchanges their LP for collateral - expectedCollateral = 1.328633417610322269 * 1e18; + expectedCollateral = 1.328632745081612279 * 1e18; _removeAllCollateral({ from: _lender1, amount: expectedCollateral, index: 2550, - lpRedeem: 3_999.999999999999999063 * 1e18 + lpRedeem: 3_999.999999999999999139 * 1e18 }); _assertLenderLpBalance({ diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol index a5f99db9b..013736d79 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -734,7 +734,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { amount: withdrawal1, index: 1606, newLup: _priceAt(1663), - lpRedeem: 1_699.988264063119375503 * 1e18 + lpRedeem: 1_699.989134088091859893 * 1e18 }); // lender removes all quote token, including interest, from the bucket @@ -742,13 +742,13 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { assertGt(_priceAt(1606), _htp()); - uint256 expectedWithdrawal2 = 1_700.147864111729705749 * 1e18; + uint256 expectedWithdrawal2 = 1_700.138879728085771159 * 1e18; _removeAllLiquidity({ from: _lender, amount: expectedWithdrawal2, index: 1606, newLup: _priceAt(1663), - lpRedeem: 1_700.011735936880624497 * 1e18 + lpRedeem: 1_700.010865911908140107 * 1e18 }); assertEq(_quote.balanceOf(_lender), lenderBalanceBefore + withdrawal1 + expectedWithdrawal2); @@ -770,8 +770,8 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { index: 1663, lpBalance: 3_400 * 1e18, collateral: 0, - deposit: 3_400.272254470191528600 * 1e18, - exchangeRate: 1.000080074844173979 * 1e18 + deposit: 3_400.256025995910604600 * 1e18, + exchangeRate: 1.000075301763503119 * 1e18 }); _assertLenderLpBalance({ lender: _lender, @@ -1155,7 +1155,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { from: _lender1, amount: 1_000 * 1e18, index: 2873, - lpAward: 999.958129650486586454 * 1e18, + lpAward: 999.958177826584067212 * 1e18, newLup: 601.252968524772188572 * 1e18 }); @@ -1168,7 +1168,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { amountMoved: 2_499.691780821917807500 * 1e18, fromIndex: lupIndex, toIndex: 2954, - lpRedeemFrom: 2_499.815331532038898922 * 1e18, + lpRedeemFrom: 2_499.816688122962822235 * 1e18, lpAwardTo: 2_499.691780821917807500 * 1e18, newLup: _lup() }); @@ -1180,7 +1180,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { from: _lender1, amount: 9_000 * 1e18, index: 2873, - lpAward: 8_994.177960110671668131 * 1e18, + lpAward: 8_994.229791354853043265 * 1e18, newLup: 601.252968524772188572 * 1e18 }); @@ -1189,10 +1189,10 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { _removeAllLiquidity({ from: _lender, - amount: 5_003.525624026555345183 * 1e18, + amount: 5_003.495432642728075897 * 1e18, index: 2873, newLup: 601.252968524772188572 * 1e18, - lpRedeem: 5_000.281794392950113758 * 1e18 + lpRedeem: 5_000.280437802026190445 * 1e18 }); _removeAllLiquidity({ diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol index 285b0cf8c..30be7660b 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol @@ -136,7 +136,7 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { }); } - function testBorrowAndRepay() external { + function testBorrowAndRepay() external tearDown { // lender deposits 10000 Quote into 3 buckets _addInitialLiquidity({ @@ -275,11 +275,11 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { htp: 502.333658244714424687 * 1e18, lup: _priceAt(2550), poolSize: 30_003.498905447098680000 * 1e18, - pledgedCollateral: Maths.wad(3), + pledgedCollateral: 3 * 1e18, encumberedCollateral: 0.500516446164039921 * 1e18, - poolDebt: 1507.000974734143274062 * 1e18, - actualUtilization: 0.050233397762005623 * 1e18, - targetUtilization: 0.166838815388013307 * 1e18, + poolDebt: 1_507.000974734143274062 * 1e18, + actualUtilization: 0.100096122026423251 * 1e18, + targetUtilization: 0.332446840033426268 * 1e18, minDebtAmount: 150.700097473414327406 * 1e18, loans: 1, maxBorrower: _borrower, @@ -349,12 +349,12 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 30_005.083883677219590000 * 1e18, + poolSize: 30_005.088767154245370000 * 1e18, pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 0.050227507786735589 * 1e18, - targetUtilization: 0.166838815388013307 * 1e18, + actualUtilization: 0.050227555333959397 * 1e18, + targetUtilization: 0.202597018753257617 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -363,9 +363,9 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { }) ); _assertEMAs({ - debtColEma: 755_981.012555885345015825 * 1e18, - lupt0DebtEma: 4_531_205.827599034360703833 * 1e18, - debtEma: 1_507.000974734143274062 * 1e18, + debtColEma: 1_009_226.137898421530685238 * 1e18, + lupt0DebtEma: 4_981_446.144217726231751319 * 1e18, + debtEma: 1_507.002401317220586672 * 1e18, depositEma: 30_003.498902092092525534 * 1e18 }); // check bucket state after fully repay @@ -373,8 +373,8 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { index: 2550, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 10_001.694627892406530000 * 1e18, - exchangeRate: 1.000169462789240653 * 1e18 + deposit: 10_001.696255718081790000 * 1e18, + exchangeRate: 1.000169625571808179 * 1e18 }); // check borrower info after fully repay _assertBorrower({ diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol index ab7441a50..58334c1c0 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolCollateral.t.sol @@ -720,8 +720,8 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { pledgedCollateral: 2 * 1e18, encumberedCollateral: 5_992_754_428.551908353085520210 * 1e18, poolDebt: 598.294326419208615388 * 1e18, - actualUtilization: 2.990870666205084615 * 1e18, - targetUtilization: 2_995_775_262.887499112057200872 * 1e18, + actualUtilization: 0.750721153846153847 * 1e18, + targetUtilization: 0.328577182109433013 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), @@ -736,80 +736,80 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { index: 3060, lpBalance: 20 * 1e18, collateral: 0.0000000000000000000 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3061, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3062, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3063, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3064, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3065, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3066, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3067, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 66.832513309346669380 * 1e18, - exchangeRate: 3.341625665467333469 * 1e18 + deposit: 66.831193726562997920 * 1e18, + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3068, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 20.004183919163565300 * 1e18, - exchangeRate: 1.000209195958178265 * 1e18 + deposit: 20.003788944092390860 * 1e18, + exchangeRate: 1.000189447204619543 * 1e18 }); _assertBucket({ index: 3069, lpBalance: 20.0 * 1e18, collateral: 0.0 * 1e18, - deposit: 20.004183919163565300 * 1e18, - exchangeRate: 1.000209195958178265 * 1e18 + deposit: 20.003788944092390860 * 1e18, + exchangeRate: 1.000189447204619543 * 1e18 }); _assertBucket({ @@ -836,17 +836,17 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBucket({ index: 3060, lpBalance: 20.202020202020202020 * 1e18, - collateral: 0.285330970663516468 * 1e18, + collateral: 0.285325336911052408 * 1e18, deposit: 0, - exchangeRate: 3.341625665467333471 * 1e18 + exchangeRate: 3.341559686328149896 * 1e18 }); _assertBucket({ index: 3061, lpBalance: 20.202020202020202020 * 1e18, - collateral: 0.286757625516834048 * 1e18, + collateral: 0.286751963595607668 * 1e18, deposit: 0, - exchangeRate: 3.341625665467333471 * 1e18 + exchangeRate: 3.341559686328149900 * 1e18 }); _assertBucket({ @@ -868,8 +868,8 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { kickMomp: 0.000000099836282890 * 1e18, totalBondEscrowed: 5.907892673985352325 * 1e18, auctionPrice: 0.000004621809202112 * 1e18, - debtInAuction: 439.677389340513210329 * 1e18, - thresholdPrice: 385.776675964868418456 * 1e18, + debtInAuction: 439.681348088864224700 * 1e18, + thresholdPrice: 385.774399985858744599 * 1e18, neutralPrice: 310.164365384230997074 * 1e18 }) ); @@ -878,16 +878,16 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { PoolParams({ htp: 0, lup: 99836282890, - poolSize: 374.170934385060477538 * 1e18, - pledgedCollateral: 1.139719990175231268 * 1e18, - encumberedCollateral: 4403983968.683524073950025244 * 1e18, - poolDebt: 439.677389340513210329 * 1e18, - actualUtilization: 1.117110216223813782 * 1e18, - targetUtilization: 2_995_775_262.887499112057200872 * 1e18, + poolSize: 374.163546520999771310 * 1e18, + pledgedCollateral: 1.139736976079754220 * 1e18, + encumberedCollateral: 4404023621.084799631606156246 * 1e18, + poolDebt: 439.681348088864224700 * 1e18, + actualUtilization: 0.061025469820976144 * 1e18, + targetUtilization: 0.328577182109433013 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), - interestRate: 0.0605 * 1e18, + interestRate: 0.0495 * 1e18, interestRateUpdate: block.timestamp }) ); @@ -898,10 +898,10 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 439.677389340513210329 * 1e18, - borrowerCollateral: 1.139719990175231268 * 1e18, + borrowerDebt: 439.681348088864224700 * 1e18, + borrowerCollateral: 1.139736976079754220 * 1e18, borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000258792947 * 1e18, + borrowerCollateralization: 0.000000000258794474 * 1e18, tokenIds: borrowerTokenIds }); @@ -924,10 +924,10 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBorrower({ borrower: _borrower, - borrowerDebt: 439.677384764922100239 * 1e18, - borrowerCollateral: 0.139719990175231268 * 1e18, + borrowerDebt: 439.681343513273114610 * 1e18, + borrowerCollateral: 0.139736976079754220 * 1e18, borrowert0Np: 78.825721153846153882 * 1e18, - borrowerCollateralization: 0.000000000031725817 * 1e18, + borrowerCollateralization: 0.000000000031729389 * 1e18, tokenIds: borrowerTokenIds }); @@ -955,9 +955,9 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { kickTime: block.timestamp - (32 hours + 4210 minutes), kickMomp: 0.000000099836282890 * 1e18, totalBondEscrowed: 5.907892720203444346 * 1e18, - auctionPrice: 0 * 1e18, - debtInAuction: 439.677384764922100239 * 1e18, - thresholdPrice: 3_148.371989379999109069 * 1e18, + auctionPrice: 0, + debtInAuction: 439.681343513273114610 * 1e18, + thresholdPrice: 3_147.740272854337762554 * 1e18, neutralPrice: 310.164365384230997074 * 1e18 }) ); @@ -966,7 +966,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { from: _lender, borrower: _borrower, maxDepth: 10, - settledDebt: 111.717941412250938348 * 1e18 + settledDebt: 111.718947293453085984 * 1e18 }); _assertBorrower({ @@ -1002,9 +1002,9 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _assertBucket({ index: 3060, lpBalance: 20.202020202020202020 * 1e18, - collateral: 0.285330970663516468 * 1e18, + collateral: 0.285325336911052408 * 1e18, deposit: 0, - exchangeRate: 3.341625665467333471 * 1e18 + exchangeRate: 3.341559686328149896 * 1e18 }); _assertLenderLpBalance({ lender: _borrower, @@ -1163,16 +1163,16 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 50.000004575591109996 * 1e18, + poolSize: 50.000004575591110007 * 1e18, pledgedCollateral: 0, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 1.002468098960724093 * 1e18, - targetUtilization: 13_424_722_854.598244140780064966 * 1e18, + actualUtilization: 0.001552228747991919 * 1e18, + targetUtilization: 0.328577182109433013 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), - interestRate: 0.06655 * 1e18, + interestRate: 0.04455 * 1e18, interestRateUpdate: block.timestamp }) ); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol index f26eb1cba..e9e869fd8 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol @@ -77,8 +77,8 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { pledgedCollateral: 6 * 1e18, encumberedCollateral: 4.620028820788372636 * 1e18, // 6 / 1.3 = 4.62 poolDebt: 6_954.361808414458420694 * 1e18, - actualUtilization: 0.586829404159407881 * 1e18, // moving -> 6_947 / 10_000 (meaningful) = 0.7 - targetUtilization: 0.769969644230769231 * 1e18, + actualUtilization: 0.000000000000000000 * 1e18, // moving -> 6_947 / 10_000 (meaningful) = 0.7 + targetUtilization: 1.000000000000000000 * 1e18, minDebtAmount: 695.436180841445842069 * 1e18, // debt / 10; only one loan, so not enforced loans: 1, maxBorrower: address(_borrower), @@ -87,9 +87,9 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }) ); _assertEMAs({ - debtColEma: 8_059_788.606357480557372857 * 1e18, // 6_954^2 / 6 ~= 8_059_686 - lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // 1_505.26 * 6_954.04 ~= 10_467_638.25 - debtEma: 6_954.044264896858085302 * 1e18, // current debt with origination fee + debtColEma: 0.000000000000000000 * 1e18, // 6_954^2 / 6 ~= 8_059_686 + lupt0DebtEma: 0.000000000000000000 * 1e18, // 1_505.26 * 6_954.04 ~= 10_467_638.25 + debtEma: 0.000000000000000000 * 1e18, // current debt with origination fee // previous accumulator had updated to 15_000 before debt was drawn, but now 5_000 is no longer meaningful... depositEma: 11_850.197375262816985000 * 1e18 // ...so it is moving down toward 10_000 }); @@ -108,8 +108,8 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { pledgedCollateral: 6 * 1e18, encumberedCollateral: 4.620107931548236591 * 1e18, // small increase due to pending interest poolDebt: 6_954.480890971813258160 * 1e18, // small increase due to pending interest - actualUtilization: 0.586829404159407881 * 1e18, - targetUtilization: 0.769969644230769231 * 1e18, // debtColEma / lupt0DebtEma + actualUtilization: 0.000000000000000000 * 1e18, + targetUtilization: 1.000000000000000000 * 1e18, // debtColEma / lupt0DebtEma minDebtAmount: 695.448089097181325816 * 1e18, // small increase due to pending interest loans: 1, maxBorrower: address(_borrower), @@ -118,10 +118,10 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }) ); _assertEMAs({ - debtColEma: 8_059_788.606357480557372857 * 1e18, // unchanged from setup - lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // unchanged from setup - debtEma: 6_954.044264896858085302 * 1e18, // unchanged from setup - depositEma: 11_850.197375262816985000 * 1e18 // unchanged from setup + debtColEma: 0.000000000000000000 * 1e18, // unchanged from setup + lupt0DebtEma: 0.000000000000000000 * 1e18, // unchanged from setup + debtEma: 0.000000000000000000 * 1e18, // unchanged from setup + depositEma: 11_850.197375262816985000 * 1e18 // unchanged from setup }); // touch the pool, triggering an interest accrual - EMAs should update @@ -130,11 +130,11 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { PoolParams({ htp: 1_159.080148495302209694 * 1e18, lup: _p1505_26, - poolSize: 15_000.38784582038918 * 1e18, // first interest accrual + poolSize: 15_000.371132163711890000 * 1e18, // first interest accrual pledgedCollateral: 6 * 1e18, encumberedCollateral: 4.620107931548236591 * 1e18, poolDebt: 6_954.480890971813258160 * 1e18, // pending interest now equals current interest - actualUtilization: 0.601778294656389596 * 1e18, + actualUtilization: 0.095745083902338016 * 1e18, targetUtilization: 0.769969644230769231 * 1e18, minDebtAmount: 695.448089097181325816 * 1e18, loans: 1, @@ -144,10 +144,10 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }) ); _assertEMAs({ - debtColEma: 8_059_788.606357480557372857 * 1e18, // accumulator updated, no EMA change - lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // accumulator updated, no EMA change - debtEma: 6_954.044264896858085302 * 1e18, // accumulator updated, no EMA change - depositEma: 11_555.824340370334487364 * 1e18 // still moving toward 10_000 + debtColEma: 197_072.776194638866068935 * 1e18, // accumulator updated, EMA initialized + lupt0DebtEma: 255_948.760670328150445660 * 1e18, // accumulator updated, EMA initialized + debtEma: 1_106.413371029437537443 * 1e18, // accumulator updated, EMA initialized + depositEma: 11_555.824340370334487364 * 1e18 // still moving toward 10_000 }); (uint256 interestRate, ) = _pool.interestRateInfo(); assertEq(interestRate, 0.05 * 1e18); @@ -155,13 +155,13 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { skip(9 hours); // 12 hours since debt was drawn _pool.updateInterest(); _assertEMAs({ - debtColEma: 8_059_824.827133087800583978 * 1e18, // updated for interest accrual - lupt0DebtEma: 10_467_670.598117585349615039 * 1e18, // updated for interest accrual - debtEma: 6_954.221271554347056671 * 1e18, // updated for interest accrual - depositEma: 10_925.255918947232279645 * 1e18 // still moving toward 10_000 + debtColEma: 759_883.557390504783896613 * 1e18, // updated for interest accrual + lupt0DebtEma: 986_853.627682966275217023 * 1e18, // updated for interest accrual + debtEma: 3_477.199139105917836267 * 1e18, // updated for interest accrual + depositEma: 10_925.249143290274162648 * 1e18 // still moving toward 10_000 }); (interestRate, ) = _pool.interestRateInfo(); - assertEq(interestRate, 0.05 * 1e18); + assertEq(interestRate, 0.045 * 1e18); skip(6 hours); @@ -174,44 +174,44 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 4 hours, - mau: 0.552709628959737370 * 1e18, // dropping from 60% to 35% - tu: 0.769980648855063767 * 1e18, // still at 77% - rate: 0.05 * 1e18 + mau: 0.397622119994472546 * 1e18, // dropping from 60% to 35% + tu: 0.770035415811601449 * 1e18, // still at 77% + rate: 0.045 * 1e18 }); (, , , uint256 depositEma) = _pool.emasInfo(); - assertEq(depositEma, 12_582.630574533185444544 * 1e18); // now moving toward 20_000 + assertEq(depositEma, 12_582.608507963702724933 * 1e18); // now moving toward 20_000 _skipAndAccrue({ time: 20 hours, // 24 hours since liquidity was added - mau: 0.393730664447534871 * 1e18, // still dropping toward 35% - tu: 0.769999034610545182 * 1e18, // still at 77% - rate: 0.045 * 1e18 // first interest rate drop + mau: 0.358933852890687729 * 1e18, // still dropping toward 35% + tu: 0.770067458236015074 * 1e18, // still at 77% + rate: 0.0405 * 1e18 // dropping at 4.05% }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 17_664.401438069534311691 * 1e18); // still moving toward 20_000 + assertEq(depositEma, 17_664.344669688571758689 * 1e18); // still moving toward 20_000 _skipAndAccrue({ time: 2 days, // 3 days since liquidity was added - mau: 0.350326278385275702 * 1e18, // reached 35% - tu: 0.770061298755197770 * 1e18, // still at 77% - rate: 0.0405 * 1e18 // second interest rate drop + mau: 0.348388356770742011 * 1e18, // reached 35% + tu: 0.770135325994564531 * 1e18, // still at 77% + rate: 0.03645 * 1e18 // second interest rate drop }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 19_855.678232854988878203 * 1e18); // reached (sort of) 20_000 + assertEq(depositEma, 19_855.532581473734007629 * 1e18); // reached (sort of) 20_000 _assertPool( PoolParams({ - htp: 1_159.624091089473286060 * 1e18, + htp: 1_159.575642053959188547 * 1e18, lup: _p1505_26, - poolSize: 25_003.260972741349788790 * 1e18, // reflects additional 10_000 deposit + poolSize: 25_002.955913967460376246 * 1e18, // reflects additional 10_000 deposit pledgedCollateral: 6 * 1e18, - encumberedCollateral: 4.622276093514343199 * 1e18, - poolDebt: 6_957.744546536839716358 * 1e18, - actualUtilization: 0.350326278385275702 * 1e18, // dropped to 35% as expected - targetUtilization: 0.770061298755197770 * 1e18, - minDebtAmount: 695.774454653683971636 * 1e18, + encumberedCollateral: 4.622082975054377226 * 1e18, + poolDebt: 6_957.453852323755131280 * 1e18, + actualUtilization: 0.348388356770742011 * 1e18, // dropped to 35% as expected + targetUtilization: 0.770135325994564531 * 1e18, + minDebtAmount: 695.745385232375513128* 1e18, loans: 1, maxBorrower: address(_borrower), - interestRate: 0.0405 * 1e18, // dropped twice + interestRate: 0.03645 * 1e18, // dropped twice interestRateUpdate: _startTime + 98 hours }) ); @@ -234,45 +234,45 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 3 hours, - mau: 0.438034189478303512 * 1e18, // rising from 35% to 90% - tu: 0.783712586574747919 * 1e18, // increases as collateralization decreases - rate: 0.0405 * 1e18 + mau: 0.436398947261022405 * 1e18, // rising from 35% to 90% + tu: 0.794802131238362891 * 1e18, // increases as collateralization decreases + rate: 0.03645 * 1e18 }); (, , uint256 debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 8_707.751377089437009807 * 1e18); // increasing from 7_000 to 18_000 + assertEq(debtEma, 8_675.169506576032073392 * 1e18); // increasing from 7_000 to 18_000 _skipAndAccrue({ time: 9 hours, - mau: 0.625264252786034776 * 1e18, // still rising to 90% - tu: 0.817638199962595844 * 1e18, - rate: 0.0405 * 1e18 + mau: 0.624275651572709578 * 1e18, // still rising to 90% + tu: 0.846215553223533151 * 1e18, + rate: 0.03645 * 1e18 }); (, , debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 12_461.239878735709484526 * 1e18); // increasing from 7_000 to 18_000 + assertEq(debtEma, 12_441.391312587344397940 * 1e18); // increasing from 7_000 to 18_000 _skipAndAccrue({ time: 4 days, - mau: 0.897117712497350670 * 1e18, // reached 90% - tu: 0.947031347885781555 * 1e18, - rate: 0.0405 * 1e18 + mau: 0.897069303670436098 * 1e18, // reached 90% + tu: 0.966852816219664605 * 1e18, + rate: 0.032805 * 1e18 }); (, , debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 17_945.800561906185304271 * 1e18); // reached 18_000 + assertEq(debtEma, 17_944.480736533919717209 * 1e18); // reached 18_000 _assertPool( PoolParams({ - htp: 1_497.940412039697435044 * 1e18, + htp: 1_497.769957757345433425 * 1e18, lup: _p1505_26, - poolSize: 25_011.246566016078833928 * 1e18, + poolSize: 25_010.141517477798670592 * 1e18, pledgedCollateral: 12 * 1e18, // 6 additional NFTs deposited - encumberedCollateral: 11.941618338706780744 * 1e18, // all 12 NFTs are encumbered - poolDebt: 17_975.284944476369220528 * 1e18, // includes new debt - actualUtilization: 0.897117712497350670 * 1e18, - targetUtilization: 0.947031347885781555 * 1e18, - minDebtAmount: 1_797.528494447636922053 * 1e18, + encumberedCollateral: 11.940259472915000621 * 1e18, // all 12 NFTs are encumbered + poolDebt: 17_973.239493088145201104 * 1e18, // includes new debt + actualUtilization: 0.897069303670436098 * 1e18, + targetUtilization: 0.966852816219664605 * 1e18, + minDebtAmount: 1_797.323949308814520110 * 1e18, loans: 1, maxBorrower: address(_borrower), - interestRate: 0.0405 * 1e18, - interestRateUpdate: _startTime + 98 hours + interestRate: 0.032805 * 1e18, + interestRateUpdate: _startTime + 110 hours + 4 days }) ); } @@ -289,15 +289,15 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }); _skipAndAccrue({ time: 40 hours, // 2 days after liquidity was added - mau: 0.744857048522821158 * 1e18, // 7_647 / 10_000 ~= 76% - tu: 0.793326272355691526 * 1e18, // starting at 77% + mau: 0.677110233556701963 * 1e18, // 7_647 / 10_000 ~= 76% + tu: 0.847585126397651853 * 1e18, // starting at 77% rate: 0.05 * 1e18 }); _assertEMAs({ - debtColEma: 8_539_491.492000790693673965 * 1e18, // reflects newly drawn debt - lupt0DebtEma: 10_764_160.711133073306706753 * 1e18, // unchanged from setup - debtEma: 7_585.487807318324588356 * 1e18, // increasing toward 7_647 - depositEma: 10_183.816911394801808573 * 1e18 // decreasing toward 10_000 + debtColEma: 2_745_524.266553065584517923 * 1e18, // reflects newly drawn debt + lupt0DebtEma: 3_239_231.294940137090747973 * 1e18, // unchanged from setup + debtEma: 6_895.559233472655919795 * 1e18, // increasing toward 7_647 + depositEma: 10_183.805962068972523753 * 1e18 // decreasing toward 10_000 }); // bad actor comes along and deposits large amount for 5 minutes, and then withdraws @@ -310,52 +310,53 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _pool.updateInterest(); // not really needed, since removing liquidity will trigger rate update _removeAllLiquidity({ from: _attacker, - amount: 150_000.003089440922870341 * 1e18, + amount: 150_000.003062917635863985 * 1e18, index: _i1505_26, newLup: _p1505_26, - lpRedeem: 149_972.484368509876401577 * 1e18 + lpRedeem: 149_973.669906855426845472 * 1e18 }); + uint256 rateChangeTs = block.timestamp; _skipAndAccrue({ time: 12, // skip a single block - mau: 0.695753471133465072 * 1e18, // impacted, but not enough to cause rate change - tu: 0.793367939903626038 * 1e18, - rate: 0.05 * 1e18 // rate unchanged + mau: 0.632791692026653958 * 1e18, // impacted, enough to cause rate change + tu: 0.847585617691556722 * 1e18, + rate: 0.045 * 1e18 // rate changed }); _assertEMAs({ - debtColEma: 8_540_370.017841347311996670 * 1e18, - lupt0DebtEma: 10_764_702.716470726509705193 * 1e18, - debtEma: 7_585.843823429738778980 * 1e18, - depositEma: 10_903.062849361711208755 * 1e18 // still noticably impacted + debtColEma: 2_750_544.877504425497154098 * 1e18, + lupt0DebtEma: 3_245_152.843668674754668559 * 1e18, + debtEma: 6_899.360444842923621304 * 1e18, + depositEma: 10_903.051559899926973925 * 1e18 // still noticably impacted }); _skipAndAccrue({ time: 12 hours, - mau: 0.729141586574051710 * 1e18, // moving back toward 75% - tu: 0.798822457321421405 * 1e18, - rate: 0.05 * 1e18 + mau: 0.696306196911713144 * 1e18, // moving back toward 75% + tu: 0.847637823306888876 * 1e18, + rate: 0.045 * 1e18 }); _assertEMAs({ - debtColEma: 8_656_142.490618553816291562 * 1e18, - lupt0DebtEma: 10_836_128.117434222666947215 * 1e18, - debtEma: 7_621.315210378439120928 * 1e18, - depositEma: 10_452.448949164988281908 * 1e18 // moving down back to 10_000 + debtColEma: 3_412_160.847313164565839074 * 1e18, + lupt0DebtEma: 4_025_493.853024754985190842 * 1e18, + debtEma: 7_278.073513801178223507 * 1e18, + depositEma: 10_452.403764437541109495 * 1e18 // moving down back to 10_000 }); _assertPool( PoolParams({ - htp: 1_276.218508787651473374 * 1e18, + htp: 1_276.209765166823398404 * 1e18, lup: _p1505_26, - poolSize: 15_002.306593887595200000 * 1e18, + poolSize: 15_002.177276783057210000 * 1e18, pledgedCollateral: 6 * 1e18, - encumberedCollateral: 5.087022896986824909 * 1e18, - poolDebt: 7_657.311052725908840244 * 1e18, // 7_647 principal plus some interest - actualUtilization: 0.729141586574051710 * 1e18, - targetUtilization: 0.798822457321421405 * 1e18, - minDebtAmount: 765.731105272590884024 * 1e18, + encumberedCollateral: 5.086988044805126619 * 1e18, + poolDebt: 7_657.258591000940390422 * 1e18, // 7_647 principal plus some interest + actualUtilization: 0.696306196911713144 * 1e18, + targetUtilization: 0.847637823306888876 * 1e18, + minDebtAmount: 765.725859100094039042 * 1e18, loans: 1, maxBorrower: address(_borrower), - interestRate: 0.05 * 1e18, - interestRateUpdate: _startTime + interestRate: 0.045 * 1e18, + interestRateUpdate: rateChangeTs }) ); } diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol index 13f63d486..fdaffd2d5 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolInterest.t.sol @@ -154,7 +154,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.800750077529217167 * 1e18 }); - _assertLenderInterest(liquidityAdded, 10.004632566415800000 * 1e18); + _assertLenderInterest(liquidityAdded, 10.004595693661100000 * 1e18); // borrower borrows some additional quote after some time has passed skip(10 days); @@ -178,7 +178,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.500002057800446964 * 1e18 }); - _assertLenderInterest(liquidityAdded, 14.290569428855550000 * 1e18); + _assertLenderInterest(liquidityAdded, 14.290532556069200000 * 1e18); // mint additional quote to borrower to enable repayment deal(address(_quote), _borrower, 20_000 * 1e18); @@ -352,7 +352,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.197213816827790670 * 1e18 }); - _assertLenderInterest(liquidityAdded, 0.371927286666690000 * 1e18); + _assertLenderInterest(liquidityAdded, 0.365554305676230000 * 1e18); skip(4 hours); @@ -361,7 +361,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { from: _lender, amount: 1 * 1e18, index: 2550, - lpAward: 0.999978744441788346 * 1e18, + lpAward: 0.999979133938993539 * 1e18, newLup: 2995.912459898389633881 * 1e18 }); liquidityAdded += 1e18; @@ -372,7 +372,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { (poolDebt,,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); - _assertLenderInterest(liquidityAdded, 0.637680300600360000 * 1e18); + _assertLenderInterest(liquidityAdded, 0.625994892241440000 * 1e18); expectedBorrower1Debt = 8_008.240798551896146546 * 1e18; @@ -556,7 +556,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 2.404169939255701731 * 1e18 }); - _assertLenderInterest(liquidityAdded, 25.750896517609250000 * 1e18); + _assertLenderInterest(liquidityAdded, 25.779680075333500000 * 1e18); // borrower pulls some of their collateral after some time has passed skip(10 days); @@ -581,7 +581,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.801327695821111558 * 1e18 }); - _assertLenderInterest(liquidityAdded, 30.031168354510400000 * 1e18); + _assertLenderInterest(liquidityAdded, 30.059922094508300000 * 1e18); // borrower borrows some additional quote after some time has passed skip(10 days); @@ -605,7 +605,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.500545633513497515 * 1e18 }); - _assertLenderInterest(liquidityAdded, 33.887067044166600000 * 1e18); + _assertLenderInterest(liquidityAdded, 33.915820770203100000 * 1e18); // mint additional quote to borrower to enable repayment deal(address(_quote), _borrower, 20_000 * 1e18); @@ -633,6 +633,6 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1 * 1e18 }); - _assertLenderInterest(liquidityAdded, 38.052830597199800000 * 1e18); + _assertLenderInterest(liquidityAdded, 38.081584308182850000 * 1e18); } } diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol index 2bd1218dd..2cebb27e1 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsDepositTake.t.sol @@ -163,8 +163,8 @@ contract ERC721PoolLiquidationsDepositTakeTest is ERC721HelperContract { pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, - actualUtilization: 0.00055111720508951 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000477170706006322 * 1e18, + targetUtilization: 0.786051641950380194 * 1e18, minDebtAmount: 4.023155597153422423 * 1e18, loans: 1, maxBorrower: address(_borrower2), diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol index 299e19453..4ff5963c3 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsKick.t.sol @@ -183,8 +183,8 @@ contract ERC721PoolLiquidationsKickTest is ERC721HelperContract { pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, - actualUtilization: 0.00055111720508951 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000477170706006322 * 1e18, + targetUtilization: 0.786051641950380194 * 1e18, minDebtAmount: 4.023155597153422423 * 1e18, loans: 1, maxBorrower: address(_borrower2), diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol index 02164c30b..08b173dce 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettle.t.sol @@ -257,8 +257,8 @@ contract ERC721PoolLiquidationsSettleTest is ERC721HelperContract { pledgedCollateral: 1 * 1e18, encumberedCollateral: 0, poolDebt: 0, - actualUtilization: 0.389471335647237918 * 1e18, - targetUtilization: 0.656073460432016344 * 1e18, + actualUtilization: 0, + targetUtilization: 1 * 1e18, minDebtAmount: 0, loans: 0, maxBorrower: address(0), diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol index da90ec96b..4ae5172cc 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsSettleAuction.t.sol @@ -415,14 +415,14 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertBucket({ index: 2501, lpBalance: 2_000 * 1e18, - collateral: 0.110649792292326223 * 1e18, - deposit: 1_575.729229030217308768 * 1e18, - exchangeRate: 1.000557427154164986 * 1e18 + collateral: 0.110639687749865566 * 1e18, + deposit: 1_575.596009456533555165 * 1e18, + exchangeRate: 1.000471394253082551 * 1e18 }); _assertBucket({ index: MAX_FENWICK_INDEX, - lpBalance: 4132730466, - collateral: 0.041395075478140787 * 1e18, + lpBalance: 4133739266, + collateral: 0.041405180020601444 * 1e18, deposit: 0, exchangeRate: 1 * 1e18 }); @@ -550,7 +550,7 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { borrower: _borrower, borrowerDebt: 425.172014916344732952 * 1e18, borrowerCollateral: 3 * 1e18, - borrowert0Np: 149.491518364962118535 * 1e18, + borrowert0Np: 148.074537148232619781 * 1e18, borrowerCollateralization: 27.126189445355815944 * 1e18 }); _assertAuction( @@ -599,9 +599,9 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _addLiquidityWithPenalty({ from: _lender, amount: 1000 * 1e18, - amountAdded: 999.849315068493151000 * 1e18, + amountAdded: 999.876712328767123000 * 1e18, index: 6113, - lpAward: 999.849315068493151000 * 1e18, + lpAward: 999.876712328767123000 * 1e18, newLup: 3_844.432207828138682757 * 1e18 }); uint256[] memory removalIndexes = new uint256[](2); @@ -1249,9 +1249,9 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { _assertBucket({ index: 2500, lpBalance: 4_997.115384615384614000 * 1e18, - collateral: 0.886287035829988542 * 1e18, - deposit: 1_574.488096437633785035 * 1e18, - exchangeRate: 1.000336091874601493 * 1e18 + collateral: 0.886214193313763368 * 1e18, + deposit: 1_574.549120144393919177 * 1e18, + exchangeRate: 1.000291983507608180 * 1e18 }); _assertBucket({ index: 2502, @@ -1262,8 +1262,8 @@ contract ERC721PoolLiquidationsSettleAuctionTest is ERC721HelperContract { }); _assertBucket({ index: 7388, - lpBalance: 0.000000036608930253 * 1e18, - collateral: 0.366689636201052678 * 1e18, + lpBalance: 0.000000036616202579 * 1e18, + collateral: 0.366762478717277852 * 1e18, deposit: 0, exchangeRate: 1 * 1e18 }); diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol index 9cb9a9117..1781b565d 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolLiquidationsTake.t.sol @@ -189,8 +189,8 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, - actualUtilization: 0.00055111720508951 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000477170706006322 * 1e18, + targetUtilization: 0.786051641950380194 * 1e18, minDebtAmount: 4.023155597153422423 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -292,12 +292,12 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 7.749209044755361552 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_004.347853842247224958 * 1e18, + poolSize: 73_004.347853838043123634 * 1e18, pledgedCollateral: 4 * 1e18, encumberedCollateral: 2.517692578855560848 * 1e18, poolDebt: 24.968422683457442924 * 1e18, - actualUtilization: 0.000551108273306260 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000411519999179138 * 1e18, + targetUtilization: 0.781984313351887130 * 1e18, minDebtAmount: 1.248421134172872146 * 1e18, loans: 2, maxBorrower: address(_borrower), @@ -364,12 +364,12 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739737879567360457 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_004.347853842247224958 * 1e18, + poolSize: 73_004.347853838043123634 * 1e18, pledgedCollateral: 3.0 * 1e18, encumberedCollateral: 1.736300564176668638 * 1e18, poolDebt: 17.219213638702081372 * 1e18, - actualUtilization: 0.000551108273306260 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000411519999179138 * 1e18, + targetUtilization: 0.781984313351887130 * 1e18, minDebtAmount: 1.721921363870208137 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -467,8 +467,8 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { pledgedCollateral: 5 * 1e18, encumberedCollateral: 4.056751649452525709 * 1e18, poolDebt: 40.231555971534224231 * 1e18, - actualUtilization: 0.00055111720508951 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000477170706006322 * 1e18, + targetUtilization: 0.786051641950380194 * 1e18, minDebtAmount: 4.023155597153422423 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -573,12 +573,12 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { PoolParams({ htp: 5.739870563397816903 * 1e18, lup: 9.917184843435912074 * 1e18, - poolSize: 73_004.348644408093384607 * 1e18, + poolSize: 73_004.348644400449318457 * 1e18, pledgedCollateral: 3 * 1e18, encumberedCollateral: 4.070504644882883983 * 1e18, poolDebt: 40.367946969368016673 * 1e18, - actualUtilization: 0.000551102806362854 * 1e18, - targetUtilization: 0.911373730814237973 * 1e18, + actualUtilization: 0.000371331832771808 * 1e18, + targetUtilization: 0.778640404875432888 * 1e18, minDebtAmount: 4.036794696936801667 * 1e18, loans: 1, maxBorrower: address(_borrower2), @@ -653,14 +653,14 @@ contract ERC721PoolLiquidationsTakeTest is ERC721HelperContract { changePrank(_lender); // Kicker claims bond + reward and transfer to a different address - _pool.withdrawBonds(_withdrawRecipient, type(uint256).max); - assertEq(_quote.balanceOf(_withdrawRecipient), 0.242202920686750816 * 1e18); + _pool.withdrawBonds(_withdrawRecipient, 0.1 * 1e18); + assertEq(_quote.balanceOf(_withdrawRecipient), 0.1 * 1e18); - vm.revertTo(snapshot); - - // Kicker claims bond + reward + // Kicker claims remaining bond + reward to his own address _pool.withdrawBonds(_lender, type(uint256).max); - assertEq(_quote.balanceOf(_lender), 46_998.523343483554970812 * 1e18); + assertEq(_quote.balanceOf(_lender), 46_998.423343483554970812 * 1e18); + + vm.revertTo(snapshot); // revert to ensure tear down has enough balance in pool } function testTakeCollateralSubsetPoolAndSettleByRepayAndPledge() external tearDown { diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol index bc5ebbfe1..acd448d01 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolPurchaseQuote.t.sol @@ -257,7 +257,7 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { uint256 amountToPurchase = 10_100 * 1e18; assertGt(_quote.balanceOf(address(_pool)), amountToPurchase); - uint256 amountWithInterest = 24_002.859358364989920000 * 1e18; + uint256 amountWithInterest = 24_002.693295339317544000 * 1e18; _addCollateral({ from: _bidder, @@ -273,7 +273,7 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { amount: amountWithInterest, index: 2350, newLup: _priceAt(2352), - lpRedeem: 24_000.858274845540873331 * 1e18 + lpRedeem: 24_000.762569742914924690 * 1e18 }); assertEq(_quote.balanceOf(_bidder), amountWithInterest); @@ -281,10 +281,10 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { // check bucket state _assertBucket({ index: 2350, - lpBalance: 32_653.618582201813497294 * 1e18, + lpBalance: 32_653.714287304439445935 * 1e18, collateral: Maths.wad(4), deposit: 0, - exchangeRate: 1.000083375498348170 * 1e18 + exchangeRate: 1.000080444343832501 * 1e18 }); // bidder withdraws unused collateral @@ -292,13 +292,13 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { from: _bidder, amount: 1, index: 2350, - lpRedeem: 8_163.404645550453371392 * 1e18 + lpRedeem: 8_163.428571826109864353 * 1e18 }); _assertLenderLpBalance({ lender: _bidder, index: 2350, - lpBalance: 490.213936651360125902 * 1e18, + lpBalance: 490.285715478329581582 * 1e18, depositTime: _startTime + 25 hours }); @@ -310,13 +310,13 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { from: _lender, amount: 1, index: 2350, - lpRedeem: 8_163.404645550453371392 * 1e18 + lpRedeem: 8_163.428571826109864353 * 1e18 }); _assertLenderLpBalance({ lender: _bidder, index: 2350, - lpBalance: 490.213936651360125902 * 1e18, + lpBalance: 490.285715478329581582 * 1e18, depositTime: _startTime + 25 hours }); @@ -325,10 +325,10 @@ contract ERC721PoolPurchaseQuoteTest is ERC721HelperContract { // check bucket state _assertBucket({ index: 2350, - lpBalance: 16_326.809291100906754510 * 1e18, + lpBalance: 16_326.857143652219717229 * 1e18, collateral: Maths.wad(2), deposit: 0, - exchangeRate: 1.000083375498348169 * 1e18 + exchangeRate: 1.000080444343832502 * 1e18 }); // should revert if lender2 attempts to remove more collateral than lp is available for diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol index 28a2f5219..9e34bf724 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolReserveAuction.t.sol @@ -344,7 +344,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { }); } - function testReserveAuctionPartiallyTaken() external { + function testReserveAuctionPartiallyTaken() external tearDown { // borrower repays partial debt (auction for full reserves) _repayDebt({ from: _borrower, @@ -425,7 +425,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { from: _borrower, borrower: _borrower, amountToRepay: 105_000 * 1e18, - amountRepaid: 79_940.029064520279557316 * 1e18, + amountRepaid: 79_975.078950647281196428 * 1e18, collateralToPull: 0, newLup: MAX_PRICE }); @@ -433,7 +433,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { // start an auction, confirm old claimable reserves are included alongside new claimable reserves skip(1 days); - reserves = 442.238729377484010623 * 1e18; + reserves = 426.739865239988891464 * 1e18; uint256 newClaimableReserves = reserves; _assertReserveAuction({ reserves: reserves, @@ -452,9 +452,11 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { epoch: 2 }); + uint256 snapshot = vm.snapshot(); + // take everything skip(28 hours); - assertEq(expectedReserves, 767.113079809103242756 * 1e18); + assertEq(expectedReserves, 751.769204312983074788 * 1e18); expectedPrice = 3.725290298461914062 * 1e18; _assertReserveAuction({ reserves: 0, @@ -478,5 +480,7 @@ contract ERC721PoolReserveAuctionTest is ERC721HelperContract { auctionPrice: expectedPrice, timeRemaining: 44 hours }); + + vm.revertTo(snapshot); // revert to ensure tearDown has enough balance in pool } } diff --git a/tests/forge/unit/PositionManager.t.sol b/tests/forge/unit/PositionManager.t.sol index 72833270e..bf3aa409b 100644 --- a/tests/forge/unit/PositionManager.t.sol +++ b/tests/forge/unit/PositionManager.t.sol @@ -1943,7 +1943,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _assertLenderLpBalance({ lender: address(_positionManager), index: moveIndex, - lpBalance: 1_999.870115955512239554 * 1e18, + lpBalance: 1_999.865897356084855977 * 1e18, depositTime: _startTime }); skip(1 weeks); diff --git a/tests/forge/unit/Rewards/RewardsManager.t.sol b/tests/forge/unit/Rewards/RewardsManager.t.sol index f6892ac12..4100e9f28 100644 --- a/tests/forge/unit/Rewards/RewardsManager.t.sol +++ b/tests/forge/unit/Rewards/RewardsManager.t.sol @@ -441,7 +441,7 @@ contract RewardsManagerTest is RewardsHelperContract { borrowAmount: 300 * 1e18, limitIndex: 2555, pool: address(_pool), - tokensToBurn: 150.804205428530300439 * 1e18 + tokensToBurn: 150.531521503946490109 * 1e18 }); // check owner can withdraw the NFT and rewards will be automatically claimed @@ -450,9 +450,9 @@ contract RewardsManagerTest is RewardsHelperContract { pool: address(_pool), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(2, 0), - reward: 78.852344077558527016 * 1e18, + reward: 78.702367919037406995 * 1e18, indexes: depositIndexes, - updateExchangeRatesReward: 3.450241363293378951 * 1e18 + updateExchangeRatesReward: 3.436607167064188546 * 1e18 }); } @@ -524,7 +524,7 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater, pool: address(_pool), indexes: depositIndexes, - reward: 3.399633583097821167 * 1e18 + reward: 3.399661193610840835 * 1e18 }); } @@ -695,7 +695,7 @@ contract RewardsManagerTest is RewardsHelperContract { index: _i9_81, lpBalance: 10_000 * 1e18, collateral: 0, - deposit: 4_936.865619773958005817 * 1e18, + deposit: 4_936.865619773958009217 * 1e18, exchangeRate: 0.493686561977395801 * 1e18 }); @@ -976,7 +976,7 @@ contract RewardsManagerTest is RewardsHelperContract { // trigger second reserve auction totalTokensBurned += _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 753.761937534761336922 * 1e18, + tokensToBurn: 749.938886647400234043 * 1e18, borrowAmount: 1_500 * 1e18, limitIndex: 6_000, pool: address(_pool) @@ -987,13 +987,13 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater, pool: address(_pool), indexes: depositIndexes, - reward: 17.238252336072314418 * 1e18 + reward: 17.047099791704330880 * 1e18 }); - assertEq(_ajnaToken.balanceOf(_updater), 37.688096876738003300 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater), 37.496944332370019762 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + assertEq(rewardsEarned, 374.969443323700090181 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); /*****************************/ @@ -1003,7 +1003,7 @@ contract RewardsManagerTest is RewardsHelperContract { // trigger third reserve auction totalTokensBurned += _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 1_034.145224534232837796 * 1e18, + tokensToBurn: 1_030.322190308494974315 * 1e18, borrowAmount: 1_500 * 1e18, limitIndex: 6_000, pool: address(_pool) @@ -1011,7 +1011,7 @@ contract RewardsManagerTest is RewardsHelperContract { // skip updating exchange rates and check available rewards uint256 rewardsEarnedNoUpdate = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarnedNoUpdate, 376.880968767380567488 * 1e18); + assertEq(rewardsEarnedNoUpdate, 374.969443323700090181 * 1e18); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); // snapshot calling update exchange rate @@ -1022,10 +1022,10 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater2, pool: address(_pool), indexes: depositIndexes, - reward: 14.019164349973606338 * 1e18 + reward: 14.019165183054794390 * 1e18 }); - assertEq(_ajnaToken.balanceOf(_updater2), 14.019164349973606338 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 14.019165183054794390 * 1e18); // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); @@ -1042,7 +1042,7 @@ contract RewardsManagerTest is RewardsHelperContract { // triger fourth reserve auction totalTokensBurned += _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 1_289.513636917170056104 * 1e18, + tokensToBurn: 1_285.690624286578714549 * 1e18, borrowAmount: 1_500 * 1e18, limitIndex: 6_000, pool: address(_pool) @@ -1050,7 +1050,7 @@ contract RewardsManagerTest is RewardsHelperContract { // check rewards earned rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + assertEq(rewardsEarned, 374.969443323700090181 * 1e18); // call update exchange rate _updateExchangeRates({ @@ -1063,7 +1063,7 @@ contract RewardsManagerTest is RewardsHelperContract { // check rewards earned won't increase since previous update was missed rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 376.880968767380567488 * 1e18); + assertEq(rewardsEarned, 374.969443323700090181 * 1e18); /*****************************/ /*** Fifth Reserve Auction ***/ @@ -1072,7 +1072,7 @@ contract RewardsManagerTest is RewardsHelperContract { // triger fifth reserve auction totalTokensBurned += _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 1521.830620022508618175 * 1e18, + tokensToBurn: 1_518.007628131033839702 * 1e18, borrowAmount: 1_500 * 1e18, limitIndex: 6_000, pool: address(_pool) @@ -1083,12 +1083,12 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater2, pool: address(_pool), indexes: depositIndexes, - reward: 11.615849155266905358 * 1e18 + reward: 11.615850192222782234 * 1e18 }); - assertEq(_ajnaToken.balanceOf(_updater2), 11.615849155266905358 * 1e18); + assertEq(_ajnaToken.balanceOf(_updater2), 11.615850192222782234 * 1e18); rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); - assertEq(rewardsEarned, 493.039460320049744942 * 1e18); + assertEq(rewardsEarned, 491.127945245927630407 * 1e18); // claim all rewards accrued since deposit _claimRewards({ @@ -1096,7 +1096,7 @@ contract RewardsManagerTest is RewardsHelperContract { from: _minterOne, tokenId: tokenIdOne, epochsClaimed: _epochsClaimedArray(5,0), - reward: 493.039460320049744942 * 1e18 + reward: 491.127945245927630407 * 1e18 }); assertEq(_ajnaToken.balanceOf(_minterOne), rewardsEarned); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -1192,11 +1192,11 @@ contract RewardsManagerTest is RewardsHelperContract { // retrieve the position managers index set, recreating typical tx flow since positionIndexes are stored unordered in EnnumerableSets secondIndexes = _positionManager.getPositionIndexes(tokenIdOne); - secondLpsAwarded[0] = 1_000.000164743206012000 * 1e18; - secondLpsAwarded[1] = 1_006.443804104948552000 * 1e18; - secondLpsAwarded[2] = 1_000.000164743206012000 * 1e18; - secondLpsAwarded[3] = 1_000.000164743206012000 * 1e18; - secondLpsAwarded[4] = 1_000.000164743206012000 * 1e18; + secondLpsAwarded[0] = 1_000.000165321954673000 * 1e18; + secondLpsAwarded[1] = 1_006.443804687426460000 * 1e18; + secondLpsAwarded[2] = 1_000.000165321954673000 * 1e18; + secondLpsAwarded[3] = 1_000.000165321954673000 * 1e18; + secondLpsAwarded[4] = 1_000.000165321954673000 * 1e18; _moveStakedLiquidity({ from: _minterOne, @@ -1216,7 +1216,7 @@ contract RewardsManagerTest is RewardsHelperContract { // second reserve auction happens successfully -> epoch 1 _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 149.275382184037345529 * 1e18, + tokensToBurn: 149.002721220086908460 * 1e18, borrowAmount: 300 * 1e18, limitIndex: 2555, pool: address(_pool) @@ -1233,7 +1233,7 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater, pool: address(_pool), indexes: depositIndexes, - reward: 3.373279477974559345 * 1e18 + reward: 3.359647161647741986 * 1e18 }); /*********************/ @@ -1244,7 +1244,7 @@ contract RewardsManagerTest is RewardsHelperContract { from: _minterOne, tokenId: tokenIdOne, epochsClaimed: _epochsClaimedArray(1,1), - reward: 33.732794779745615314 * 1e18 + reward: 33.596471616477451732 * 1e18 }); } @@ -1320,7 +1320,7 @@ contract RewardsManagerTest is RewardsHelperContract { _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 133.010293986888191760 * 1e18, + tokensToBurn: 133.011310982683297932 * 1e18, borrowAmount: 300 * 1e18, limitIndex: 2555, pool: address(_pool) @@ -1332,24 +1332,24 @@ contract RewardsManagerTest is RewardsHelperContract { pool: address(_pool), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(1, 0), - reward: 39.906015449635223195 * 1e18, + reward: 39.906320577094451437 * 1e18, indexes: depositIndexes, - updateExchangeRatesReward: 6.651002176006408713 * 1e18 + updateExchangeRatesReward: 6.651053030580225818 * 1e18 }); uint256 minterTwoBalance = _ajnaToken.balanceOf(_minterTwo); - assertEq(minterTwoBalance, 39.906015449635223195 * 1e18); + assertEq(minterTwoBalance, 39.906320577094451437 * 1e18); _unstakeToken({ owner: _minterThree, pool: address(_pool), tokenId: tokenIdThree, claimedArray: _epochsClaimedArray(1, 0), - reward: 33.250133719815196731 * 1e18, + reward: 33.250387944827385443 * 1e18, indexes: depositIndexes, updateExchangeRatesReward: 0 }); uint256 minterThreeBalance = _ajnaToken.balanceOf(_minterThree); - assertEq(minterThreeBalance, 33.250133719815196731 * 1e18); + assertEq(minterThreeBalance, 33.250387944827385443 * 1e18); assertGt(minterTwoBalance, minterThreeBalance); } @@ -1416,7 +1416,7 @@ contract RewardsManagerTest is RewardsHelperContract { owner: _minterTwo, tokenId: tokenIdTwo }); - assertEq(_ajnaToken.balanceOf(_minterTwo), 8.154797961459423073 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterTwo), 8.154804173752250280 * 1e18); // calculate rewards earned since exchange rates have been updated uint256 idOneRewardsAtOne = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); @@ -1439,7 +1439,7 @@ contract RewardsManagerTest is RewardsHelperContract { // conduct second reserve auction uint256 secondTokensToBurn = _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 176.189760225502880454 * 1e18, + tokensToBurn: 175.886535409777500511 * 1e18, borrowAmount: 300 * 1e18, limitIndex: 3, pool: address(_pool) @@ -1471,11 +1471,11 @@ contract RewardsManagerTest is RewardsHelperContract { uint256 idOneRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); assertLt(idOneRewardsAtTwo, secondTokensToBurn); assertGt(idOneRewardsAtTwo, 0); - assertEq(idOneRewardsAtTwo, 23.615632136163281165 * 1e18); + assertEq(idOneRewardsAtTwo, 23.539744751129506689 * 1e18); uint256 idTwoRewardsAtTwo = _rewardsManager.calculateRewards(tokenIdTwo, _pool.currentBurnEpoch()); assertLt(idOneRewardsAtTwo + idTwoRewardsAtTwo, secondTokensToBurn); - assertEq(idTwoRewardsAtTwo, 23.583081554200477722 * 1e18); + assertEq(idTwoRewardsAtTwo, 23.507298745456577468 * 1e18); assertGt(idTwoRewardsAtTwo, 0); // minter one claims rewards accrued after second auction @@ -1484,7 +1484,7 @@ contract RewardsManagerTest is RewardsHelperContract { from: _minterOne, tokenId: tokenIdOne, epochsClaimed: _epochsClaimedArray(1,1), - reward: 23.615632136163281165 * 1e18 + reward: 23.539744751129506689 * 1e18 }); assertEq(_ajnaToken.balanceOf(_minterOne), idOneRewardsAtOne + idOneRewardsAtTwo); @@ -1497,7 +1497,7 @@ contract RewardsManagerTest is RewardsHelperContract { epochsClaimed: _epochsClaimedArray(1,1), reward: idTwoRewardsAtTwo }); - assertEq(_ajnaToken.balanceOf(_minterTwo), 31.737879515659900795 * 1e18); + assertEq(_ajnaToken.balanceOf(_minterTwo), 31.662102919208827748 * 1e18); // check there are no remaining rewards available after claiming uint256 remainingRewards = _rewardsManager.calculateRewards(tokenIdOne, _pool.currentBurnEpoch()); @@ -1594,7 +1594,7 @@ contract RewardsManagerTest is RewardsHelperContract { // auction two uint256 tokensToBurnE2 = _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 308.672122067616182565 * 1e18, + tokensToBurn: 308.524022190658113598 * 1e18, borrowAmount: 1_000 * 1e18, limitIndex: 2555, pool: address(_pool) @@ -1604,22 +1604,22 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater, pool: address(_pool), indexes: depositIndexes, - reward: 11.343637195247653990 * 1e18 + reward: 11.336232201399613917 * 1e18 }); _assertBurn({ pool: address(_pool), epoch: 2, timestamp: block.timestamp - 24 hours, - burned: 308.672122067616182565 * 1e18, + burned: 308.524022190658113598 * 1e18, tokensToBurn: tokensToBurnE2, - interest: 23.936044239893182713 * 1e18 + interest: 23.938554041534910348 * 1e18 }); // auction three uint256 tokensToBurnE3 = _triggerReserveAuctions({ borrower: _borrower, - tokensToBurn: 676.658832743043390869 * 1e18, + tokensToBurn: 676.510732923020389616 * 1e18, borrowAmount: 2_000 * 1e18, limitIndex: 2555, pool: address(_pool) @@ -1629,16 +1629,16 @@ contract RewardsManagerTest is RewardsHelperContract { updater: _updater, pool: address(_pool), indexes: depositIndexes, - reward: 18.399335533771072810 * 1e18 + reward: 18.399335536618388154 * 1e18 }); _assertBurn({ pool: address(_pool), epoch: 3, timestamp: block.timestamp - 24 hours, - burned: 676.658832743043390869 * 1e18, + burned: 676.510732923020389616 * 1e18, tokensToBurn: tokensToBurnE3, - interest: 52.421031459480795903 * 1e18 + interest: 52.423541260157607958 * 1e18 }); // both stakers claim rewards @@ -1647,7 +1647,7 @@ contract RewardsManagerTest is RewardsHelperContract { pool: address(_pool), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(3, 0), - reward: 51.511621814050026070 * 1e18, + reward: 51.499282055430577895 * 1e18, indexes: firstIndexes, updateExchangeRatesReward: 0 }); @@ -1657,7 +1657,7 @@ contract RewardsManagerTest is RewardsHelperContract { pool: address(_pool), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(3, 0), - reward: 286.817794557471160695 * 1e18, + reward: 286.756084406079436885 * 1e18, indexes: secondIndexes, updateExchangeRatesReward: 0 }); From c3ec05a58f53165462978842141b29bab1394cff Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 24 Apr 2023 06:52:33 +0300 Subject: [PATCH 70/70] Fenwick gas improvements (#761) * Calcualte current index only once * Declare vars outside for / while loops --- src/libraries/internal/Deposits.sol | 48 ++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/libraries/internal/Deposits.sol b/src/libraries/internal/Deposits.sol index 57f721e82..a9b90e19c 100644 --- a/src/libraries/internal/Deposits.sol +++ b/src/libraries/internal/Deposits.sol @@ -40,13 +40,17 @@ library Deposits { // 2- We often need to precisely change the value in the tree, avoiding the rounding that dividing by scale(index). // This is more relevant to unscaledRemove(...), where we need to ensure the value is precisely set to 0, but we // also prefer it here for consistency. - + + uint256 value; + uint256 scaling; + uint256 newValue; + while (index_ <= SIZE) { - uint256 value = deposits_.values[index_]; - uint256 scaling = deposits_.scaling[index_]; + value = deposits_.values[index_]; + scaling = deposits_.scaling[index_]; // Compute the new value to be put in location index_ - uint256 newValue = value + unscaledAddAmount_; + newValue = value + unscaledAddAmount_; // Update unscaledAddAmount to propogate up the Fenwick tree // Note: we can't just multiply addAmount_ by scaling[i_] due to rounding @@ -81,22 +85,27 @@ library Deposits { // We construct the target sumIndex_ bit by bit, from MSB to LSB. lowerIndexSum_ always maintains the sum // up to the current value of sumIndex_ uint256 lowerIndexSum; + uint256 curIndex; + uint256 value; + uint256 scaling; + uint256 scaledValue; while (i > 0) { // Consider if the target index is less than or greater than sumIndex_ + i - uint256 value = deposits_.values[sumIndex_ + i]; - uint256 scaling = deposits_.scaling[sumIndex_ + i]; + curIndex = sumIndex_ + i; + value = deposits_.values[curIndex]; + scaling = deposits_.scaling[curIndex]; // Compute sum up to sumIndex_ + i - uint256 scaledValue = + scaledValue = lowerIndexSum + (scaling != 0 ? (runningScale * scaling * value + 5e35) / 1e36 : Maths.wmul(runningScale, value)); if (scaledValue < targetSum_) { // Target value is too small, need to consider increasing sumIndex_ still - if (sumIndex_ + i <= MAX_FENWICK_INDEX) { + if (curIndex <= MAX_FENWICK_INDEX) { // sumIndex_+i is in range of Fenwick prices. Target index has this bit set to 1. - sumIndex_ += i; + sumIndex_ = curIndex; lowerIndexSum = scaledValue; } } else { @@ -227,23 +236,26 @@ library Deposits { // Used to terminate loop. We don't need to consider final 0 bits of sumIndex_ uint256 indexLSB = lsb(sumIndex_); + uint256 curIndex; while (j >= indexLSB) { + curIndex = index + j; + // Skip considering indices outside bounds of Fenwick tree - if (index + j > SIZE) continue; + if (curIndex > SIZE) continue; // We are considering whether to include node index + j in the sum or not. Either way, we need to scaling[index + j], // either to increment sum_ or to accumulate in runningScale - uint256 scaled = deposits_.scaling[index + j]; + uint256 scaled = deposits_.scaling[curIndex]; if (sumIndex_ & j != 0) { // node index + j of tree is included in sum - uint256 value = deposits_.values[index + j]; + uint256 value = deposits_.values[curIndex]; // Accumulate in sum_, recall that scaled==0 means that the scale factor is actually 1 sum_ += scaled != 0 ? (runningScale * scaled * value + 5e35) / 1e36 : Maths.wmul(runningScale, value); // Build up index bit by bit - index += j; + index = curIndex; // terminate if we've already matched sumIndex_ if (index == sumIndex_) break; @@ -352,9 +364,15 @@ library Deposits { // 2- We may already have computed the scale factor, so we can avoid duplicate traversal unscaledDepositValue_ = deposits_.values[index_]; + uint256 curIndex; + uint256 value; + uint256 scaling; + while (j & index_ == 0) { - uint256 value = deposits_.values[index_ - j]; - uint256 scaling = deposits_.scaling[index_ - j]; + curIndex = index_ - j; + + value = deposits_.values[curIndex]; + scaling = deposits_.scaling[curIndex]; unscaledDepositValue_ -= scaling != 0 ? Maths.wmul(scaling, value) : value; j = j << 1;