From 7127dff150fb4f93fd57dfd2f78a1594d75d25e2 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 31 Jan 2023 09:19:45 +0200 Subject: [PATCH] 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) --- .github/pull_request_template.md | 37 ++++ src/ERC20Pool.sol | 26 +-- src/ERC721Pool.sol | 26 +-- src/base/Pool.sol | 21 +- .../pool/commons/IPoolInternals.sol | 49 +++-- .../commons/IPoolReserveAuctionActions.sol | 2 +- src/interfaces/pool/commons/IPoolState.sol | 1 + src/libraries/external/Auctions.sol | 188 +++++++++--------- src/libraries/external/BorrowerActions.sol | 92 +++++---- .../ERC20Pool/ERC20PoolLiquidationsTake.t.sol | 97 +++++++++ .../ERC721Pool/ERC721PoolCollateral.t.sol | 2 +- .../forge/ERC721Pool/ERC721PoolInterest.t.sol | 32 +-- tests/forge/RewardsManager.t.sol | 20 +- 13 files changed, 359 insertions(+), 234 deletions(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..53f44caf0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,37 @@ + + +# Description of change +## High level +* + * + + + +# Description of bug or vulnerability and solution +* +* + +# Contract size +## Pre Change + +## Post Change + + +# Gas usage +## Pre Change + +## Post Change + + diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 4f90e78c8..6ba111342 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -165,7 +165,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt += result.t0DebtChange; + poolBalances.t0Debt = result.t0PoolDebt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -212,7 +212,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt -= result.t0RepaidDebt; + poolBalances.t0Debt = result.t0PoolDebt; if (result.t0DebtInAuctionChange != 0) { poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; } @@ -427,18 +427,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { result.quoteTokenAmount = _roundUpToScale(result.quoteTokenAmount, _getArgUint256(QUOTE_SCALE)); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; @@ -488,18 +481,11 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index b1bf36b9e..96c1df28b 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -174,7 +174,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { // move borrowed amount from pool to sender if (amountToBorrow_ != 0) { // update pool balances state - poolBalances.t0Debt += result.t0DebtChange; + poolBalances.t0Debt = result.t0PoolDebt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); @@ -220,7 +220,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { if (result.quoteTokenToRepay != 0) { // update pool balances state - poolBalances.t0Debt -= result.t0RepaidDebt; + poolBalances.t0Debt = result.t0PoolDebt; if (result.t0DebtInAuctionChange != 0) { poolBalances.t0DebtInAuction -= result.t0DebtInAuctionChange; } @@ -422,18 +422,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; @@ -494,18 +487,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { ); // update pool balances state - uint256 t0PoolDebt = poolBalances.t0Debt; uint256 t0DebtInAuction = poolBalances.t0DebtInAuction; - - if (result.t0DebtPenalty != 0) { - t0PoolDebt += result.t0DebtPenalty; - t0DebtInAuction += result.t0DebtPenalty; - } - - t0PoolDebt -= result.t0RepayAmount; + t0DebtInAuction += result.t0DebtPenalty; t0DebtInAuction -= result.t0DebtInAuctionChange; - poolBalances.t0Debt = t0PoolDebt; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction = t0DebtInAuction; poolBalances.pledgedCollateral -= result.collateralAmount; diff --git a/src/base/Pool.sol b/src/base/Pool.sol index f5eaebfc7..e327a9db9 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -273,11 +273,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); // update pool balances state + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction += result.t0KickedDebt; - poolBalances.t0Debt += result.t0KickPenalty; // update pool interest rate state - poolState.debt += result.kickPenalty; + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); _updateInterestState(poolState, result.lup); if(result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); @@ -304,11 +304,11 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); // update pool balances state - poolBalances.t0Debt += result.t0KickPenalty; + poolBalances.t0Debt = result.t0PoolDebt; poolBalances.t0DebtInAuction += result.t0KickedDebt; // update pool interest rate state - poolState.debt += result.kickPenalty; + poolState.debt = Maths.wmul(result.t0PoolDebt, poolState.inflator); _updateInterestState(poolState, result.lup); // transfer from kicker to pool the difference to cover bond @@ -356,7 +356,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { reserveAuction, StartReserveAuctionParams({ poolSize: Deposits.treeSum(deposits), - poolDebt: poolBalances.t0Debt, + t0PoolDebt: poolBalances.t0Debt, poolBalance: _getPoolQuoteTokenBalance(), inflator: inflatorState.inflator }) @@ -424,10 +424,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { * @return poolState_ Struct containing pool details. */ function _accruePoolInterest() internal returns (PoolState memory poolState_) { - // retrieve t0Debt amount from poolBalances struct - uint256 t0Debt = poolBalances.t0Debt; - - // initialize fields of poolState_ struct with initial values + poolState_.t0Debt = poolBalances.t0Debt; poolState_.collateral = poolBalances.pledgedCollateral; poolState_.inflator = inflatorState.inflator; poolState_.rate = interestState.interestRate; @@ -435,9 +432,9 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { 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 (t0Debt != 0) { + if (poolState_.t0Debt != 0) { // Calculate prior pool debt - poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator); + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // calculate elapsed time since inflator was last updated uint256 elapsed = block.timestamp - inflatorState.inflatorUpdate; @@ -455,7 +452,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { ); poolState_.inflator = newInflator; // After debt owed to lenders has accrued, calculate current debt owed by borrowers - poolState_.debt = Maths.wmul(t0Debt, poolState_.inflator); + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // update total interest earned accumulator with the newly accrued interest reserveAuction.totalInterestEarned += newInterest; diff --git a/src/interfaces/pool/commons/IPoolInternals.sol b/src/interfaces/pool/commons/IPoolInternals.sol index 197e4ecb2..f6d276055 100644 --- a/src/interfaces/pool/commons/IPoolInternals.sol +++ b/src/interfaces/pool/commons/IPoolInternals.sol @@ -11,22 +11,21 @@ pragma solidity 0.8.14; /*****************************/ struct BucketTakeResult { - uint256 collateralAmount; - uint256 t0RepayAmount; - uint256 t0DebtPenalty; - uint256 remainingCollateral; - uint256 poolDebt; - uint256 newLup; - uint256 t0DebtInAuctionChange; - bool settledAuction; + uint256 collateralAmount; // [WAD] amount of collateral taken + 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 } struct KickResult { - uint256 amountToCoverBond; // amount of bond that needs to be covered - uint256 kickPenalty; // kick penalty - uint256 t0KickPenalty; // t0 kick penalty - uint256 t0KickedDebt; // new t0 debt after kick - uint256 lup; // current lup + uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered + uint256 t0PoolDebt; // [WAD] t0 debt in pool after kick + uint256 t0KickedDebt; // [WAD] new t0 debt after kick + uint256 lup; // [WAD] current lup } struct SettleParams { @@ -38,16 +37,16 @@ struct SettleParams { } struct TakeResult { - uint256 collateralAmount; - uint256 quoteTokenAmount; - uint256 t0RepayAmount; - uint256 t0DebtPenalty; - uint256 excessQuoteToken; - uint256 remainingCollateral; - uint256 poolDebt; - uint256 newLup; - uint256 t0DebtInAuctionChange; - bool settledAuction; + uint256 collateralAmount; // [WAD] amount of collateral taken + 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 + 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 } /******************************************/ @@ -83,7 +82,7 @@ struct DrawDebtResult { uint256 remainingCollateral; // [WAD] amount of borrower collateral after draw debt (for NFT can be diminished if auction settled) bool settledAuction; // true if collateral pledged settles auction uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after pledge collateral - uint256 t0DebtChange; // [WAD] change of total t0 pool debt after after draw debt + uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after draw debt } struct RepayDebtResult { @@ -93,6 +92,6 @@ struct RepayDebtResult { uint256 remainingCollateral; // [WAD] amount of borrower collateral after pull collateral bool settledAuction; // true if repay debt settles auction uint256 t0DebtInAuctionChange; // [WAD] change of t0 pool debt in auction after repay debt - uint256 t0RepaidDebt; // [WAD] amount of t0 repaid debt + uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after repay uint256 quoteTokenToRepay; // [WAD] quote token amount to be transferred from sender to pool } \ No newline at end of file diff --git a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol b/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol index 9e28f5fbe..6ceed46dc 100644 --- a/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol +++ b/src/interfaces/pool/commons/IPoolReserveAuctionActions.sol @@ -27,7 +27,7 @@ interface IPoolReserveAuctionActions { struct StartReserveAuctionParams { uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) - uint256 poolDebt; // [WAD] current t0 pool 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/IPoolState.sol b/src/interfaces/pool/commons/IPoolState.sol index d8123d53a..a03f05b5d 100644 --- a/src/interfaces/pool/commons/IPoolState.sol +++ b/src/interfaces/pool/commons/IPoolState.sol @@ -250,6 +250,7 @@ struct PoolBalancesState { struct PoolState { uint8 poolType; // pool type, can be ERC20 or ERC721 + uint256 t0Debt; // [WAD] t0 debt in 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/Auctions.sol b/src/libraries/external/Auctions.sol index cd7cc022b..990448cfc 100644 --- a/src/libraries/external/Auctions.sol +++ b/src/libraries/external/Auctions.sol @@ -59,17 +59,13 @@ library Auctions { struct BucketTakeParams { address borrower; // borrower address to take from - uint256 collateral; // [WAD] borrower available collateral to take 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 t0Debt; // [WAD] borrower t0 debt uint256 collateralScale; // precision of collateral token based on decimals } struct TakeParams { address borrower; // borrower address to take from - uint256 collateral; // [WAD] borrower available collateral to take - uint256 t0Debt; // [WAD] borrower t0 debt uint256 takeCollateral; // [WAD] desired amount to take uint256 inflator; // [WAD] current pool inflator uint256 poolType; // pool type (ERC20 or NFT) @@ -122,20 +118,6 @@ library Auctions { uint256 unscaledDeposit; // [WAD] Unscaled bucket quantity uint256 unscaledQuoteTokenAmount; // [WAD] The unscaled token amount that taker should pay for collateral taken. } - struct TakeLoanLocalVars { - uint256 repaidDebt; // [WAD] the amount of debt repaid to th epool by take auction - uint256 borrowerDebt; // [WAD] the amount of borrower debt - bool inAuction; // true if loan in auction - } - struct TakeFromLoanLocalVars { - uint256 borrowerDebt; // [WAD] borrower's accrued debt - bool inAuction; // true if loan still in auction after auction is taken, false otherwise - uint256 newLup; // [WAD] LUP after auction is taken - uint256 repaidDebt; // [WAD] debt repaid when auction is taken - uint256 t0DebtInAuction; // [WAD] t0 pool debt in auction - uint256 t0DebtInAuctionChange; // [WAD] t0 change amount of debt after auction is taken - uint256 t0PoolDebt; // [WAD] t0 pool debt - } /**************/ /*** Events ***/ @@ -379,7 +361,7 @@ library Auctions { DepositsState storage deposits_, mapping(uint256 => Bucket) storage buckets_, LoansState storage loans_, - PoolState memory poolState_, + PoolState calldata poolState_, uint256 index_ ) external returns ( KickResult memory kickResult_ @@ -484,19 +466,20 @@ library Auctions { if (borrower.collateral == 0) revert InsufficientCollateral(); // revert if borrower's collateral is 0 + uint256 t0RepayAmount; + uint256 t0BorrowerDebt; ( result_.collateralAmount, - result_.t0RepayAmount, - borrower.t0Debt, + t0RepayAmount, + t0BorrowerDebt, result_.t0DebtPenalty ) = _takeBucket( auctions_, buckets_, deposits_, + borrower, BucketTakeParams({ borrower: borrowerAddress_, - collateral: borrower.collateral, - t0Debt: borrower.t0Debt, inflator: poolState_.inflator, depositTake: depositTake_, index: index_, @@ -505,15 +488,17 @@ library Auctions { ); borrower.collateral -= result_.collateralAmount; + borrower.t0Debt = t0BorrowerDebt - t0RepayAmount; - if (result_.t0DebtPenalty != 0) { - poolState_.debt += Maths.wmul(result_.t0DebtPenalty, poolState_.inflator); - } + // update pool debt: apply penalty if case + poolState_.t0Debt += result_.t0DebtPenalty; + poolState_.t0Debt -= t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; ( - result_.poolDebt, result_.newLup, - result_.t0DebtInAuctionChange, result_.settledAuction ) = _takeLoan( auctions_, @@ -522,9 +507,16 @@ library Auctions { loans_, poolState_, borrower, - borrowerAddress_, - result_.t0RepayAmount + borrowerAddress_ ); + + if (result_.settledAuction) { + // the overall debt in auction change is the total borrower debt exiting auction + result_.t0DebtInAuctionChange = t0BorrowerDebt; + } else { + // the overall debt in auction change is the amount of partially repaid debt + result_.t0DebtInAuctionChange = t0RepayAmount; + } } /** @@ -550,19 +542,20 @@ library Auctions { // revert if borrower's collateral is 0 or if maxCollateral to be taken is 0 if (borrower.collateral == 0 || collateral_ == 0) revert InsufficientCollateral(); + uint256 t0RepayAmount; + uint256 t0BorrowerDebt; ( result_.collateralAmount, result_.quoteTokenAmount, - result_.t0RepayAmount, - borrower.t0Debt, + t0RepayAmount, + t0BorrowerDebt, result_.t0DebtPenalty, result_.excessQuoteToken ) = _take( auctions_, + borrower, TakeParams({ borrower: borrowerAddress_, - collateral: borrower.collateral, - t0Debt: borrower.t0Debt, takeCollateral: collateral_, inflator: poolState_.inflator, poolType: poolState_.poolType, @@ -571,15 +564,17 @@ library Auctions { ); borrower.collateral -= result_.collateralAmount; + borrower.t0Debt = t0BorrowerDebt - t0RepayAmount; - if (result_.t0DebtPenalty != 0) { - poolState_.debt += Maths.wmul(result_.t0DebtPenalty, poolState_.inflator); - } + // update pool debt: apply penalty if case + poolState_.t0Debt += result_.t0DebtPenalty; + poolState_.t0Debt -= t0RepayAmount; + poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; ( - result_.poolDebt, result_.newLup, - result_.t0DebtInAuctionChange, result_.settledAuction ) = _takeLoan( auctions_, @@ -588,9 +583,16 @@ library Auctions { loans_, poolState_, borrower, - borrowerAddress_, - result_.t0RepayAmount + borrowerAddress_ ); + + if (result_.settledAuction) { + // the overall debt in auction change is the total borrower debt exiting auction + result_.t0DebtInAuctionChange = t0BorrowerDebt; + } else { + // the overall debt in auction change is the amount of partially repaid debt + result_.t0DebtInAuctionChange = t0RepayAmount; + } } /** @@ -611,7 +613,7 @@ library Auctions { uint256 curUnclaimedAuctionReserve = reserveAuction_.unclaimed; uint256 claimable = _claimableReserves( - Maths.wmul(params_.poolDebt, params_.inflator), + Maths.wmul(params_.t0PoolDebt, params_.inflator), params_.poolSize, auctions_.totalBondEscrowed, curUnclaimedAuctionReserve, @@ -770,7 +772,7 @@ library Auctions { AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, - PoolState memory poolState_, + PoolState calldata poolState_, address borrowerAddress_, uint256 additionalDebt_ ) internal returns ( @@ -806,10 +808,6 @@ library Auctions { momp ); - // when loan is kicked, penalty of three months of interest is added - kickResult_.kickPenalty = Maths.wmul(Maths.wdiv(poolState_.rate, 4 * 1e18), borrowerDebt); - kickResult_.t0KickPenalty = Maths.wdiv(kickResult_.kickPenalty, poolState_.inflator); - // record liquidation info uint256 neutralPrice = Maths.wmul(borrower.t0Np, poolState_.inflator); _recordAuction( @@ -827,14 +825,20 @@ library Auctions { // remove kicked loan from heap Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); - kickResult_.t0KickedDebt += kickResult_.t0KickPenalty; + // when loan is kicked, penalty of three months of interest is added + uint256 t0KickPenalty = Maths.wmul(kickResult_.t0KickedDebt, Maths.wdiv(poolState_.rate, 4 * 1e18)); + uint256 kickPenalty = Maths.wmul(t0KickPenalty, poolState_.inflator); + kickResult_.t0PoolDebt = poolState_.t0Debt + t0KickPenalty; + kickResult_.t0KickedDebt += t0KickPenalty; + + // update borrower debt with kicked debt penalty borrower.t0Debt = kickResult_.t0KickedDebt; emit Kick( borrowerAddress_, - borrowerDebt + kickResult_.kickPenalty, - borrower.collateral, + borrowerDebt + kickPenalty, + borrowerCollateral, bondSize ); } @@ -843,7 +847,8 @@ library Auctions { * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. * @dev emit events: * - Take - * @param params_ Struct containing take action params details. + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action params details. * @return Collateral amount taken. * @return Quote token to be received from taker. * @return T0 debt amount repaid. @@ -853,11 +858,17 @@ library Auctions { */ function _take( AuctionsState storage auctions_, + Borrower memory borrower_, TakeParams memory params_ ) internal returns (uint256, uint256, uint256, uint256, uint256, uint256) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - TakeLocalVars memory vars = _prepareTake(liquidation, params_.t0Debt, params_.collateral, params_.inflator); + TakeLocalVars memory 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) @@ -868,7 +879,7 @@ library Auctions { // 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(params_.collateral, params_.takeCollateral), + Maths.min(borrower_.collateral, params_.takeCollateral), params_.inflator, params_.collateralScale, vars @@ -888,7 +899,7 @@ library Auctions { // 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 && params_.collateral >= collateralTaken + 1e18) { // collateral taken not a round number + if (collateralTaken != vars.collateralAmount && borrower_.collateral >= collateralTaken + 1e18) { // collateral taken not a round number 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 @@ -912,7 +923,8 @@ library Auctions { * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). * @dev emit events: * - BucketTake - * @param params_ Struct containing take action details. + * @param borrower_ Struct containing auctioned borrower details. + * @param params_ Struct containing take action details. * @return Collateral amount taken. * @return T0 debt amount repaid. * @return T0 borrower debt (including penalty). @@ -922,12 +934,18 @@ library Auctions { AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, + Borrower memory borrower_, BucketTakeParams memory params_ ) internal returns (uint256, uint256, uint256, uint256) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; - TakeLocalVars memory vars = _prepareTake(liquidation, params_.t0Debt, params_.collateral, params_.inflator); + TakeLocalVars memory vars = _prepareTake( + liquidation, + borrower_.t0Debt, + borrower_.collateral, + params_.inflator + ); vars.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); @@ -944,7 +962,7 @@ library Auctions { vars.bucketScale = Deposits.scale(deposits_, params_.index); vars = _calculateTakeFlowsAndBondChange( - params_.collateral, + borrower_.collateral, params_.inflator, params_.collateralScale, vars @@ -982,13 +1000,11 @@ library Auctions { * @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_ The borrower details owning loan that is taken. - * @param borrowerAddress_ The address of the borrower. - * @param t0RepaidDebt_ T0 debt amount repaid by the take action. - * @return poolDebt_ Accrued debt pool after debt is repaid. - * @return newLup_ The new LUP of pool (after debt is repaid). - * @return t0DebtInAuctionChange_ The overall debt in auction change (remaining borrower debt if auction settled, repaid debt otherwise). - * @return settledAuction_ True if auction is settled by the take action. + * @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. */ function _takeLoan( AuctionsState storage auctions_, @@ -997,39 +1013,28 @@ library Auctions { LoansState storage loans_, PoolState memory poolState_, Borrower memory borrower_, - address borrowerAddress_, - uint256 t0RepaidDebt_ + address borrowerAddress_ ) internal returns ( - uint256 poolDebt_, uint256 newLup_, - uint256 t0DebtInAuctionChange_, bool settledAuction_ ) { - TakeLoanLocalVars memory vars; - - vars.repaidDebt = Maths.wmul(t0RepaidDebt_, poolState_.inflator); - vars.borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); - - vars.borrowerDebt -= vars.repaidDebt; - poolDebt_ = poolState_.debt - vars.repaidDebt; + uint256 borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); // check that taking from loan doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, poolDebt_, vars.borrowerDebt, poolState_.quoteDustLimit); - - newLup_ = _lup(deposits_, poolDebt_); - - vars.inAuction = true; + _revertOnMinDebt( + loans_, + poolState_.debt, + borrowerDebt, + poolState_.quoteDustLimit + ); - if (_isCollateralized(vars.borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { - // settle auction if borrower becomes re-collateralized + // calculate new lup with repaid debt from take + newLup_ = _lup(deposits_, poolState_.debt); - vars.inAuction = false; + if (_isCollateralized(borrowerDebt, borrower_.collateral, newLup_, poolState_.poolType)) { settledAuction_ = true; - // the overall debt in auction change is the total borrower debt exiting auction - t0DebtInAuctionChange_ = borrower_.t0Debt; - // settle auction and update borrower's collateral with value after settlement borrower_.collateral = _settleAuction( auctions_, @@ -1039,13 +1044,8 @@ library Auctions { borrower_.collateral, poolState_.poolType ); - } else { - // the overall debt in auction change is the amount of partially repaid debt - t0DebtInAuctionChange_ = t0RepaidDebt_; } - borrower_.t0Debt -= t0RepaidDebt_; - // update loan state, stamp borrower t0Np only when exiting from auction Loans.update( loans_, @@ -1053,11 +1053,11 @@ library Auctions { deposits_, borrower_, borrowerAddress_, - vars.borrowerDebt, + borrowerDebt, poolState_.rate, newLup_, - vars.inAuction, - !vars.inAuction // stamp borrower t0Np if exiting from auction + !settledAuction_, + settledAuction_ // stamp borrower t0Np if exiting from auction ); } diff --git a/src/libraries/external/BorrowerActions.sol b/src/libraries/external/BorrowerActions.sol index 5fb1f70a5..163d974cb 100644 --- a/src/libraries/external/BorrowerActions.sol +++ b/src/libraries/external/BorrowerActions.sol @@ -41,11 +41,14 @@ library BorrowerActions { /*************************/ struct DrawDebtLocalVars { - uint256 borrowerDebt; // [WAD] borrower's accrued debt - uint256 debtChange; // [WAD] additional debt resulted from draw debt action - bool inAuction; // true if loan is auctioned - uint256 lupId; // id of new LUP - bool stampT0Np; // true if loan's t0 neutral price should be restamped (when drawing debt or pledge settles auction) + bool borrow; // true if borrow action + uint256 borrowerDebt; // [WAD] borrower's accrued debt + 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 + uint256 lupId; // id of new LUP + 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) } struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt @@ -112,21 +115,23 @@ library BorrowerActions { ) { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; - result_.poolDebt = poolState_.debt; - result_.newLup = _lup(deposits_, result_.poolDebt); - result_.poolCollateral = poolState_.collateral; - DrawDebtLocalVars memory vars; + vars.pledge = collateralToPledge_ != 0; + vars.borrow = amountToBorrow_ != 0 || limitIndex_ != 0; // enable an intentional 0 borrow loan call to update borrower's loan state vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - // pledge collateral to pool - if (collateralToPledge_ != 0) { + result_.t0PoolDebt = poolState_.t0Debt; + result_.poolDebt = poolState_.debt; + result_.poolCollateral = poolState_.collateral; + + if (vars.pledge) { // add new amount of collateral to pledge to borrower balance borrower.collateral += collateralToPledge_; - // load loan's auction state + result_.newLup = _lup(deposits_, result_.poolDebt); vars.inAuction = _inAuction(auctions_, borrowerAddress_); + // if loan is auctioned and becomes collateralized by newly pledged collateral then settle auction if ( vars.inAuction && @@ -158,22 +163,30 @@ library BorrowerActions { result_.poolCollateral += collateralToPledge_; } - // borrow against pledged collateral - // check both values to enable an intentional 0 borrow loan call to update borrower's loan state - if (amountToBorrow_ != 0 || limitIndex_ != 0) { + if (vars.borrow) { // only intended recipient can borrow quote if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); - // add origination fee to the amount to borrow and add to borrower's debt - vars.debtChange = Maths.wmul(amountToBorrow_, _feeRate(poolState_.rate) + Maths.WAD); + vars.t0BorrowAmount = Maths.wdiv(amountToBorrow_, poolState_.inflator); + + // t0 debt change is t0 amount to borrow plus the origination fee + vars.t0DebtChange = Maths.wmul(vars.t0BorrowAmount, _feeRate(poolState_.rate) + Maths.WAD); + + borrower.t0Debt += vars.t0DebtChange; - vars.borrowerDebt += vars.debtChange; + vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); - // check that drawing debt doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteDustLimit); + // check that drawing debt doesn't leave borrower debt under pool min debt amount + _revertOnMinDebt( + loans_, + result_.poolDebt, + vars.borrowerDebt, + poolState_.quoteDustLimit + ); // add debt change to pool's debt - result_.poolDebt += vars.debtChange; + result_.t0PoolDebt += vars.t0DebtChange; + result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); // determine new lup index and revert if borrow happens at a price higher than the specified limit (lower index than lup index) vars.lupId = _lupIndex(deposits_, result_.poolDebt); @@ -189,10 +202,11 @@ library BorrowerActions { // stamp borrower t0Np when draw debt vars.stampT0Np = true; + } - result_.t0DebtChange = Maths.wdiv(vars.debtChange, poolState_.inflator); - - borrower.t0Debt += result_.t0DebtChange; + // calculate LUP if it wasn't calculated previously + if (!vars.pledge && !vars.borrow) { + result_.newLup = _lup(deposits_, result_.poolDebt); } // update loan state @@ -251,9 +265,10 @@ library BorrowerActions { RepayDebtLocalVars memory vars; vars.repay = maxQuoteTokenAmountToRepay_ != 0; - vars.pull = collateralAmountToPull_ != 0; + vars.pull = collateralAmountToPull_ != 0; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); + result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.poolCollateral = poolState_.collateral; @@ -261,25 +276,32 @@ library BorrowerActions { if (borrower.t0Debt == 0) revert NoDebt(); if (maxQuoteTokenAmountToRepay_ == type(uint256).max) { - result_.t0RepaidDebt = borrower.t0Debt; + vars.t0RepaidDebt = borrower.t0Debt; } else { - result_.t0RepaidDebt = Maths.min( + vars.t0RepaidDebt = Maths.min( borrower.t0Debt, Maths.wdiv(maxQuoteTokenAmountToRepay_, poolState_.inflator) ); } - result_.quoteTokenToRepay = Maths.wmul(result_.t0RepaidDebt, poolState_.inflator); + result_.t0PoolDebt -= vars.t0RepaidDebt; - result_.poolDebt -= result_.quoteTokenToRepay; - vars.borrowerDebt -= result_.quoteTokenToRepay; + result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); + result_.quoteTokenToRepay = Maths.wmul(vars.t0RepaidDebt, poolState_.inflator); + vars.borrowerDebt = Maths.wmul(borrower.t0Debt - vars.t0RepaidDebt, poolState_.inflator); // check that paying the loan doesn't leave borrower debt under min debt amount - _revertOnMinDebt(loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteDustLimit); + _revertOnMinDebt( + loans_, + result_.poolDebt, + vars.borrowerDebt, + poolState_.quoteDustLimit + ); result_.newLup = _lup(deposits_, result_.poolDebt); vars.inAuction = _inAuction(auctions_, borrowerAddress_); + // if loan is auctioned and becomes collateralized by repaying debt then settle auction if (vars.inAuction) { if (_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) { // borrower becomes re-collateralized @@ -304,18 +326,18 @@ library BorrowerActions { borrower.collateral = result_.remainingCollateral; } else { // partial repay, remove only the paid debt from pool auctions debt accumulator - result_.t0DebtInAuctionChange = result_.t0RepaidDebt; + result_.t0DebtInAuctionChange = vars.t0RepaidDebt; } } - borrower.t0Debt -= result_.t0RepaidDebt; + borrower.t0Debt -= vars.t0RepaidDebt; } if (vars.pull) { // only intended recipient can pull collateral if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); - // calculate LUP only if it wasn't calculated by repay action + // calculate LUP only if it wasn't calculated in repay action if (!vars.repay) result_.newLup = _lup(deposits_, result_.poolDebt); uint256 encumberedCollateral = borrower.t0Debt != 0 ? Maths.wdiv(vars.borrowerDebt, result_.newLup) : 0; @@ -329,7 +351,7 @@ library BorrowerActions { result_.poolCollateral -= collateralAmountToPull_; } - // calculate LUP if repay is called with 0 amount + // calculate LUP if it wasn't calculated previously if (!vars.repay && !vars.pull) { result_.newLup = _lup(deposits_, result_.poolDebt); } diff --git a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol index 9433fb5f5..8d6a3aaf9 100644 --- a/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol +++ b/tests/forge/ERC20Pool/ERC20PoolLiquidationsTake.t.sol @@ -1891,3 +1891,100 @@ contract ERC20PoolLiquidationsTakeTest is ERC20HelperContract { }); } } + +contract ERC20PoolLiquidationsTakeAndRepayAllDebtInPoolTest is ERC20HelperContract { + + address internal _lender; + address internal _borrower; + address internal _kicker; + address internal _taker; + + function setUp() external { + _lender = makeAddr("lender"); + _borrower = makeAddr("borrower"); + _kicker = makeAddr("kicker"); + _taker = makeAddr("taker"); + + _mintQuoteAndApproveTokens(_lender, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_borrower, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_kicker, 1_000_000 * 1e18); + _mintQuoteAndApproveTokens(_taker, 1_000_000 * 1e18); + + _mintCollateralAndApproveTokens(_borrower, 150_000 * 1e18); + + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2690 + }); + _addInitialLiquidity({ + from: _lender, + amount: 1_000 * 1e18, + index: 2700 + }); + } + + function testTakeAuctionRepaidAmountGreaterThanPoolDebt() external tearDown { + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: 0, + amountRepaid: 0, + collateralToPull: 0 + }); + + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 635.189921955815900534 * 1e18, + limitIndex: 7000, + collateralToPledge: 0.428329945169804100 * 1e18 + }); + + skip(3276); + + _repayDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToRepay: type(uint256).max, + amountRepaid: 635.803983894118939950 * 1e18, + collateralToPull: 0.428329945169804100 * 1e18 + }); + + _drawDebtNoLupCheck({ + from: _borrower, + borrower: _borrower, + amountToBorrow: 100 * 1e18, + limitIndex: 7000, + collateralToPledge: 0.067433366047580170 * 1e18 + }); + + skip(964); + skip(86400 * 200); + + _kick({ + from: _kicker, + borrower: _borrower, + debt: 104.162540773774892915 * 1e18, + collateral: 0.067433366047580170 * 1e18, + bond: 1.028765834802714992 * 1e18, + transferAmount: 1.028765834802714992 * 1e18 + }); + + skip(964); + skip(3600 * 3); + + // the calculated repaid amount is with 1 WAD greater than the pool debt + // check that take works and doesn't overflow + _take({ + from: _taker, + borrower: _borrower, + maxCollateral: 0.067433366047580170 * 1e18, + bondChange: 1.028765834802714992 * 1e18, + givenAmount: 111.455789568155429076 * 1e18, + collateralTaken: 0.010471063560951988 * 1e18, + isReward: false + }); + + } +} diff --git a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol index bfe81a130..70a3caf8b 100644 --- a/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolCollateral.t.sol @@ -576,7 +576,7 @@ contract ERC721PoolCollateralTest is ERC721HelperContract { _kick({ from: _lender, borrower: _borrower, - debt: 598.174133241016922933 * 1e18, + debt: 598.174133241016922932 * 1e18, collateral: 2.0 * 1e18, bond: 5.907892673985352325 * 1e18, transferAmount: 5.907892673985352325 * 1e18 diff --git a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol index 214114822..a86c14d40 100644 --- a/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol +++ b/tests/forge/ERC721Pool/ERC721PoolInterest.t.sol @@ -325,8 +325,8 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { expectedBorrower1Debt = 8_008.057964091143327677 * 1e18; expectedBorrower2Debt = 2_752.707077245346929053 * 1e18; - uint256 expectedBorrower3Debt = 2_502.403846153846155000 * 1e18; - expectedPoolDebt = 13_263.168887490336411731 * 1e18; + uint256 expectedBorrower3Debt = 2_502.403846153846154999 * 1e18; + expectedPoolDebt = 13_263.168887490336411730 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); @@ -349,7 +349,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower3, borrowerDebt: expectedBorrower3Debt, borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_640.541083248800813687 * 1e18, + borrowert0Np: 2_640.541083248800813686 * 1e18, borrowerCollateralization: 1.197213816827790670 * 1e18 }); @@ -368,7 +368,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { liquidityAdded += 1e18; // check pool and borrower debt to confirm interest has accumulated - expectedPoolDebt = 13_263.471703022178416340 * 1e18; + expectedPoolDebt = 13_263.471703022178416339 * 1e18; (poolDebt,,) = _pool.debtInfo(); assertEq(poolDebt, expectedPoolDebt); @@ -395,13 +395,13 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrowerCollateralization: 1.088326500707555859 * 1e18 }); - expectedBorrower3Debt = 2_502.460979313951751742 * 1e18; + expectedBorrower3Debt = 2_502.460979313951751741 * 1e18; _assertBorrower({ borrower: _borrower3, borrowerDebt: expectedBorrower3Debt, borrowerCollateral: 1 * 1e18, - borrowert0Np: 2_640.541083248800813687 * 1e18, + borrowert0Np: 2_640.541083248800813686 * 1e18, borrowerCollateralization: 1.197186483491030227 * 1e18 }); @@ -521,7 +521,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { newLup: 3_010.892022197881557845 * 1e18 }); - expectedDebt = 5_003.894230769230770000 * 1e18; + expectedDebt = 5_003.894230769230769999 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); @@ -529,7 +529,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_726.979669209494250313 * 1e18, + borrowert0Np: 1_726.979669209494250312 * 1e18, borrowerCollateralization: 1.805129295309881815 * 1e18 }); @@ -546,7 +546,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { tokenIds: tokenIdsToAdd }); - expectedDebt = 5_009.449578476990224066 * 1e18; + expectedDebt = 5_009.449578476990224065 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); @@ -554,7 +554,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 4 * 1e18, - borrowert0Np: 1_726.979669209494250313 * 1e18, + borrowert0Np: 1_726.979669209494250312 * 1e18, borrowerCollateralization: 2.404169939255701731 * 1e18 }); @@ -571,7 +571,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { collateralToPull: 1 }); - expectedDebt = 5_014.454664494689841710 * 1e18; + expectedDebt = 5_014.454664494689841709 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); @@ -579,7 +579,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 3 * 1e18, - borrowert0Np: 1_720.257643586910442803 * 1e18, + borrowert0Np: 1_720.257643586910442802 * 1e18, borrowerCollateralization: 1.801327695821111558 * 1e18 }); @@ -595,7 +595,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { newLup: 3_010.892022197881557845 * 1e18 }); - expectedDebt = 6_019.594382773827921758 * 1e18; + expectedDebt = 6_019.594382773827921756 * 1e18; (poolDebt, , ) = _pool.debtInfo(); assertEq(poolDebt, expectedDebt); @@ -603,7 +603,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower, borrowerDebt: expectedDebt, borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_055.969470907112040316 * 1e18, + borrowert0Np: 2_055.969470907112040315 * 1e18, borrowerCollateralization: 1.500545633513497515 * 1e18 }); @@ -619,7 +619,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { from: _borrower, borrower: _borrower, amountToRepay: 7_000 * 1e18, - amountRepaid: 6_024.465544800440916672 * 1e18, + amountRepaid: 6_024.465544800440916669 * 1e18, collateralToPull: 0, newLup: MAX_PRICE }); @@ -631,7 +631,7 @@ contract ERC721PoolSubsetInterestTest is ERC721PoolInterestTest { borrower: _borrower, borrowerDebt: 0, borrowerCollateral: 3 * 1e18, - borrowert0Np: 2_055.969470907112040316 * 1e18, + borrowert0Np: 2_055.969470907112040315 * 1e18, borrowerCollateralization: 1 * 1e18 }); diff --git a/tests/forge/RewardsManager.t.sol b/tests/forge/RewardsManager.t.sol index 048ce433f..960646d1a 100644 --- a/tests/forge/RewardsManager.t.sol +++ b/tests/forge/RewardsManager.t.sol @@ -817,7 +817,7 @@ contract RewardsManagerTest is DSTestPlus { // check available rewards rewardsEarned = _rewardsManager.calculateRewards(tokenIdOne, _poolOne.currentBurnEpoch()); - assertEq(rewardsEarned, 489.772410159936903182 * 1e18); + assertEq(rewardsEarned, 489.772410159936903152 * 1e18); assertGt(rewardsEarned, rewardsEarnedNoUpdate); assertLt(rewardsEarned, Maths.wmul(totalTokensBurned, 0.800000000000000000 * 1e18)); @@ -1180,7 +1180,7 @@ contract RewardsManagerTest is DSTestPlus { updater: _updater, pool: address(_poolOne), depositIndexes: depositIndexes, - reward: 11.241216009399483348 * 1e18 + reward: 11.241216009399483350 * 1e18 }); _triggerReserveAuctions(TriggerReserveAuctionParams({ @@ -1209,24 +1209,24 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), epoch: 1, timestamp: block.timestamp - (52 weeks + 72 hours), - interest: 6447445050021308895, - burned: 81574747191341355205 + interest: 6.447445050021308895 * 1e18, + burned: 81.574747191341355205 * 1e18 }); _assertBurn({ pool: address(_poolOne), epoch: 2, timestamp: block.timestamp - (26 weeks + 48 hours), - burned: 306399067379332449973, - interest: 23974564976746846096 + burned: 306.399067379332450033 * 1e18, + interest: 23.974564976746846096 * 1e18 }); _assertBurn({ pool: address(_poolOne), epoch: 3, timestamp: block.timestamp - 24 hours, - burned: 699814215483322160364, - interest: 55764974712671474765 + burned: 699.814215483322160424 * 1e18, + interest: 55.764974712671474765 * 1e18 }); // both stakers claim rewards @@ -1235,7 +1235,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdOne, claimedArray: _epochsClaimedArray(3, 0), - reward: 58.317851290276861885 * 1e18, + reward: 58.317851290276861890 * 1e18, updateRatesReward: 0 }); @@ -1244,7 +1244,7 @@ contract RewardsManagerTest is DSTestPlus { pool: address(_poolOne), tokenId: tokenIdTwo, claimedArray: _epochsClaimedArray(3, 0), - reward: 291.589256451384309685 * 1e18, + reward: 291.589256451384309710 * 1e18, updateRatesReward: 0 }); }