From 08bcbf2ba1709398d582e215ffb830f14585c795 Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Tue, 7 Nov 2023 13:59:15 -0500 Subject: [PATCH 1/5] this did not work; cannot prevent rate from changing after 12 hours --- .../unit/ERC721Pool/ERC721PoolBorrow.t.sol | 73 +++++++++++++++++++ tests/forge/utils/DSTestPlus.sol | 10 +++ 2 files changed, 83 insertions(+) diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol index 120a71786..7ca73ada5 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol @@ -8,6 +8,7 @@ import 'src/ERC721Pool.sol'; import 'src/libraries/internal/Maths.sol'; import { MAX_FENWICK_INDEX, MAX_PRICE, _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; +import "forge-std/console.sol"; abstract contract ERC721PoolBorrowTest is ERC721HelperContract { address internal _borrower; @@ -390,6 +391,78 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { assertEq(_collateral.balanceOf(address(_pool)), 0); } + function testFirstInterestAccrual() external { + // add liquidity + _addInitialLiquidity({ + from: _lender, + amount: 300 * 1e18, + index: _i100_33 + }); + uint256 snapshot = vm.snapshot(); + + // calculate debt if borrower took a 90 day loan shortly after pool creation + uint loanTerm = 30 days; + uint loanPrincipal = 150 * 1e18; + skip(12 hours); + uint256[] memory tokenIdsToAdd = new uint256[](3); + tokenIdsToAdd[0] = 1; + tokenIdsToAdd[1] = 3; + tokenIdsToAdd[2] = 5; + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: loanPrincipal, + indexLimit: _i100_33, + newLup: _p100_33 + }); + (uint256 borrowRate1, ) = _pool.interestRateInfo(); + assertEq(borrowRate1, 0.045 * 1e18); + skipWithActivity(loanTerm); + (uint256 debt1, , ) = _poolUtils.borrowerInfo(address(_pool), _borrower); + assertGt(debt1, loanPrincipal); + (borrowRate1, ) = _pool.interestRateInfo(); + assertEq(borrowRate1, 0.045 * 1e18); + uint256 borrowFee1 = Maths.wmul(_poolUtils.borrowFeeRate(address(_pool)), loanPrincipal); + return; + + vm.revertTo(snapshot); + + // calculate debt if borrower took a 90 day loan long after pool creation + skip(90 days); + // _pool.updateInterest(); + // (uint256 borrowRate2, ) = _pool.interestRateInfo(); + // assertEq(borrowRate2, 0.045 * 1e18); + _pledgeCollateral({ + from: _borrower, + borrower: _borrower, + tokenIds: tokenIdsToAdd + }); + _borrow({ + from: _borrower, + amount: loanPrincipal, + indexLimit: _i100_33, + newLup: _p100_33 + }); + (uint256 borrowRate2, ) = _pool.interestRateInfo(); + assertEq(borrowRate2, 0.045 * 1e18); + skipWithActivity(loanTerm); + (, uint256 collateralization, uint256 mau, uint256 tu) = _poolUtils.poolUtilizationInfo(address(_pool)); + console.log("CR %s MAU %s TU %s", collateralization, mau, tu); + (uint256 debt2, , ) = _poolUtils.borrowerInfo(address(_pool), _borrower); + assertGt(debt2, loanPrincipal); + (borrowRate2, ) = _pool.interestRateInfo(); + assertEq(borrowRate2, 0.045 * 1e18); + uint256 borrowFee2 = Maths.wmul(_poolUtils.borrowFeeRate(address(_pool)), loanPrincipal); + + // ensure both loans owe the same origination fee and interest + assertEq(borrowFee1, borrowFee2); + // assertEq(debt1, debt2); + } + function testPoolRepayRequireChecks() external tearDown { // add initial quote to the pool _addInitialLiquidity({ diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index d5b945159..b4e092f8d 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -1458,6 +1458,16 @@ abstract contract DSTestPlus is Test, IPoolEvents { _nonce = seed; } + // updates EMAs while advancing time + function skipWithActivity(uint256 timeToSkip) public { + while (timeToSkip > 12 hours) { + skip(12 hours); + _pool.updateInterest(); + timeToSkip -= 12 hours; + } + skip(timeToSkip); + } + function getNextNonce() public returns (uint256) { return _nonce == type(uint256).max ? 0 : ++_nonce; } From 62866c557433dcbd671017c099444463d901cc7b Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Wed, 8 Nov 2023 10:13:49 -0500 Subject: [PATCH 2/5] fixed issue with _updateInterestState --- src/base/Pool.sol | 5 +- .../unit/ERC721Pool/ERC721PoolBorrow.t.sol | 78 ++++++++----------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/src/base/Pool.sol b/src/base/Pool.sol index 0f7cf3504..c864e6f50 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -679,7 +679,6 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { PoolState memory poolState_, uint256 lup_ ) internal { - PoolCommons.updateInterestState(interestState, emaState, deposits, poolState_, lup_); // update pool inflator @@ -691,6 +690,10 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { } else if (poolState_.debt == 0) { inflatorState.inflator = uint208(Maths.WAD); inflatorState.inflatorUpdate = uint48(block.timestamp); + // if the first loan has just been drawn, update the inflator timestamp + // slither-disable-next-line incorrect-equality + } else if (inflatorState.inflator == Maths.WAD && inflatorState.inflatorUpdate != block.timestamp){ + inflatorState.inflatorUpdate = uint48(block.timestamp); } } diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol index 7ca73ada5..bad8f3c38 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolBorrow.t.sol @@ -8,7 +8,6 @@ import 'src/ERC721Pool.sol'; import 'src/libraries/internal/Maths.sol'; import { MAX_FENWICK_INDEX, MAX_PRICE, _priceAt } from 'src/libraries/helpers/PoolHelper.sol'; -import "forge-std/console.sol"; abstract contract ERC721PoolBorrowTest is ERC721HelperContract { address internal _borrower; @@ -398,69 +397,56 @@ contract ERC721SubsetPoolBorrowTest is ERC721PoolBorrowTest { amount: 300 * 1e18, index: _i100_33 }); + // mint some quote token for borrower to repay + _mintAndApproveQuoteTokens(_borrower, 2 * 1e18); uint256 snapshot = vm.snapshot(); - // calculate debt if borrower took a 90 day loan shortly after pool creation - uint loanTerm = 30 days; + uint loanTerm = 1 hours; uint loanPrincipal = 150 * 1e18; - skip(12 hours); + ERC721Pool pool = ERC721Pool(address(_pool)); + + // calculate debt if borrower took a 90 day loan shortly after pool creation uint256[] memory tokenIdsToAdd = new uint256[](3); tokenIdsToAdd[0] = 1; tokenIdsToAdd[1] = 3; tokenIdsToAdd[2] = 5; - _pledgeCollateral({ - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - }); - _borrow({ - from: _borrower, - amount: loanPrincipal, - indexLimit: _i100_33, - newLup: _p100_33 - }); + changePrank(_borrower); + pool.drawDebt(_borrower, loanPrincipal, _i100_33, tokenIdsToAdd); + skip(loanTerm); (uint256 borrowRate1, ) = _pool.interestRateInfo(); - assertEq(borrowRate1, 0.045 * 1e18); - skipWithActivity(loanTerm); - (uint256 debt1, , ) = _poolUtils.borrowerInfo(address(_pool), _borrower); - assertGt(debt1, loanPrincipal); - (borrowRate1, ) = _pool.interestRateInfo(); - assertEq(borrowRate1, 0.045 * 1e18); + assertEq(borrowRate1, 0.05 * 1e18); uint256 borrowFee1 = Maths.wmul(_poolUtils.borrowFeeRate(address(_pool)), loanPrincipal); - return; + (borrowRate1, ) = _pool.interestRateInfo(); + assertEq(borrowRate1, 0.05 * 1e18); + uint256 repaid1 = pool.repayDebt({ + borrowerAddress_: _borrower, + maxQuoteTokenAmountToRepay_: type(uint256).max, + noOfNFTsToPull_: 3, + collateralReceiver_: _borrower, + limitIndex_: _i100_33 + }); vm.revertTo(snapshot); // calculate debt if borrower took a 90 day loan long after pool creation - skip(90 days); - // _pool.updateInterest(); - // (uint256 borrowRate2, ) = _pool.interestRateInfo(); - // assertEq(borrowRate2, 0.045 * 1e18); - _pledgeCollateral({ - from: _borrower, - borrower: _borrower, - tokenIds: tokenIdsToAdd - }); - _borrow({ - from: _borrower, - amount: loanPrincipal, - indexLimit: _i100_33, - newLup: _p100_33 - }); + skip(10 hours); + changePrank(_borrower); + pool.drawDebt(_borrower, loanPrincipal, _i100_33, tokenIdsToAdd); + skip(loanTerm); (uint256 borrowRate2, ) = _pool.interestRateInfo(); - assertEq(borrowRate2, 0.045 * 1e18); - skipWithActivity(loanTerm); - (, uint256 collateralization, uint256 mau, uint256 tu) = _poolUtils.poolUtilizationInfo(address(_pool)); - console.log("CR %s MAU %s TU %s", collateralization, mau, tu); - (uint256 debt2, , ) = _poolUtils.borrowerInfo(address(_pool), _borrower); - assertGt(debt2, loanPrincipal); - (borrowRate2, ) = _pool.interestRateInfo(); - assertEq(borrowRate2, 0.045 * 1e18); + assertEq(borrowRate2, 0.05 * 1e18); uint256 borrowFee2 = Maths.wmul(_poolUtils.borrowFeeRate(address(_pool)), loanPrincipal); + uint256 repaid2 = pool.repayDebt({ + borrowerAddress_: _borrower, + maxQuoteTokenAmountToRepay_: type(uint256).max, + noOfNFTsToPull_: 3, + collateralReceiver_: _borrower, + limitIndex_: _i100_33 + }); // ensure both loans owe the same origination fee and interest assertEq(borrowFee1, borrowFee2); - // assertEq(debt1, debt2); + assertEq(repaid1, repaid2); } function testPoolRepayRequireChecks() external tearDown { From 102725fd13efb1693acbb19cf2b4bf501489977f Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Wed, 8 Nov 2023 11:33:06 -0500 Subject: [PATCH 3/5] fixed unit tests --- .../unit/ERC20Pool/ERC20PoolBorrow.t.sol | 92 +++++------ .../ERC20PoolInterestRateAndEMAs.t.sol | 20 +-- .../unit/ERC20Pool/ERC20PoolQuoteToken.t.sol | 10 +- .../unit/ERC721Pool/ERC721PoolEMAs.t.sol | 154 +++++++++--------- .../unit/Positions/PositionManager.t.sol | 4 +- 5 files changed, 140 insertions(+), 140 deletions(-) diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol index 904869fdf..bfe1c3e2e 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolBorrow.t.sol @@ -331,7 +331,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { newLup: 2_981.007422784467321543 * 1e18 }); - uint256 expectedDebt = 21_046.123595032677924434 * 1e18; + uint256 expectedDebt = 21_020.192307692307702000 * 1e18; _assertPool( PoolParams({ @@ -339,11 +339,11 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { lup: 2_981.007422784467321543 * 1e18, poolSize: 50_000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.060070845235984474 * 1e18, + encumberedCollateral: 7.051372011699988577 * 1e18, poolDebt: expectedDebt, actualUtilization: 0.000000000000000000 * 1e18, targetUtilization: 1.000000000000000000 * 1e18, - minDebtAmount: 2_104.612359503267792443 * 1e18, + minDebtAmount: 2_102.0192307692307702000 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.045 * 1e18, @@ -355,7 +355,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 484.222578900118175410 * 1e18, - borrowerCollateralization: 7.082081907682151400 * 1e18 + borrowerCollateralization: 7.090818626082626625 * 1e18 }); skip(10 days); @@ -366,19 +366,19 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { amount: 10 * 1e18 }); - expectedDebt = 21_072.086872169016071673 * 1e18; + expectedDebt = 21_046.123595032677924434 * 1e18; _assertPool( PoolParams({ - htp: 351.201447869483601195 * 1e18, + htp: 350.768726583877965407 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_044.110379805202100000 * 1e18, + poolSize: 50_022.041594239314650000 * 1e18, pledgedCollateral: 60 * 1e18, - encumberedCollateral: 7.068780409975037237 * 1e18, + encumberedCollateral: 7.060070845235984474 * 1e18, poolDebt: expectedDebt, actualUtilization: 0.420403445225801443 * 1e18, targetUtilization: 0.141027440233999772 * 1e18, - minDebtAmount: 2_107.208687216901607167 * 1e18, + minDebtAmount: 2_104.612359503267792443 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.0495 * 1e18, @@ -390,9 +390,9 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 60 * 1e18, borrowert0Np: 403.518815750098479508 * 1e18, - borrowerCollateralization: 8.488027144729466085 * 1e18 + borrowerCollateralization: 8.498498289218581680 * 1e18 }); - _assertLenderInterest(liquidityAdded, 44.110379805202100000 * 1e18); + _assertLenderInterest(liquidityAdded, 22.041594239314650000 * 1e18); skip(10 days); @@ -404,19 +404,19 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { collateralToPull: 10 * 1e18 }); - expectedDebt = 21_100.683472334824303370 * 1e18; + expectedDebt = 21_074.684960840362729425 * 1e18; _assertPool( PoolParams({ - htp: 422.013669446696486067 * 1e18, + htp: 421.493699216807254588 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_069.130567554535450000 * 1e18, + poolSize: 50_047.030954189176600000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.078373341528054648 * 1e18, + encumberedCollateral: 7.069651957174647985 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421070265420802644 * 1e18, - targetUtilization: 0.120628314442263426 * 1e18, - minDebtAmount: 2_110.068347233482430337 * 1e18, + actualUtilization: 0.420736997299034551 * 1e18, + targetUtilization: 0.120500759095098154 * 1e18, + minDebtAmount: 2_107.468496084036272943 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.05445 * 1e18, @@ -428,9 +428,9 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 483.986975517230275430 * 1e18, - borrowerCollateralization: 7.063769822178689107 * 1e18 + borrowerCollateralization: 7.072483950112624325 * 1e18 }); - _assertLenderInterest(liquidityAdded, 69.130567554535450000 * 1e18); + _assertLenderInterest(liquidityAdded, 47.030954189176600000 * 1e18); skip(10 days); @@ -439,19 +439,19 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { emit LoanStamped(_borrower); _pool.stampLoan(); - expectedDebt = 21_132.184557783880298441 * 1e18; + expectedDebt = 21_106.147233265508423039 * 1e18; _assertPool( PoolParams({ - htp: 422.643691155677605969 * 1e18, + htp: 422.122944665310168461 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_096.693504733499050000 * 1e18, + poolSize: 50_074.559175998268400000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.088940603188755848 * 1e18, + encumberedCollateral: 7.080206198732274753 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421430993826609139 * 1e18, - targetUtilization: 0.138725200998853604 * 1e18, - minDebtAmount: 2_113.218455778388029844 * 1e18, + actualUtilization: 0.421097606428009905 * 1e18, + targetUtilization: 0.138557135730267727 * 1e18, + minDebtAmount: 2_110.614723326550842304 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.059895 * 1e18, @@ -463,27 +463,27 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 486.269617724627962428 * 1e18, - borrowerCollateralization: 7.053240081812639175 * 1e18 + borrowerCollateralization: 7.061941219869076860 * 1e18 }); - _assertLenderInterest(liquidityAdded, 96.693504733499050000 * 1e18); + _assertLenderInterest(liquidityAdded, 74.559175998268400000 * 1e18); skip(10 days); _updateInterest(); - expectedDebt = 21_166.890071570436649845 * 1e18; + expectedDebt = 21_140.809985797421809167 * 1e18; _assertPool( PoolParams({ - htp: 423.337801431408732997 * 1e18, + htp: 422.816199715948436183 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_127.061165904636850000 * 1e18, + poolSize: 50_104.888588646515350000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.100582812973708010 * 1e18, + encumberedCollateral: 7.091834063952260008 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421827930356991768 * 1e18, - targetUtilization: 0.141358334848097873 * 1e18, - minDebtAmount: 2_116.689007157043664985 * 1e18, + actualUtilization: 0.421494418755518513 * 1e18, + targetUtilization: 0.141184558710452235 * 1e18, + minDebtAmount: 2_114.080998579742180917 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.0658845 * 1e18, @@ -495,25 +495,25 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 486.269617724627962428 * 1e18, - borrowerCollateralization: 7.041675495797803839 * 1e18 + borrowerCollateralization: 7.050362367352844516 * 1e18 }); - _assertLenderInterest(liquidityAdded, 127.061165904636850000 * 1e18); + _assertLenderInterest(liquidityAdded, 104.888588646515350000 * 1e18); skip(10 days); - expectedDebt = 21_205.131971958652447704 * 1e18; + expectedDebt = 21_179.004767688830766408 * 1e18; _assertPool( PoolParams({ - htp: 423.337801431408732997 * 1e18, + htp: 422.816199715948436183 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 50_127.061165904636850000 * 1e18, + poolSize: 50_104.888588646515350000 * 1e18, pledgedCollateral: 50 * 1e18, - encumberedCollateral: 7.113411328627820409 * 1e18, + encumberedCollateral: 7.104646773373738867 * 1e18, poolDebt: expectedDebt, - actualUtilization: 0.421827930356991768 * 1e18, - targetUtilization: 0.141358334848097873 * 1e18, - minDebtAmount: 2_120.513197195865244770 * 1e18, + actualUtilization: 0.421494418755518513 * 1e18, + targetUtilization: 0.141184558710452235 * 1e18, + minDebtAmount: 2_117.900476768883076641 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.0658845 * 1e18, @@ -525,7 +525,7 @@ contract ERC20PoolBorrowTest is ERC20HelperContract { borrowerDebt: expectedDebt, borrowerCollateral: 50 * 1e18, borrowert0Np: 486.269617724627962428 * 1e18, - borrowerCollateralization: 7.028976350457301320 * 1e18 + borrowerCollateralization: 7.037647555876562588 * 1e18 }); } diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol index 3971cd081..ffd369a2a 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolInterestRateAndEMAs.t.sol @@ -99,11 +99,11 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { lup: 2_981.007422784467321543 * 1e18, poolSize: 110_000 * 1e18, pledgedCollateral: 100 * 1e18, - encumberedCollateral: 15.464917089564537419 * 1e18, - poolDebt: 46_101.032636738246882092 * 1e18, + encumberedCollateral: 15.445862501819022598 * 1e18, + poolDebt: 46_044.230769230769252000 * 1e18, actualUtilization: 0.000000000000000000 * 1e18, targetUtilization: 1.000000000000000000 * 1e18, - minDebtAmount: 4_610.103263673824688209 * 1e18, + minDebtAmount: 4_604.423076923076925200 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.045 * 1e18, @@ -125,15 +125,15 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { _assertPool( PoolParams({ - htp: 461.043482411861952490 * 1e18, + htp: 460.475422884660056974 * 1e18, lup: 2_981.007422784467321543 * 1e18, - poolSize: 110_051.099851162112050000 * 1e18, + poolSize: 110_002.814791349950950000 * 1e18, pledgedCollateral: 100 * 1e18, - encumberedCollateral: 15.466029332500467905 * 1e18, - poolDebt: 46_104.348241186195248997 * 1e18, + encumberedCollateral: 15.446973374341487926 * 1e18, + poolDebt: 46_047.542288466005697371 * 1e18, actualUtilization: 0.332788778646025592 * 1e18, targetUtilization: 0.154458625018190226 * 1e18, - minDebtAmount: 4_610.434824118619524900 * 1e18, + minDebtAmount: 4_604.754228846600569737 * 1e18, loans: 1, maxBorrower: _borrower, interestRate: 0.045 * 1e18, @@ -156,7 +156,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { from: _borrower, borrower: _borrower, amountToRepay: 46_200 * 1e18, - amountRepaid: 46_104.348241186195248997 * 1e18, + amountRepaid: 46_047.542288466005697371 * 1e18, collateralToPull: 0, newLup: MAX_PRICE }); @@ -165,7 +165,7 @@ contract ERC20PoolInterestRateTestAndEMAs is ERC20HelperContract { PoolParams({ htp: 0, lup: MAX_PRICE, - poolSize: 110_051.099851162112050000 * 1e18, + poolSize: 110_002.814791349950950000 * 1e18, pledgedCollateral: 100 * 1e18, encumberedCollateral: 0, poolDebt: 0, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol index 75a129d05..ee1eab4c5 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolQuoteToken.t.sol @@ -737,7 +737,7 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { amount: withdrawal1, index: 1606, newLup: _priceAt(1663), - lpRedeem: 1_699.989134088091859893 * 1e18 + lpRedeem: 1_699.992715594878010449 * 1e18 }); // lender removes all quote token, including interest, from the bucket @@ -745,13 +745,13 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { assertGt(_priceAt(1606), _htp()); - uint256 expectedWithdrawal2 = 1_700.138879728085771159 * 1e18; + uint256 expectedWithdrawal2 = 1_700.131715815996933416 * 1e18; _removeAllLiquidity({ from: _lender, amount: expectedWithdrawal2, index: 1606, newLup: _priceAt(1663), - lpRedeem: 1_700.010865911908140107 * 1e18 + lpRedeem: 1_700.007284405121989551 * 1e18 }); assertEq(_quote.balanceOf(_lender), lenderBalanceBefore + withdrawal1 + expectedWithdrawal2); @@ -773,8 +773,8 @@ contract ERC20PoolQuoteTokenTest is ERC20HelperContract { index: 1663, lpBalance: 3_400 * 1e18, collateral: 0, - deposit: 3_400.256025995910604600 * 1e18, - exchangeRate: 1.000075301763503119 * 1e18 + deposit: 3_400.248861755391155000 * 1e18, + exchangeRate: 1.000073194633938575 * 1e18 }); _assertLenderLpBalance({ lender: _lender, diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol index 80337f4d5..e20558741 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolEMAs.t.sol @@ -77,11 +77,11 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { lup: _p1505_26, poolSize: 15_000 * 1e18, pledgedCollateral: 6 * 1e18, - encumberedCollateral: 4.620028820788372636 * 1e18, // 6 / 1.3 = 4.62 - poolDebt: 6_954.361808414458420695 * 1e18, + encumberedCollateral: 4.619817865384615387 * 1e18, // 6 / 1.3 = 4.62 + poolDebt: 6_954.044264896858085302 * 1e18, actualUtilization: 0.000000000000000000 * 1e18, // moving -> 6_947 / 10_000 (meaningful) = 0.7 targetUtilization: 1.000000000000000000 * 1e18, - minDebtAmount: 695.436180841445842070 * 1e18, // debt / 10; only one loan, so not enforced + minDebtAmount: 695.404426489685808530 * 1e18, // debt / 10; only one loan, so not enforced loans: 1, maxBorrower: address(_borrower), interestRate: 0.05 * 1e18, @@ -89,11 +89,11 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }) ); _assertEMAs({ - debtColEma: 0.000000000000000000 * 1e18, // 6_954^2 / 6 ~= 8_059_686 + 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 + 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 + depositEma: 11_850.197375262816985000 * 1e18 // ...so it is moving down toward 10_000 }); } @@ -108,11 +108,11 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { 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 + encumberedCollateral: 4.619896972532198353 * 1e18, // small increase due to pending interest + poolDebt: 6_954.163342016777374652 * 1e18, // small increase due to pending interest actualUtilization: 0.000000000000000000 * 1e18, targetUtilization: 1.000000000000000000 * 1e18, // debtColEma / lupt0DebtEma - minDebtAmount: 695.448089097181325816 * 1e18, // small increase due to pending interest + minDebtAmount: 695.416334201677737465 * 1e18, // small increase due to pending interest loans: 1, maxBorrower: address(_borrower), interestRate: 0.05 * 1e18, @@ -130,15 +130,15 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _pool.updateInterest(); _assertPool( PoolParams({ - htp: 1_159.080148495302209694 * 1e18, + htp: 1_159.027223669462895776 * 1e18, lup: _p1505_26, - poolSize: 15_000.371132163711890000 * 1e18, // first interest accrual + poolSize: 15_000.101215551931390000 * 1e18, // first interest accrual pledgedCollateral: 6 * 1e18, - encumberedCollateral: 4.620107931548236591 * 1e18, - poolDebt: 6_954.480890971813258160 * 1e18, // pending interest now equals current interest + encumberedCollateral: 4.619896972532198353 * 1e18, + poolDebt: 6_954.163342016777374652 * 1e18, // pending interest now equals current interest actualUtilization: 0.095745083902338016 * 1e18, targetUtilization: 0.769969644230769231 * 1e18, - minDebtAmount: 695.448089097181325816 * 1e18, + minDebtAmount: 695.416334201677737465 * 1e18, loans: 1, maxBorrower: address(_borrower), interestRate: 0.05 * 1e18, @@ -157,10 +157,10 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { skip(9 hours); // 12 hours since debt was drawn _pool.updateInterest(); _assertEMAs({ - debtColEma: 759_883.557390504783896613 * 1e18, // updated for interest accrual + debtColEma: 759_857.214782711948426497 * 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 + debtEma: 3_477.070405889227128676 * 1e18, // updated for interest accrual + depositEma: 10_925.139720056087061594 * 1e18 // still moving toward 10_000 }); (interestRate, ) = _pool.interestRateInfo(); assertEq(interestRate, 0.045 * 1e18); @@ -176,44 +176,44 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 4 hours, - mau: 0.397622119994472546 * 1e18, // dropping from 60% to 35% - tu: 0.770035415811601449 * 1e18, // still at 77% + mau: 0.397610987303759359 * 1e18, // dropping from 60% to 35% + tu: 0.770004681960247602 * 1e18, // still at 77% rate: 0.045 * 1e18 }); (, , , uint256 depositEma) = _pool.emasInfo(); - assertEq(depositEma, 12_582.608507963702724933 * 1e18); // now moving toward 20_000 + assertEq(depositEma, 12_582.428657317994850387 * 1e18); // now moving toward 20_000 _skipAndAccrue({ time: 20 hours, // 24 hours since liquidity was added - mau: 0.358933852890687729 * 1e18, // still dropping toward 35% - tu: 0.770067458236015074 * 1e18, // still at 77% + mau: 0.358922672624231859 * 1e18, // still dropping toward 35% + tu: 0.770034423541948909 * 1e18, // still at 77% rate: 0.0405 * 1e18 // dropping at 4.05% }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 17_664.344669688571758688 * 1e18); // still moving toward 20_000 + assertEq(depositEma, 17_664.103102633595201121 * 1e18); // still moving toward 20_000 _skipAndAccrue({ time: 2 days, // 3 days since liquidity was added - mau: 0.348388356770742011 * 1e18, // reached 35% - tu: 0.770135325994564531 * 1e18, // still at 77% + mau: 0.348377171409880699 * 1e18, // reached 35% + tu: 0.770100960789580357 * 1e18, // still at 77% rate: 0.03645 * 1e18 // second interest rate drop }); (, , , depositEma) = _pool.emasInfo(); - assertEq(depositEma, 19_855.532581473734007628 * 1e18); // reached (sort of) 20_000 + assertEq(depositEma, 19_855.264382859478015763 * 1e18); // reached (sort of) 20_000 _assertPool( PoolParams({ - htp: 1_159.575642053959188547 * 1e18, + htp: 1_159.522694603359774153 * 1e18, lup: _p1505_26, - poolSize: 25_002.955913967460376246 * 1e18, // reflects additional 10_000 deposit + poolSize: 25_002.685877380424023738 * 1e18, // reflects additional 10_000 deposit pledgedCollateral: 6 * 1e18, - encumberedCollateral: 4.622082975054377226 * 1e18, - poolDebt: 6_957.453852323755131281 * 1e18, - actualUtilization: 0.348388356770742011 * 1e18, // dropped to 35% as expected - targetUtilization: 0.770135325994564531 * 1e18, - minDebtAmount: 695.745385232375513128* 1e18, + encumberedCollateral: 4.621871925855762982 * 1e18, + poolDebt: 6_957.136167620158644917 * 1e18, + actualUtilization: 0.348377171409880699 * 1e18, // dropped to 35% as expected + targetUtilization: 0.770100960789580357 * 1e18, + minDebtAmount: 695.713616762015864492 * 1e18, loans: 1, maxBorrower: address(_borrower), - interestRate: 0.03645 * 1e18, // dropped twice + interestRate: 0.03645 * 1e18, // dropped twice interestRateUpdate: _startTime + 98 hours }) ); @@ -235,41 +235,41 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _skipAndAccrue({ time: 3 hours, - mau: 0.436398947261022405 * 1e18, // rising from 35% to 90% - tu: 0.794802131238362891 * 1e18, // increases as collateralization decreases + mau: 0.436388951653038759 * 1e18, // rising from 35% to 90% + tu: 0.794770224325259086 * 1e18, // increases as collateralization decreases rate: 0.03645 * 1e18 }); (, , uint256 debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 8_675.169506576032073392 * 1e18); // increasing from 7_000 to 18_000 + assertEq(debtEma, 8_674.853637478768045307 * 1e18); // increasing from 7_000 to 18_000 _skipAndAccrue({ time: 9 hours, - mau: 0.624275651572709578 * 1e18, // still rising to 90% - tu: 0.846215553223533151 * 1e18, + mau: 0.624268195008900647 * 1e18, // still rising to 90% + tu: 0.846188282239484818 * 1e18, rate: 0.03645 * 1e18 }); (, , debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 12_441.391312587344397940 * 1e18); // increasing from 7_000 to 18_000 + assertEq(debtEma, 12_441.074705842074347362 * 1e18); // increasing from 7_000 to 18_000 _skipAndAccrue({ time: 4 days, - mau: 0.897069303670436098 * 1e18, // reached 90% - tu: 0.966852816219664605 * 1e18, + mau: 0.897065532020579682 * 1e18, // reached 90% + tu: 0.966833833545113868 * 1e18, rate: 0.032805 * 1e18 }); (, , debtEma, ) = _pool.emasInfo(); - assertEq(debtEma, 17_944.480736533919717209 * 1e18); // reached 18_000 + assertEq(debtEma, 17_944.163040240190307741 * 1e18); // reached 18_000 _assertPool( PoolParams({ - htp: 1_497.769957757345433425 * 1e18, + htp: 1_497.743472132515234070 * 1e18, lup: _p1505_26, - poolSize: 25_010.141517477798670592 * 1e18, + poolSize: 25_009.871348025868322555 * 1e18, pledgedCollateral: 12 * 1e18, // 6 additional NFTs deposited - encumberedCollateral: 11.940259472915000621 * 1e18, // all 12 NFTs are encumbered - poolDebt: 17_973.239493088145201105 * 1e18, // includes new debt - actualUtilization: 0.897069303670436098 * 1e18, - targetUtilization: 0.966852816219664605 * 1e18, - minDebtAmount: 1_797.323949308814520111 * 1e18, + encumberedCollateral: 11.940048328853032398 * 1e18, // all 12 NFTs are encumbered + poolDebt: 17_972.921665590182808833 * 1e18, // includes new debt + actualUtilization: 0.897065532020579682 * 1e18, + targetUtilization: 0.966833833545113868 * 1e18, + minDebtAmount: 1_797.292166559018280883 * 1e18, loans: 1, maxBorrower: address(_borrower), interestRate: 0.032805 * 1e18, @@ -290,15 +290,15 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { }); _skipAndAccrue({ time: 40 hours, // 2 days after liquidity was added - mau: 0.677110233556701963 * 1e18, // 7_647 / 10_000 ~= 76% - tu: 0.847585126397651853 * 1e18, // starting at 77% + mau: 0.677098311290654158 * 1e18, // 7_647 / 10_000 ~= 76% + tu: 0.847549967163692311 * 1e18, // starting at 77% rate: 0.05 * 1e18 }); _assertEMAs({ - 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 + debtColEma: 2_745_421.852361791542203581 * 1e18, // reflects newly drawn debt + lupt0DebtEma: 3_239_244.833610561710979058 * 1e18, // unchanged from setup + debtEma: 6_895.273194262173362837 * 1e18, // increasing toward 7_647 + depositEma: 10_183.562828740062354918 * 1e18 // decreasing toward 10_000 }); // bad actor comes along and deposits large amount for 5 minutes, and then withdraws @@ -311,49 +311,49 @@ contract ERC721PoolEMAsTest is ERC721HelperContract { _pool.updateInterest(); // not really needed, since removing liquidity will trigger rate update _removeAllLiquidity({ from: _attacker, - amount: 150_000.003062917635863984 * 1e18, + amount: 150_000.003062791424711266 * 1e18, index: _i1505_26, newLup: _p1505_26, - lpRedeem: 149_973.669906855426845472 * 1e18 + lpRedeem: 149_977.718198879171195883 * 1e18 }); uint256 rateChangeTs = block.timestamp; _skipAndAccrue({ time: 12, // skip a single block - mau: 0.632791692026653958 * 1e18, // impacted, enough to cause rate change - tu: 0.847585617691556722 * 1e18, + mau: 0.632779561268705826 * 1e18, // impacted, enough to cause rate change + tu: 0.847550458437217496 * 1e18, rate: 0.045 * 1e18 // rate changed }); _assertEMAs({ - 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 + debtColEma: 2_750_442.276033142114157597 * 1e18, + lupt0DebtEma: 3_245_166.407088766607440379 * 1e18, + debtEma: 6_899.074247951901688984 * 1e18, + depositEma: 10_902.808292542580376732 * 1e18 // still noticably impacted }); _skipAndAccrue({ time: 12 hours, - mau: 0.696306196911713144 * 1e18, // moving back toward 75% - tu: 0.847637823306888876 * 1e18, + mau: 0.696294407937553020 * 1e18, // moving back toward 75% + tu: 0.847602661886974316 * 1e18, rate: 0.045 * 1e18 }); _assertEMAs({ - debtColEma: 3_412_160.847313164565839074 * 1e18, - lupt0DebtEma: 4_025_493.853024754985190842 * 1e18, - debtEma: 7_278.073513801178223507 * 1e18, - depositEma: 10_452.403764437541109496 * 1e18 // moving down back to 10_000 + debtColEma: 3_412_033.566087523366578909 * 1e18, + lupt0DebtEma: 4_025_510.677953143880612044 * 1e18, + debtEma: 7_277.771607265212861084 * 1e18, + depositEma: 10_452.147143939030295899 * 1e18 // moving down back to 10_000 }); _assertPool( PoolParams({ - htp: 1_276.209765166823398404 * 1e18, + htp: 1_276.156825873391412088 * 1e18, lup: _p1505_26, - poolSize: 15_002.177276783057210001 * 1e18, + poolSize: 15_001.907285108568550001 * 1e18, pledgedCollateral: 6 * 1e18, - encumberedCollateral: 5.086988044805126619 * 1e18, - poolDebt: 7_657.258591000940390423 * 1e18, // 7_647 principal plus some interest - actualUtilization: 0.696306196911713144 * 1e18, - targetUtilization: 0.847637823306888876 * 1e18, - minDebtAmount: 765.725859100094039042 * 1e18, + encumberedCollateral: 5.086777028121083505 * 1e18, + poolDebt: 7_656.940955240348472526 * 1e18, // 7_647 principal plus some interest + actualUtilization: 0.696294407937553020 * 1e18, + targetUtilization: 0.847602661886974316 * 1e18, + minDebtAmount: 765.694095524034847253 * 1e18, loans: 1, maxBorrower: address(_borrower), interestRate: 0.045 * 1e18, diff --git a/tests/forge/unit/Positions/PositionManager.t.sol b/tests/forge/unit/Positions/PositionManager.t.sol index 926f69913..ba1d7826a 100644 --- a/tests/forge/unit/Positions/PositionManager.t.sol +++ b/tests/forge/unit/Positions/PositionManager.t.sol @@ -1950,7 +1950,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _assertLenderLpBalance({ lender: address(_positionManager), index: moveIndex, - lpBalance: 1_999.865897356084855977 * 1e18, + lpBalance: 1_999.862011303232353901 * 1e18, depositTime: _startTime }); skip(1 weeks); @@ -1969,7 +1969,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract from: borrower, borrower: borrower, amountToRepay: type(uint256).max, - amountRepaid: 1_002.608307827389905518 * 1e18, + amountRepaid: 1_002.596862592399049586 * 1e18, collateralToPull: 250 * 1e18, newLup: MAX_PRICE }); From dd3be42db9b1d43f0b7442f04ec98a08f9d87deb Mon Sep 17 00:00:00 2001 From: Ed Noepel Date: Wed, 8 Nov 2023 11:33:21 -0500 Subject: [PATCH 4/5] removed unused test utility --- tests/forge/utils/DSTestPlus.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/forge/utils/DSTestPlus.sol b/tests/forge/utils/DSTestPlus.sol index b4e092f8d..d5b945159 100644 --- a/tests/forge/utils/DSTestPlus.sol +++ b/tests/forge/utils/DSTestPlus.sol @@ -1458,16 +1458,6 @@ abstract contract DSTestPlus is Test, IPoolEvents { _nonce = seed; } - // updates EMAs while advancing time - function skipWithActivity(uint256 timeToSkip) public { - while (timeToSkip > 12 hours) { - skip(12 hours); - _pool.updateInterest(); - timeToSkip -= 12 hours; - } - skip(timeToSkip); - } - function getNextNonce() public returns (uint256) { return _nonce == type(uint256).max ? 0 : ++_nonce; } From f1ad5cbdba46f90d4ceaf88c1ac56f4cc7e61602 Mon Sep 17 00:00:00 2001 From: Ed Noepel <46749157+EdNoepel@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:40:16 -0500 Subject: [PATCH 5/5] Contract size mitigation (#979) * moved inflator state update logic to PoolHelper * moved flashLoan impl into PoolCommons --- src/ERC721Pool.sol | 1 - src/base/FlashloanablePool.sol | 26 ++------------ src/base/Pool.sol | 19 +++-------- src/libraries/external/PoolCommons.sol | 47 ++++++++++++++++++++++++++ src/libraries/helpers/PoolHelper.sol | 33 +++++++++++++++++- 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/ERC721Pool.sol b/src/ERC721Pool.sol index 3792eaed2..003d5ee80 100644 --- a/src/ERC721Pool.sol +++ b/src/ERC721Pool.sol @@ -344,7 +344,6 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { // Total collateral in buckets meets the requested removal amount, noOfNFTsToRemove_ _transferFromPoolToAddress(msg.sender, bucketTokenIds, noOfNFTsToRemove_); } - } /** diff --git a/src/base/FlashloanablePool.sol b/src/base/FlashloanablePool.sol index a6d35f176..1fd28104d 100644 --- a/src/base/FlashloanablePool.sol +++ b/src/base/FlashloanablePool.sol @@ -6,6 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Pool } from './Pool.sol'; +import { PoolCommons } from '../libraries/external/PoolCommons.sol'; import { IERC3156FlashBorrower } from '../interfaces/pool/IERC3156FlashBorrower.sol'; /** @@ -32,30 +33,7 @@ abstract contract FlashloanablePool is Pool { bytes calldata data_ ) external virtual override nonReentrant returns (bool success_) { if (!_isFlashloanSupported(token_)) revert FlashloanUnavailableForToken(); - - IERC20 tokenContract = IERC20(token_); - - uint256 initialBalance = tokenContract.balanceOf(address(this)); - - tokenContract.safeTransfer( - address(receiver_), - amount_ - ); - - if (receiver_.onFlashLoan(msg.sender, token_, amount_, 0, data_) != - keccak256("ERC3156FlashBorrower.onFlashLoan")) revert FlashloanCallbackFailed(); - - tokenContract.safeTransferFrom( - address(receiver_), - address(this), - amount_ - ); - - if (tokenContract.balanceOf(address(this)) != initialBalance) revert FlashloanIncorrectBalance(); - - success_ = true; - - emit Flashloan(address(receiver_), token_, amount_); + success_ = PoolCommons.flashLoan(receiver_, token_, amount_, data_); } /** diff --git a/src/base/Pool.sol b/src/base/Pool.sol index d4c41d70a..2f6d873fd 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.18; import { Clone } from '@clones/Clone.sol'; import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuard.sol'; import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol'; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -51,6 +50,7 @@ import { } from '../interfaces/pool/commons/IPoolInternals.sol'; import { + _determineInflatorState, _priceAt, _roundToScale } from '../libraries/helpers/PoolHelper.sol'; @@ -688,20 +688,9 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { emit InterestUpdateFailure(); } - // update pool inflator - if (poolState_.isNewInterestAccrued) { - inflatorState.inflator = SafeCast.toUint208(poolState_.inflator); - inflatorState.inflatorUpdate = SafeCast.toUint48(block.timestamp); - // if the debt in the current pool state is 0, also update the inflator and inflatorUpdate fields in inflatorState - // slither-disable-next-line incorrect-equality - } else if (poolState_.debt == 0) { - inflatorState.inflator = SafeCast.toUint208(Maths.WAD); - inflatorState.inflatorUpdate = SafeCast.toUint48(block.timestamp); - // if the first loan has just been drawn, update the inflator timestamp - // slither-disable-next-line incorrect-equality - } else if (inflatorState.inflator == Maths.WAD && inflatorState.inflatorUpdate != block.timestamp){ - inflatorState.inflatorUpdate = SafeCast.toUint48(block.timestamp); - } + (uint208 newInflator, bool updateTimestamp) = _determineInflatorState(poolState_, inflatorState); + inflatorState.inflator = newInflator; + if (updateTimestamp) inflatorState.inflatorUpdate = uint48(block.timestamp); } /** diff --git a/src/libraries/external/PoolCommons.sol b/src/libraries/external/PoolCommons.sol index 164dff91a..9ab7f8d79 100644 --- a/src/libraries/external/PoolCommons.sol +++ b/src/libraries/external/PoolCommons.sol @@ -5,10 +5,16 @@ pragma solidity 0.8.18; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { PRBMathUD60x18 } from "@prb-math/contracts/PRBMathUD60x18.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + + import { InterestState, EmaState, PoolState, DepositsState } from '../../interfaces/pool/commons/IPoolState.sol'; +import { IERC3156FlashBorrower } from '../../interfaces/pool/IERC3156FlashBorrower.sol'; import { _dwatp, _indexOf, MAX_FENWICK_INDEX, MIN_PRICE, MAX_PRICE } from '../helpers/PoolHelper.sol'; + import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Loans } from '../internal/Loans.sol'; @@ -21,6 +27,8 @@ import { Maths } from '../internal/Maths.sol'; - pool utilization */ library PoolCommons { + using SafeERC20 for IERC20; + /*****************/ /*** Constants ***/ @@ -40,9 +48,18 @@ library PoolCommons { /**************/ // See `IPoolEvents` for descriptions + event Flashloan(address indexed receiver, address indexed token, uint256 amount); event ResetInterestRate(uint256 oldRate, uint256 newRate); event UpdateInterestRate(uint256 oldRate, uint256 newRate); + /**************/ + /*** Errors ***/ + /**************/ + + // See `IPoolErrors` for descriptions + error FlashloanCallbackFailed(); + error FlashloanIncorrectBalance(); + /*************************/ /*** Local Var Structs ***/ /*************************/ @@ -259,6 +276,36 @@ library PoolCommons { } } + function flashLoan( + IERC3156FlashBorrower receiver_, + address token_, + uint256 amount_, + bytes calldata data_ + ) external returns (bool success_) { + IERC20 tokenContract = IERC20(token_); + + uint256 initialBalance = tokenContract.balanceOf(address(this)); + + tokenContract.safeTransfer( + address(receiver_), + amount_ + ); + + if (receiver_.onFlashLoan(msg.sender, token_, amount_, 0, data_) != + keccak256("ERC3156FlashBorrower.onFlashLoan")) revert FlashloanCallbackFailed(); + + tokenContract.safeTransferFrom( + address(receiver_), + address(this), + amount_ + ); + + if (tokenContract.balanceOf(address(this)) != initialBalance) revert FlashloanIncorrectBalance(); + + success_ = true; + emit Flashloan(address(receiver_), token_, amount_); + } + /**************************/ /*** Internal Functions ***/ /**************************/ diff --git a/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index aa4e17a9f..2dde8fd62 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -4,8 +4,10 @@ pragma solidity 0.8.18; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { PoolType } from '../../interfaces/pool/IPool.sol'; +import { PoolType } from '../../interfaces/pool/IPool.sol'; +import { InflatorState, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Maths } from '../internal/Maths.sol'; @@ -133,6 +135,35 @@ import { Maths } from '../internal/Maths.sol'; return Maths.min(Maths.wdiv(interestRate_, 365 * 1e18), 0.1 * 1e18); } + /** + * @notice Determines how the inflator state should be updated + * @param poolState_ State of the pool after updateInterestState was called. + * @param inflatorState_ Old inflator state. + * @return newInflator_ New inflator value. + * @return updateTimestamp_ `True` if timestamp of last update should be updated. + */ + function _determineInflatorState( + PoolState memory poolState_, + InflatorState memory inflatorState_ + ) view returns (uint208 newInflator_, bool updateTimestamp_) { + newInflator_ = inflatorState_.inflator; + + // update pool inflator + if (poolState_.isNewInterestAccrued) { + newInflator_ = SafeCast.toUint208(poolState_.inflator); + updateTimestamp_ = true; + // if the debt in the current pool state is 0, also update the inflator and inflatorUpdate fields in inflatorState + // slither-disable-next-line incorrect-equality + } else if (poolState_.debt == 0) { + newInflator_ = SafeCast.toUint208(Maths.WAD); + updateTimestamp_ = true; + // if the first loan has just been drawn, update the inflator timestamp + // slither-disable-next-line incorrect-equality + } else if (inflatorState_.inflator == Maths.WAD && inflatorState_.inflatorUpdate != block.timestamp){ + updateTimestamp_ = true; + } + } + /** * @notice Calculates debt-weighted average threshold price. * @param t0Debt_ Pool debt owed by borrowers in `t0` terms.