From 5a92282ee8dcc94b25a94deccabcf5f29b2fc9d0 Mon Sep 17 00:00:00 2001 From: prateek105 Date: Wed, 20 Dec 2023 19:41:22 +0530 Subject: [PATCH 1/3] Moved '_determineInflatorState' and '_getCollateralDustPricePrecisionAdjustment' method logic into Pool contracts, update 'getExchangeRate' visibility to external --- src/ERC20Pool.sol | 21 ++++++-- src/base/Pool.sol | 20 ++++++-- src/libraries/helpers/PoolHelper.sol | 51 +------------------ src/libraries/internal/Buckets.sol | 2 +- .../unit/ERC20Pool/ERC20PoolPrecision.t.sol | 9 ---- 5 files changed, 34 insertions(+), 69 deletions(-) diff --git a/src/ERC20Pool.sol b/src/ERC20Pool.sol index 6dfc1b378..dcc4f3a81 100644 --- a/src/ERC20Pool.sol +++ b/src/ERC20Pool.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.18; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { IERC20Pool, @@ -36,7 +37,6 @@ import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; import { FlashloanablePool } from './base/FlashloanablePool.sol'; import { - _getCollateralDustPricePrecisionAdjustment, _roundToScale, _roundUpToScale } from './libraries/helpers/PoolHelper.sol'; @@ -493,8 +493,19 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @return Amount of collateral dust amount of the bucket. */ function _bucketCollateralDust(uint256 bucketIndex_) internal pure returns (uint256) { - // price precision adjustment will always be 0 for encumbered collateral - uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex_); + // Price precision adjustment used in calculating collateral dust for a bucket. + // To ensure the accuracy of the exchange rate calculation, buckets with smaller prices require + // larger minimum amounts of collateral. This formula imposes a lower bound independent of token scale. + uint256 pricePrecisionAdjustment; + + // conditional is a gas optimization + if (bucketIndex_ > 3900) { + int256 bucketOffset = int256(bucketIndex_ - 3900); + int256 result = PRBMathSD59x18.sqrt(PRBMathSD59x18.div(bucketOffset * 1e18, int256(36 * 1e18))); + // price precision adjustment will always be 0 for encumbered collateral + pricePrecisionAdjustment = uint256(result / 1e18); + } + // difference between the normalized scale and the collateral token's scale return Maths.max(_getArgUint256(COLLATERAL_SCALE), 10 ** pricePrecisionAdjustment); } diff --git a/src/base/Pool.sol b/src/base/Pool.sol index df23f08e2..536222ea5 100644 --- a/src/base/Pool.sol +++ b/src/base/Pool.sol @@ -7,6 +7,8 @@ import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuar import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol'; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { IPool, @@ -51,7 +53,6 @@ import { import { COLLATERALIZATION_FACTOR, - _determineInflatorState, _priceAt, _roundToScale } from '../libraries/helpers/PoolHelper.sol'; @@ -681,9 +682,20 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { emit InterestUpdateFailure(); } - (uint208 newInflator, bool updateTimestamp) = _determineInflatorState(poolState_, inflatorState); - inflatorState.inflator = newInflator; - if (updateTimestamp) inflatorState.inflatorUpdate = uint48(block.timestamp); + // update pool inflator + if (poolState_.isNewInterestAccrued) { + inflatorState.inflator = SafeCast.toUint208(poolState_.inflator); + inflatorState.inflatorUpdate = uint48(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 = 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/src/libraries/helpers/PoolHelper.sol b/src/libraries/helpers/PoolHelper.sol index 6076d50ae..4d56ec1db 100644 --- a/src/libraries/helpers/PoolHelper.sol +++ b/src/libraries/helpers/PoolHelper.sol @@ -4,10 +4,8 @@ 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 { InflatorState, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; +import { PoolType } from '../../interfaces/pool/IPool.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Maths } from '../internal/Maths.sol'; @@ -138,35 +136,6 @@ import { Maths } from '../internal/Maths.sol'; return Maths.wdiv(interestRate_, 365 * 3e18); } - /** - * @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 `HTP` price. * @param maxT0DebtToCollateral_ Max t0 debt to collateral in pool. @@ -228,24 +197,6 @@ import { Maths } from '../internal/Maths.sol'; return Maths.wmul(collateral_, price_) >= Maths.wmul(COLLATERALIZATION_FACTOR, debt_); } - /** - * @notice Price precision adjustment used in calculating collateral dust for a bucket. - * To ensure the accuracy of the exchange rate calculation, buckets with smaller prices require - * larger minimum amounts of collateral. This formula imposes a lower bound independent of token scale. - * @param bucketIndex_ Index of the bucket, or `0` for encumbered collateral with no bucket affinity. - * @return pricePrecisionAdjustment_ Unscaled integer of the minimum number of decimal places the dust limit requires. - */ - function _getCollateralDustPricePrecisionAdjustment( - uint256 bucketIndex_ - ) pure returns (uint256 pricePrecisionAdjustment_) { - // conditional is a gas optimization - if (bucketIndex_ > 3900) { - int256 bucketOffset = int256(bucketIndex_ - 3900); - int256 result = PRBMathSD59x18.sqrt(PRBMathSD59x18.div(bucketOffset * 1e18, int256(36 * 1e18))); - pricePrecisionAdjustment_ = uint256(result / 1e18); - } - } - /** * @notice Returns the amount of collateral calculated for the given amount of `LP`. * @dev The value returned is capped at collateral amount available in bucket. diff --git a/src/libraries/internal/Buckets.sol b/src/libraries/internal/Buckets.sol index f1bb67262..f013bc989 100644 --- a/src/libraries/internal/Buckets.sol +++ b/src/libraries/internal/Buckets.sol @@ -250,7 +250,7 @@ library Buckets { uint256 bucketLP_, uint256 bucketDeposit_, uint256 bucketPrice_ - ) internal pure returns (uint256) { + ) external pure returns (uint256) { return lpToQuoteTokens( bucketCollateral_, bucketLP_, diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol index d16becca9..afbe91355 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolPrecision.t.sol @@ -761,15 +761,6 @@ contract ERC20PoolPrecisionTest is ERC20DSTestPlus { } function testCollateralDustPricePrecisionAdjustment() external tearDown { - // test the bucket price adjustment used for determining dust amount - assertEq(_getCollateralDustPricePrecisionAdjustment(0), 0); - assertEq(_getCollateralDustPricePrecisionAdjustment(1), 0); - assertEq(_getCollateralDustPricePrecisionAdjustment(4156), 2); - assertEq(_getCollateralDustPricePrecisionAdjustment(4310), 3); - assertEq(_getCollateralDustPricePrecisionAdjustment(5260), 6); - assertEq(_getCollateralDustPricePrecisionAdjustment(6466), 8); - assertEq(_getCollateralDustPricePrecisionAdjustment(6647), 8); - assertEq(_getCollateralDustPricePrecisionAdjustment(7388), 9); // check dust limits for 18-decimal collateral init(18, 18); From d5d3c0792923ac401786a7a44f25b5f9e0d1cbc9 Mon Sep 17 00:00:00 2001 From: prateek105 Date: Wed, 20 Dec 2023 19:42:49 +0530 Subject: [PATCH 2/3] Fix failing unit tests with new foundry version --- tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol | 2 +- tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol | 2 +- tests/forge/unit/Positions/PositionManager.t.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol index 7f0ef14af..77b38630d 100644 --- a/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol +++ b/tests/forge/unit/ERC20Pool/ERC20PoolFactory.t.sol @@ -12,7 +12,7 @@ import { IPoolErrors } from 'src/interfaces/pool/commons/IPoolErrors.sol'; import { IPoolFactory } from 'src/interfaces/pool/IPoolFactory.sol'; contract ERC20PoolFactoryTest is ERC20HelperContract { - address immutable poolAddress = 0x9Fd7552Da37D2D296CB4a84d507c35Dcc926220a; + address immutable poolAddress = 0x22e52d5C273D99547F9Fa44B8034C16d93773B13; bytes32 constant ERC20_NON_SUBSET_HASH = keccak256("ERC20_NON_SUBSET_HASH"); function setUp() external { diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol index 8cb249e14..a18f00a32 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol @@ -34,7 +34,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { // deploy NFT collection pool vm.expectEmit(true, true, false, true); - emit PoolCreated(0x8a2be84c82956B6DdA0c4D647Ae9357d845a086B, _factory.getNFTSubsetHash(tokenIds)); + emit PoolCreated(0x4693faE904804C89B14b1C13d2a53f10D2c4e5E3, _factory.getNFTSubsetHash(tokenIds)); _NFTCollectionPoolAddress = _factory.deployPool(address(_collateral), address(_quote), tokenIds, 0.05 * 10**18); _NFTCollectionPool = ERC721Pool(_NFTCollectionPoolAddress); diff --git a/tests/forge/unit/Positions/PositionManager.t.sol b/tests/forge/unit/Positions/PositionManager.t.sol index 55f3880db..96cedfd41 100644 --- a/tests/forge/unit/Positions/PositionManager.t.sol +++ b/tests/forge/unit/Positions/PositionManager.t.sol @@ -1338,7 +1338,7 @@ contract PositionManagerERC20PoolTest is PositionManagerERC20PoolHelperContract _positionManager.nonces(5); // check domain separator matches expectations for the test chain - assertEq(_positionManager.DOMAIN_SEPARATOR(), 0x255893ac72554d931c70a8c246ff1216c7122c81a4d4f7f2a2eb5377f2481f12); + assertEq(_positionManager.DOMAIN_SEPARATOR(), 0x433817320eb965ca857baacaaaffc14f306708454d94514994a825a8a91200f6); } function testPermitReverts() external { From fc251b3ad15d48328b6b356cc14296f70d02117e Mon Sep 17 00:00:00 2001 From: prateek105 Date: Wed, 20 Dec 2023 20:10:31 +0530 Subject: [PATCH 3/3] Fix ERC721PoolFactoryTest --- tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol index a18f00a32..9c05666ac 100644 --- a/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol +++ b/tests/forge/unit/ERC721Pool/ERC721PoolFactory.t.sol @@ -34,7 +34,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { // deploy NFT collection pool vm.expectEmit(true, true, false, true); - emit PoolCreated(0x4693faE904804C89B14b1C13d2a53f10D2c4e5E3, _factory.getNFTSubsetHash(tokenIds)); + emit PoolCreated(0xCF7E26A9FE732063F431b91478E27092AD911162, _factory.getNFTSubsetHash(tokenIds)); _NFTCollectionPoolAddress = _factory.deployPool(address(_collateral), address(_quote), tokenIds, 0.05 * 10**18); _NFTCollectionPool = ERC721Pool(_NFTCollectionPoolAddress); @@ -46,7 +46,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { _tokenIdsSubsetOne[3] = 61; vm.expectEmit(true, true, false, true); - emit PoolCreated(0x18D11C8Cf8dc292E647F0b324d10637a3A63F678, _factory.getNFTSubsetHash(_tokenIdsSubsetOne)); + emit PoolCreated(0x4693faE904804C89B14b1C13d2a53f10D2c4e5E3, _factory.getNFTSubsetHash(_tokenIdsSubsetOne)); _NFTSubsetOnePoolAddress = _factory.deployPool(address(_collateral), address(_quote), _tokenIdsSubsetOne, 0.05 * 10**18); _NFTSubsetOnePool = ERC721Pool(_NFTSubsetOnePoolAddress); @@ -61,7 +61,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { _tokenIdsSubsetTwo[6] = 180; vm.expectEmit(true, true, false, true); - emit PoolCreated(0xf80F47F84F20d8aF58cC8e93634348491357323c, _factory.getNFTSubsetHash(_tokenIdsSubsetTwo)); + emit PoolCreated(0x0782c1757679ea21E7eFc4af5C7374dFD2388b58, _factory.getNFTSubsetHash(_tokenIdsSubsetTwo)); _NFTSubsetTwoPoolAddress = _factory.deployPool(address(_collateral), address(_quote), _tokenIdsSubsetTwo, 0.05 * 10**18); _NFTSubsetTwoPool = ERC721Pool(_NFTSubsetTwoPoolAddress); @@ -311,7 +311,7 @@ contract ERC721PoolFactoryTest is ERC721HelperContract { tokenIdsTestSubset[2] = 3; vm.expectEmit(true, true, false, true); - emit PoolCreated(0xA8FBA534d7ebefEBB270cDC4814A1A25916A94d3, _factory.getNFTSubsetHash(tokenIdsTestSubset)); + emit PoolCreated(0x993df58454B37994dA5C81F98c33e3857a42dFc2, _factory.getNFTSubsetHash(tokenIdsTestSubset)); address poolAddress = _factory.deployPool(address(_collateral), address(_quote), tokenIdsTestSubset, 0.05 * 10**18); // check tracking of deployed pools