From fac6976bd114c3841b82177a77e7508025da5812 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 22 Feb 2023 13:56:28 +0100 Subject: [PATCH 01/32] feat: draft asset as collateral --- src/MorphoInternal.sol | 4 ++++ src/MorphoSetters.sol | 6 ++++++ src/PositionsManagerInternal.sol | 11 +++++++++++ src/libraries/Errors.sol | 1 + 4 files changed, 22 insertions(+) diff --git a/src/MorphoInternal.sol b/src/MorphoInternal.sol index 5dc452e1a..7e62ea24f 100644 --- a/src/MorphoInternal.sol +++ b/src/MorphoInternal.sol @@ -275,6 +275,10 @@ abstract contract MorphoInternal is MorphoStorage { view returns (uint256 borrowable, uint256 maxDebt) { + if (!_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { + return (0, 0); + } + (uint256 underlyingPrice, uint256 ltv, uint256 liquidationThreshold, uint256 tokenUnit) = _assetLiquidityData(underlying, vars); diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index af866595f..ad0801239 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -74,6 +74,12 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { emit Events.TreasuryVaultSet(treasuryVault); } + /// @notice Sets the `underlying` asset as collateral or not. + /// @dev Note that it is possible to set an asset whose market is not created yet on the protocol. + function setAssetAsCollateral(address underlying, bool isCollateral) external onlyOwner { + _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + } + /// @notice Sets the `underlying`'s reserve factor to `newReserveFactor` (in bps). function setReserveFactor(address underlying, uint16 newReserveFactor) external diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index a1c50b5ce..67e2247e8 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -20,6 +20,7 @@ import {LogarithmicBuckets} from "@morpho-data-structures/LogarithmicBuckets.sol import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol"; +import {UserConfiguration} from "@aave-v3-core/protocol/libraries/configuration/UserConfiguration.sol"; import {ReserveConfiguration} from "@aave-v3-core/protocol/libraries/configuration/ReserveConfiguration.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; @@ -43,6 +44,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { using EnumerableSet for EnumerableSet.AddressSet; using LogarithmicBuckets for LogarithmicBuckets.Buckets; + using UserConfiguration for DataTypes.UserConfigurationMap; using ReserveConfiguration for DataTypes.ReserveConfigurationMap; /// @dev Validates the manager's permission. @@ -171,6 +173,15 @@ abstract contract PositionsManagerInternal is MatchingEngine { if (collateralMarket.isLiquidateCollateralPaused()) revert Errors.LiquidateCollateralIsPaused(); if (borrowMarket.isLiquidateBorrowPaused()) revert Errors.LiquidateBorrowIsPaused(); + if ( + _POOL.getConfiguration(underlyingCollateral).getLiquidationThreshold() == 0 + || _POOL.getUserConfiguration(address(this)).isUsingAsCollateral( + _POOL.getReserveData(underlyingCollateral).id + ) + ) { + revert Errors.AssetNotUsedAsCollateral(); + } + if (borrowMarket.isDeprecated()) return Constants.MAX_CLOSE_FACTOR; // Allow liquidation of the whole debt. uint256 healthFactor = _getUserHealthFactor(borrower); diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index e87b80642..8e242355f 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -33,6 +33,7 @@ library Errors { error UnauthorizedWithdraw(); error UnauthorizedLiquidate(); + error AssetNotUsedAsCollateral(); error ExceedsMaxBasisPoints(); From 18656f1936f23d2fa30d7b228131447fd417fd2b Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 22 Feb 2023 15:32:57 +0100 Subject: [PATCH 02/32] feat: add isCollateral check --- src/MorphoInternal.sol | 4 +--- src/MorphoSetters.sol | 13 +++++++++++-- src/PositionsManagerInternal.sol | 10 ++-------- src/libraries/Events.sol | 2 ++ src/libraries/MarketLib.sol | 6 ++++++ src/libraries/Types.sol | 1 + test/internal/TestInternalMorphoInternal.sol | 2 ++ .../TestInternalPositionsManagerInternal.sol | 12 +++++++++++- 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/MorphoInternal.sol b/src/MorphoInternal.sol index 7e62ea24f..af73e7e01 100644 --- a/src/MorphoInternal.sol +++ b/src/MorphoInternal.sol @@ -275,9 +275,7 @@ abstract contract MorphoInternal is MorphoStorage { view returns (uint256 borrowable, uint256 maxDebt) { - if (!_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { - return (0, 0); - } + if (!_market[underlying].isCollateral) return (0, 0); (uint256 underlyingPrice, uint256 ltv, uint256 liquidationThreshold, uint256 tokenUnit) = _assetLiquidityData(underlying, vars); diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index ad0801239..ea7345fb0 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -74,10 +74,19 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { emit Events.TreasuryVaultSet(treasuryVault); } - /// @notice Sets the `underlying` asset as collateral or not. + /// @notice Sets the `underlying` asset as collateral or not on the pool. /// @dev Note that it is possible to set an asset whose market is not created yet on the protocol. - function setAssetAsCollateral(address underlying, bool isCollateral) external onlyOwner { + function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); + } + + function setAssetIsCollateral(address underlying, bool isCollateral) + external + onlyOwner + isMarketCreated(underlying) + { + _market[underlying].setAssetIsCollateral(isCollateral); } /// @notice Sets the `underlying`'s reserve factor to `newReserveFactor` (in bps). diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index 67e2247e8..4836e07f7 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -92,6 +92,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { function _validateSupplyCollateral(address underlying, uint256 amount, address user) internal view { Types.Market storage market = _validateInput(underlying, amount, user); if (market.isSupplyCollateralPaused()) revert Errors.SupplyCollateralIsPaused(); + if (!market.isCollateral) revert Errors.AssetNotUsedAsCollateral(); } /// @dev Validates a borrow action. @@ -173,14 +174,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { if (collateralMarket.isLiquidateCollateralPaused()) revert Errors.LiquidateCollateralIsPaused(); if (borrowMarket.isLiquidateBorrowPaused()) revert Errors.LiquidateBorrowIsPaused(); - if ( - _POOL.getConfiguration(underlyingCollateral).getLiquidationThreshold() == 0 - || _POOL.getUserConfiguration(address(this)).isUsingAsCollateral( - _POOL.getReserveData(underlyingCollateral).id - ) - ) { - revert Errors.AssetNotUsedAsCollateral(); - } + if (!collateralMarket.isCollateral) revert Errors.AssetNotUsedAsCollateral(); if (borrowMarket.isDeprecated()) return Constants.MAX_CLOSE_FACTOR; // Allow liquidation of the whole debt. diff --git a/src/libraries/Events.sol b/src/libraries/Events.sol index 8ce635590..7a0b566e5 100644 --- a/src/libraries/Events.sol +++ b/src/libraries/Events.sol @@ -90,6 +90,8 @@ library Events { address indexed claimer, address indexed onBehalf, address indexed rewardToken, uint256 amountClaimed ); + event IsCollateralSet(address indexed underlying, bool isCollateral); + event IsSupplyPausedSet(address indexed underlying, bool isPaused); event IsSupplyCollateralPausedSet(address indexed underlying, bool isPaused); diff --git a/src/libraries/MarketLib.sol b/src/libraries/MarketLib.sol index 37f30d41f..c20c3055a 100644 --- a/src/libraries/MarketLib.sol +++ b/src/libraries/MarketLib.sol @@ -73,6 +73,12 @@ library MarketLib { return market.pauseStatuses.isP2PDisabled; } + function setAssetIsCollateral(Types.Market storage market, bool isCollateral) internal { + market.isCollateral = isCollateral; + + emit Events.IsCollateralSet(market.underlying, isCollateral); + } + function setIsSupplyPaused(Types.Market storage market, bool isPaused) internal { market.pauseStatuses.isSupplyPaused = isPaused; diff --git a/src/libraries/Types.sol b/src/libraries/Types.sol index 4d85266f4..367d89104 100644 --- a/src/libraries/Types.sol +++ b/src/libraries/Types.sol @@ -67,6 +67,7 @@ library Types { // SLOT 6 address underlying; // 160 bits PauseStatuses pauseStatuses; // 80 bits + bool isCollateral; // 8 bits // SLOT 7 address variableDebtToken; // 160 bits uint32 lastUpdateTimestamp; // 32 bits diff --git a/test/internal/TestInternalMorphoInternal.sol b/test/internal/TestInternalMorphoInternal.sol index f505698b6..583037615 100644 --- a/test/internal/TestInternalMorphoInternal.sol +++ b/test/internal/TestInternalMorphoInternal.sol @@ -278,6 +278,8 @@ contract TestInternalMorphoInternal is InternalTest, MorphoInternal { function testLiquidityDataCollateral(uint256 amount) public { amount = bound(amount, 0, 1_000_000 ether); + _market[dai].isCollateral = true; + _marketBalances[dai].collateral[address(1)] = amount.rayDivUp(_market[dai].indexes.supply.poolIndex); DataTypes.EModeCategory memory eModeCategory = _POOL.getEModeCategoryData(0); diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index 45106a191..26c9af053 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -101,7 +101,8 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.validateSupplyCollateral(dai, 1, address(1)); } - function testValidateSupplyCollateral() public view { + function testValidateSupplyCollateral() public { + _market[dai].isCollateral = true; this.validateSupplyCollateral(dai, 1, address(1)); } @@ -195,6 +196,7 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testAuthorizeLiquidateShouldReturnMaxCloseFactorIfDeprecatedBorrow() public { + _market[dai].isCollateral = true; _userCollaterals[address(this)].add(dai); _userBorrows[address(this)].add(dai); _market[dai].pauseStatuses.isDeprecated = true; @@ -203,6 +205,8 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testAuthorizeLiquidateShouldRevertIfSentinelDisallows() public { + _market[dai].isCollateral = true; + uint256 amount = 1e18; (, uint256 lt,,,,) = _POOL.getConfiguration(dai).getParams(); (, Types.Indexes256 memory indexes) = _computeIndexes(dai); @@ -221,6 +225,8 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testAuthorizeLiquidateShouldRevertIfBorrowerHealthy() public { + _market[dai].isCollateral = true; + uint256 amount = 1e18; (, Types.Indexes256 memory indexes) = _computeIndexes(dai); @@ -234,6 +240,8 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testAuthorizeLiquidateShouldReturnMaxCloseFactorIfBelowMinThreshold(uint256 amount) public { + _market[dai].isCollateral = true; + amount = bound(amount, MIN_AMOUNT, MAX_AMOUNT); (, uint256 lt,,,,) = _POOL.getConfiguration(dai).getParams(); (, Types.Indexes256 memory indexes) = _computeIndexes(dai); @@ -250,6 +258,8 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testAuthorizeLiquidateShouldReturnDefaultCloseFactorIfAboveMinThreshold(uint256 amount) public { + _market[dai].isCollateral = true; + // Min amount needs to be high enough to have a precise enough price for this test amount = bound(amount, 1e12, MAX_AMOUNT); (, uint256 lt,,,,) = _POOL.getConfiguration(dai).getParams(); From b3c60d70557c82b4ed79845544c10ac68b7dfeed Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 11:55:53 +0100 Subject: [PATCH 03/32] feat: add revert case and mirror behavior --- src/MorphoSetters.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index ea7345fb0..56a8dd921 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -75,18 +75,22 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as collateral or not on the pool. - /// @dev Note that it is possible to set an asset whose market is not created yet on the protocol. + /// @dev Note that it is possible to set an asset as collateral whose market is not created yet on the protocol. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); } + /// @notice Sets the `underlying` asset as collateral or not on Morpho. + /// @dev If the asset is set as collateral, the behavior is propagated to the pool. function setAssetIsCollateral(address underlying, bool isCollateral) external onlyOwner isMarketCreated(underlying) { _market[underlying].setAssetIsCollateral(isCollateral); + if (isCollateral) _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); } /// @notice Sets the `underlying`'s reserve factor to `newReserveFactor` (in bps). From f2c637c1af0f5dcfb063a74802a3b164e54efbf3 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 12:05:16 +0100 Subject: [PATCH 04/32] feat: add correct check --- src/PositionsManagerInternal.sol | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index 4836e07f7..37d0d6836 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -174,7 +174,15 @@ abstract contract PositionsManagerInternal is MatchingEngine { if (collateralMarket.isLiquidateCollateralPaused()) revert Errors.LiquidateCollateralIsPaused(); if (borrowMarket.isLiquidateBorrowPaused()) revert Errors.LiquidateBorrowIsPaused(); - if (!collateralMarket.isCollateral) revert Errors.AssetNotUsedAsCollateral(); + // Revert only if the collateral is not enabled as collateral on Morpho AND on the pool. + // This way Morpho can disable an asset as collateral on Morpho side only and still be able to liquidate unhealthy positions using this asset as collateral. + // This can be helpful in case the pool sets an asset's LTV to 0. + if ( + !collateralMarket.isCollateral + && _POOL.getUserConfiguration(address(this)).isUsingAsCollateral( + _POOL.getReserveData(underlyingCollateral).id + ) + ) revert Errors.AssetNotUsedAsCollateral(); if (borrowMarket.isDeprecated()) return Constants.MAX_CLOSE_FACTOR; // Allow liquidation of the whole debt. From 4afe5f294a210125a326145844a68f89b794894d Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 12:50:37 +0100 Subject: [PATCH 05/32] fix: add missing negation --- src/PositionsManagerInternal.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index 37d0d6836..c21f41ebf 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -179,9 +179,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { // This can be helpful in case the pool sets an asset's LTV to 0. if ( !collateralMarket.isCollateral - && _POOL.getUserConfiguration(address(this)).isUsingAsCollateral( - _POOL.getReserveData(underlyingCollateral).id - ) + && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlyingCollateral).id) ) revert Errors.AssetNotUsedAsCollateral(); if (borrowMarket.isDeprecated()) return Constants.MAX_CLOSE_FACTOR; // Allow liquidation of the whole debt. From 546a14bc46f2751d8ab149a0eac3988db1c0f67c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 14:44:53 +0100 Subject: [PATCH 06/32] test: add first tests --- test/internal/TestInternalMorphoInternal.sol | 14 ++++++++- .../TestInternalPositionsManagerInternal.sol | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/test/internal/TestInternalMorphoInternal.sol b/test/internal/TestInternalMorphoInternal.sol index 583037615..6e4913bc8 100644 --- a/test/internal/TestInternalMorphoInternal.sol +++ b/test/internal/TestInternalMorphoInternal.sol @@ -275,11 +275,23 @@ contract TestInternalMorphoInternal is InternalTest, MorphoInternal { assertEq(units, 10 ** poolDecimals, "units not equal to pool decimals 2"); } + function testCollateralDataNoCollateral(uint256 amount) public { + amount = bound(amount, 0, 1_000_000 ether); + + _marketBalances[dai].collateral[address(1)] = amount.rayDivUp(_market[dai].indexes.supply.poolIndex); + + DataTypes.EModeCategory memory eModeCategory = _POOL.getEModeCategoryData(0); + Types.LiquidityVars memory vars = Types.LiquidityVars(address(1), oracle, eModeCategory); + + (uint256 borrowable, uint256 maxDebt) = _collateralData(dai, vars); + assertEq(borrowable, 0, "borrowable != 0"); + assertEq(maxDebt, 0, "maxDebt != 0"); + } + function testLiquidityDataCollateral(uint256 amount) public { amount = bound(amount, 0, 1_000_000 ether); _market[dai].isCollateral = true; - _marketBalances[dai].collateral[address(1)] = amount.rayDivUp(_market[dai].indexes.supply.poolIndex); DataTypes.EModeCategory memory eModeCategory = _POOL.getEModeCategoryData(0); diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index 26c9af053..435813ecc 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -101,6 +101,11 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.validateSupplyCollateral(dai, 1, address(1)); } + function testValidateSupplyCollateralShouldRevertIfNotCollateral() public { + vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotUsedAsCollateral.selector)); + this.validateSupplyCollateral(dai, 1, address(1)); + } + function testValidateSupplyCollateral() public { _market[dai].isCollateral = true; this.validateSupplyCollateral(dai, 1, address(1)); @@ -195,6 +200,32 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.authorizeLiquidate(dai, usdc, address(this)); } + function testAuthorizeLiquidateIfAssetNotEnabledCollateralOnMorphoAndOnPool() public { + _market[dai].isCollateral = false; + _POOL.setUserUseReserveAsCollateral(usdc, false); + + vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotUsedAsCollateral.selector)); + this.authorizeLiquidate(dai, usdc, address(this)); + } + + function testAuthorizeLiquidateShouldPassIfCollateralAssetOnlyEnabledOnPool() public { + _POOL.setUserUseReserveAsCollateral(usdc, true); + + _userCollaterals[address(this)].add(dai); + _userBorrows[address(this)].add(dai); + _market[dai].pauseStatuses.isDeprecated = true; + this.authorizeLiquidate(dai, dai, address(this)); + } + + function testAuthorizeLiquidateShouldPassIfCollateralAssetOnlyEnabledOnMorpho() public { + _market[dai].isCollateral = true; + + _userCollaterals[address(this)].add(dai); + _userBorrows[address(this)].add(dai); + _market[dai].pauseStatuses.isDeprecated = true; + this.authorizeLiquidate(dai, dai, address(this)); + } + function testAuthorizeLiquidateShouldReturnMaxCloseFactorIfDeprecatedBorrow() public { _market[dai].isCollateral = true; _userCollaterals[address(this)].add(dai); From ad47ae3176a07c85cfd586694f8f8bcd071febfd Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 16:32:11 +0100 Subject: [PATCH 07/32] feat: add functions to interface + small reorder of logic --- src/MorphoSetters.sol | 3 ++- src/interfaces/IMorpho.sol | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 56a8dd921..fc03520b9 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -77,9 +77,10 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { /// @notice Sets the `underlying` asset as collateral or not on the pool. /// @dev Note that it is possible to set an asset as collateral whose market is not created yet on the protocol. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { - _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); + + _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); } /// @notice Sets the `underlying` asset as collateral or not on Morpho. diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index f0317e56a..d9b4dc8f0 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -53,6 +53,8 @@ interface IMorphoSetters { function setP2PIndexCursor(address underlying, uint16 p2pIndexCursor) external; function setReserveFactor(address underlying, uint16 newReserveFactor) external; + function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external; + function setAssetIsCollateral(address underlying, bool isCollateral) external; function setIsP2PDisabled(address underlying, bool isP2PDisabled) external; function setIsPaused(address underlying, bool isPaused) external; function setIsPausedForAllMarkets(bool isPaused) external; From be28d1d03816e56321e32d579a5504d763c6ccf8 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 16:52:48 +0100 Subject: [PATCH 08/32] test: add missing tests --- test/helpers/ForkTest.sol | 2 +- test/helpers/IntegrationTest.sol | 7 + .../TestIntegrationAssetAsCollateral.sol | 144 ++++++++++++++++++ test/unit/TestUnitMarketLib.sol | 10 ++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test/integration/TestIntegrationAssetAsCollateral.sol diff --git a/test/helpers/ForkTest.sol b/test/helpers/ForkTest.sol index 0fa3b673a..bb74777ec 100644 --- a/test/helpers/ForkTest.sol +++ b/test/helpers/ForkTest.sol @@ -123,7 +123,7 @@ contract ForkTest is BaseTest { weth = config.getAddress("WETH"); wNative = config.getWrappedNative(); - allUnderlyings = pool.getReservesList(); + allUnderlyings = [dai, usdc, aave, wbtc, weth]; } function _label() internal virtual { diff --git a/test/helpers/IntegrationTest.sol b/test/helpers/IntegrationTest.sol index 704f089f8..fec084002 100644 --- a/test/helpers/IntegrationTest.sol +++ b/test/helpers/IntegrationTest.sol @@ -191,6 +191,13 @@ contract IntegrationTest is ForkTest { pool.deposit(market.underlying, amount, onBehalf, 0); } + /// @dev Deposits the given amount of tokens on behalf of the given address, on AaveV3. + function _depositSimple(address underlying, uint256 amount, address onBehalf) internal { + deal(underlying, address(this), amount); + ERC20(underlying).approve(address(pool), amount); + pool.deposit(underlying, amount, onBehalf, 0); + } + /// @dev Bounds the input supply cap of AaveV3 so that it is exceeded after having deposited a given amount function _boundSupplyCapExceeded(TestMarket storage market, uint256 amount, uint256 supplyCap) internal diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol new file mode 100644 index 000000000..7c6f2c76f --- /dev/null +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import {IMorpho} from "src/interfaces/IMorpho.sol"; + +import {Errors} from "src/libraries/Errors.sol"; +import {UserConfiguration} from "@aave-v3-core/protocol/libraries/configuration/UserConfiguration.sol"; + +import {Morpho} from "src/Morpho.sol"; + +import "test/helpers/IntegrationTest.sol"; + +contract TestIntegrationAssetAsCollateral is IntegrationTest { + using UserConfiguration for DataTypes.UserConfigurationMap; + + function setUp() public override { + super.setUp(); + + // Deposit LINK dust so that setting LINK as collateral does not revert on the pool. + _depositSimple(link, 1e12, address(morpho)); + + vm.startPrank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, false); + pool.setUserUseReserveAsCollateral(link, false); + vm.stopPrank(); + } + + function testSetAssetIsCollateralShouldRevertWhenMarketNotCreated(address underlying) public { + vm.expectRevert(Errors.MarketNotCreated.selector); + morpho.setAssetIsCollateral(underlying, true); + } + + function testSetAssetIsCollateral() public { + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), false); + + morpho.setAssetIsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, true); + assertEq(_isUsingAsCollateral(dai), true); + } + + function testSetAssetIsNotCollateral() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + + morpho.setAssetIsCollateral(dai, false); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + } + + function testSetAssetIsCollateralOnPoolShouldRevertWhenMarketIsNotCreated() public { + assertEq(morpho.market(link).isCollateral, false); + assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), false); + + vm.expectRevert(Errors.MarketNotCreated.selector); + morpho.setAssetIsCollateralOnPool(link, true); + } + + function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + morpho.setAssetIsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, true); + assertEq(_isUsingAsCollateral(dai), true); + + morpho.setAssetIsCollateralOnPool(dai, true); + + assertEq(morpho.market(dai).isCollateral, true); + assertEq(_isUsingAsCollateral(dai), true); + } + + function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + + morpho.setAssetIsCollateralOnPool(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + } + + function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateral() public { + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), false); + + morpho.setAssetIsCollateralOnPool(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + } + + function testSetAssetIsNotCollateralOnPoolWhenMarketIsNotCreated() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(link, true); + + assertEq(morpho.market(link).isCollateral, false); + assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), true); + + morpho.setAssetIsCollateralOnPool(link, false); + + assertEq(morpho.market(link).isCollateral, false); + assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), false); + } + + function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + morpho.setAssetIsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, true); + assertEq(_isUsingAsCollateral(dai), true); + + morpho.setAssetIsCollateralOnPool(dai, false); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), false); + } + + function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + + morpho.setAssetIsCollateralOnPool(dai, false); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), false); + } + + function _isUsingAsCollateral(address underlying) internal view returns (bool) { + return pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(underlying).id); + } +} diff --git a/test/unit/TestUnitMarketLib.sol b/test/unit/TestUnitMarketLib.sol index 7f2b9b69b..77dd3d27b 100644 --- a/test/unit/TestUnitMarketLib.sol +++ b/test/unit/TestUnitMarketLib.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {Types} from "src/libraries/Types.sol"; +import {Events} from "src/libraries/Events.sol"; import {MarketLib} from "src/libraries/MarketLib.sol"; import {Test} from "@forge-std/Test.sol"; @@ -59,4 +60,13 @@ contract TestUnitMarketLib is Test { assertEq(market.indexes.borrow.poolIndex, indexes.borrow.poolIndex); assertEq(market.indexes.borrow.p2pIndex, indexes.borrow.p2pIndex); } + + function testSetAssetIsCollateral(bool isCollateral) public { + vm.expectEmit(true, false, false, false); + emit Events.IsCollateralSet(market.underlying, isCollateral); + + market.setAssetIsCollateral(isCollateral); + + assertEq(market.isCollateral, isCollateral); + } } From bf5516d80f2ec213f7967a5ee9fe92cffc9c567c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 20:20:32 +0100 Subject: [PATCH 09/32] test: fix test setup --- test/helpers/IntegrationTest.sol | 18 ++++++++++++++---- .../TestIntegrationAssetAsCollateral.sol | 10 ++++++++++ test/integration/TestIntegrationWithdraw.sol | 17 ++++++++++------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/test/helpers/IntegrationTest.sol b/test/helpers/IntegrationTest.sol index 2345678c1..3ce56ff3e 100644 --- a/test/helpers/IntegrationTest.sol +++ b/test/helpers/IntegrationTest.sol @@ -55,9 +55,7 @@ contract IntegrationTest is ForkTest { _createMarket(allUnderlyings[i], 0, 33_33); } - // Supply dust to make UserConfigurationMap.isUsingAsCollateralOne() always return true. - _deposit(testMarkets[weth], 1e12, address(morpho)); - _deposit(testMarkets[dai], 1e12, address(morpho)); + _setAllAssetsAsCollateral(); _forward(1); // All markets are outdated in Morpho's storage. @@ -152,6 +150,18 @@ contract IntegrationTest is ForkTest { morpho.createMarket(market.underlying, market.reserveFactor, market.p2pIndexCursor); } + function _setAllAssetsAsCollateral() internal { + for (uint256 i; i < allUnderlyings.length; ++i) { + _setAssetAsCollateral(testMarkets[allUnderlyings[i]]); + } + } + + function _setAssetAsCollateral(TestMarket storage market) internal { + // Supply dust to make UserConfigurationMap.isUsingAsCollateralOne() return true. + _deposit(market, (10 ** market.decimals) / 1e6, address(morpho)); + morpho.setAssetIsCollateral(market.underlying, true); + } + /// @dev Calculates the underlying amount that can be supplied on the given market on AaveV3, reaching the supply cap. function _supplyGap(TestMarket storage market) internal view returns (uint256) { return market.supplyCap.zeroFloorSub(market.totalSupply() + _accruedToTreasury(market.underlying)); @@ -186,7 +196,7 @@ contract IntegrationTest is ForkTest { internal bypassSupplyCap(market, amount) { - deal(market.underlying, address(this), amount); + deal(market.underlying, address(this), type(uint256).max); ERC20(market.underlying).approve(address(pool), amount); pool.deposit(market.underlying, amount, onBehalf, 0); } diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index 7c6f2c76f..e42e14919 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -19,8 +19,18 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { // Deposit LINK dust so that setting LINK as collateral does not revert on the pool. _depositSimple(link, 1e12, address(morpho)); + morpho.setAssetIsCollateral(dai, false); + morpho.setAssetIsCollateral(usdc, false); + morpho.setAssetIsCollateral(aave, false); + morpho.setAssetIsCollateral(wbtc, false); + morpho.setAssetIsCollateral(weth, false); + vm.startPrank(address(morpho)); pool.setUserUseReserveAsCollateral(dai, false); + pool.setUserUseReserveAsCollateral(usdc, false); + pool.setUserUseReserveAsCollateral(aave, false); + pool.setUserUseReserveAsCollateral(wbtc, false); + pool.setUserUseReserveAsCollateral(weth, false); pool.setUserUseReserveAsCollateral(link, false); vm.stopPrank(); } diff --git a/test/integration/TestIntegrationWithdraw.sol b/test/integration/TestIntegrationWithdraw.sol index c625266c2..9d30d2440 100644 --- a/test/integration/TestIntegrationWithdraw.sol +++ b/test/integration/TestIntegrationWithdraw.sol @@ -62,7 +62,7 @@ contract TestIntegrationWithdraw is IntegrationTest { assertApproxLeAbs(test.withdrawn, amount, 1, "withdrawn != amount"); assertApproxLeAbs(p2pSupply, promoted, 2, "p2pSupply != promoted"); - assertApproxLeAbs(morpho.supplyBalance(market.underlying, onBehalf), remaining, 1, "supply != remaining"); + assertApproxLeAbs(morpho.supplyBalance(market.underlying, onBehalf), remaining, 2, "supply != remaining"); assertEq(morpho.collateralBalance(market.underlying, onBehalf), 0, "collateral != 0"); // Assert Morpho's position on pool. @@ -145,15 +145,18 @@ contract TestIntegrationWithdraw is IntegrationTest { assertEq(test.scaledPoolSupply, 0, "scaledPoolSupply != 0"); assertEq(test.scaledCollateral, 0, "scaledCollateral != 0"); assertApproxEqAbs(test.withdrawn, test.supplied, 3, "withdrawn != supplied"); - assertEq( - morpho.scaledP2PBorrowBalance(market.underlying, address(promoter1)), 0, "promoterScaledP2PBorrow != 0" + assertApproxEqAbs( + morpho.scaledP2PBorrowBalance(market.underlying, address(promoter1)), + 0, + 2, + "promoterScaledP2PBorrow != 0" ); // Assert Morpho getters. assertEq(morpho.supplyBalance(market.underlying, onBehalf), 0, "supply != 0"); assertEq(morpho.collateralBalance(market.underlying, onBehalf), 0, "collateral != 0"); - assertApproxGeAbs( - morpho.borrowBalance(market.underlying, address(promoter1)), promoted, 3, "promoterBorrow != promoted" + assertApproxEqAbs( + morpho.borrowBalance(market.underlying, address(promoter1)), promoted, 2, "promoterBorrow != promoted" ); // Assert Morpho's position on pool. @@ -173,9 +176,9 @@ contract TestIntegrationWithdraw is IntegrationTest { // Assert Morpho's market state. assertEq(test.morphoMarket.deltas.supply.scaledDelta, 0, "scaledSupplyDelta != 0"); - assertApproxEqAbs(test.morphoMarket.deltas.supply.scaledP2PTotal, 0, 1, "scaledTotalSupplyP2P != 0"); + assertApproxEqAbs(test.morphoMarket.deltas.supply.scaledP2PTotal, 0, 2, "scaledTotalSupplyP2P != 0"); assertEq(test.morphoMarket.deltas.borrow.scaledDelta, 0, "scaledBorrowDelta != 0"); - assertApproxEqAbs(test.morphoMarket.deltas.borrow.scaledP2PTotal, 0, 1, "scaledTotalBorrowP2P != 0"); + assertApproxEqAbs(test.morphoMarket.deltas.borrow.scaledP2PTotal, 0, 2, "scaledTotalBorrowP2P != 0"); assertEq(test.morphoMarket.idleSupply, 0, "idleSupply != 0"); } } From 7af52d827e1933c080965ca3728c4f861f6b73f2 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 23 Feb 2023 22:40:32 +0100 Subject: [PATCH 10/32] test: fix test after merge --- .../TestInternalPositionsManagerInternal.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index 103f2361c..d462bf6df 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -205,16 +205,16 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.validateLiquidate(usdc, dai, address(this)); } - function testValidateLiquidate() public view { - this.validateLiquidate(dai, usdc, address(this)); - } - - function testAuthorizeLiquidateIfAssetNotEnabledCollateralOnMorphoAndOnPool() public { + function testValidateLiquidateIfAssetNotEnabledCollateralOnMorphoAndOnPool() public { _market[dai].isCollateral = false; _POOL.setUserUseReserveAsCollateral(usdc, false); vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotUsedAsCollateral.selector)); - this.authorizeLiquidate(dai, usdc); + this.validateLiquidate(dai, usdc, address(this)); + } + + function testValidateLiquidate() public view { + this.validateLiquidate(dai, usdc, address(this)); } function testAuthorizeLiquidateShouldPassIfCollateralAssetOnlyEnabledOnPool() public { From ba87cab30059c209a67a6dbe4c344dd2e14abb36 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Fri, 24 Feb 2023 10:13:08 +0100 Subject: [PATCH 11/32] docs: improve comments --- src/MorphoSetters.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index fc03520b9..df62a0d3b 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -74,8 +74,8 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { emit Events.TreasuryVaultSet(treasuryVault); } - /// @notice Sets the `underlying` asset as collateral or not on the pool. - /// @dev Note that it is possible to set an asset as collateral whose market is not created yet on the protocol. + /// @notice Sets the `underlying` asset as `isCollateral` on the pool, and updates Morpho to not accept the asset as collateral if `isCollateral` is false. + /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); @@ -83,7 +83,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); } - /// @notice Sets the `underlying` asset as collateral or not on Morpho. + /// @notice Sets the `underlying` asset as `isCollateral` on Morpho, and updates Morpho's collateral status on pool if `isCollateral` is true. /// @dev If the asset is set as collateral, the behavior is propagated to the pool. function setAssetIsCollateral(address underlying, bool isCollateral) external From 57b4a3b22836ffa3669c6e82193d1eeafb94dfa2 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 27 Feb 2023 12:00:17 +0100 Subject: [PATCH 12/32] refactor: apply error renaming suggestion --- src/PositionsManagerInternal.sol | 4 ++-- src/libraries/Errors.sol | 2 +- test/internal/TestInternalPositionsManagerInternal.sol | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index c7041b2c8..c2653e995 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -92,7 +92,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { function _validateSupplyCollateral(address underlying, uint256 amount, address user) internal view { Types.Market storage market = _validateInput(underlying, amount, user); if (market.isSupplyCollateralPaused()) revert Errors.SupplyCollateralIsPaused(); - if (!market.isCollateral) revert Errors.AssetNotUsedAsCollateral(); + if (!market.isCollateral) revert Errors.AssetNotCollateral(); } /// @dev Validates a borrow action. @@ -178,7 +178,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { if ( !collateralMarket.isCollateral && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlyingCollateral).id) - ) revert Errors.AssetNotUsedAsCollateral(); + ) revert Errors.AssetNotCollateral(); } /// @dev Authorizes a liquidate action. diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 8e242355f..5e90ea184 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -33,7 +33,7 @@ library Errors { error UnauthorizedWithdraw(); error UnauthorizedLiquidate(); - error AssetNotUsedAsCollateral(); + error AssetNotCollateral(); error ExceedsMaxBasisPoints(); diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index d462bf6df..a01411bea 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -102,7 +102,7 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testValidateSupplyCollateralShouldRevertIfNotCollateral() public { - vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotUsedAsCollateral.selector)); + vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotCollateral.selector)); this.validateSupplyCollateral(dai, 1, address(1)); } @@ -209,7 +209,7 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI _market[dai].isCollateral = false; _POOL.setUserUseReserveAsCollateral(usdc, false); - vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotUsedAsCollateral.selector)); + vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotCollateral.selector)); this.validateLiquidate(dai, usdc, address(this)); } From 073ff49bfaf84ae9b216f016919739b6f0b882c4 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 27 Feb 2023 22:21:53 +0100 Subject: [PATCH 13/32] test: set true for expectEmit --- test/unit/TestUnitMarketLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/TestUnitMarketLib.sol b/test/unit/TestUnitMarketLib.sol index a37424f71..193fe97d7 100644 --- a/test/unit/TestUnitMarketLib.sol +++ b/test/unit/TestUnitMarketLib.sol @@ -307,7 +307,7 @@ contract TestUnitMarketLib is Test { } function testSetAssetIsCollateral(bool isCollateral) public { - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, true, true, true); emit Events.IsCollateralSet(market.underlying, isCollateral); market.setAssetIsCollateral(isCollateral); From 027079db63e0a625942c8bf4b47604972c271b7c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 27 Feb 2023 22:22:42 +0100 Subject: [PATCH 14/32] refactor: remove check collateral --- src/PositionsManagerInternal.sol | 8 -------- test/internal/TestInternalPositionsManagerInternal.sol | 8 -------- 2 files changed, 16 deletions(-) diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index c2653e995..e249937a3 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -171,14 +171,6 @@ abstract contract PositionsManagerInternal is MatchingEngine { if (!borrowMarket.isCreated() || !collateralMarket.isCreated()) revert Errors.MarketNotCreated(); if (collateralMarket.isLiquidateCollateralPaused()) revert Errors.LiquidateCollateralIsPaused(); if (borrowMarket.isLiquidateBorrowPaused()) revert Errors.LiquidateBorrowIsPaused(); - - // Revert only if the collateral is not enabled as collateral on Morpho AND on the pool. - // This way Morpho can disable an asset as collateral on Morpho side only and still be able to liquidate unhealthy positions using this asset as collateral. - // This can be helpful in case the pool sets an asset's LTV to 0. - if ( - !collateralMarket.isCollateral - && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlyingCollateral).id) - ) revert Errors.AssetNotCollateral(); } /// @dev Authorizes a liquidate action. diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index a01411bea..84a5bc813 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -205,14 +205,6 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.validateLiquidate(usdc, dai, address(this)); } - function testValidateLiquidateIfAssetNotEnabledCollateralOnMorphoAndOnPool() public { - _market[dai].isCollateral = false; - _POOL.setUserUseReserveAsCollateral(usdc, false); - - vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotCollateral.selector)); - this.validateLiquidate(dai, usdc, address(this)); - } - function testValidateLiquidate() public view { this.validateLiquidate(dai, usdc, address(this)); } From 9eb4672f18d63f62db4978b0b9cae10a0de2b743 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 28 Feb 2023 16:52:27 +0100 Subject: [PATCH 15/32] docs: add comment about sending assets with LTV = 0 --- src/MorphoSetters.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 4241a8699..979112f3f 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -76,6 +76,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { /// @notice Sets the `underlying` asset as `isCollateral` on the pool, and updates Morpho to not accept the asset as collateral if `isCollateral` is false. /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. + /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collatreal by default, thus, blocking withdrawals from pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); From 57a57b018d09f97fcb7093d45b72cae6c53261a2 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 1 Mar 2023 15:05:15 +0100 Subject: [PATCH 16/32] docs: apply doc suggestion --- src/MorphoSetters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 979112f3f..e3bbc12e7 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -76,7 +76,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { /// @notice Sets the `underlying` asset as `isCollateral` on the pool, and updates Morpho to not accept the asset as collateral if `isCollateral` is false. /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. - /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collatreal by default, thus, blocking withdrawals from pool. + /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); From d67a71d6f60cbb96290d98c4df074e508be10ba0 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 2 Mar 2023 11:32:17 +0100 Subject: [PATCH 17/32] refactor: revert when is collateral on morpho --- src/MorphoSetters.sol | 5 ++-- src/libraries/Errors.sol | 2 ++ .../TestIntegrationAssetAsCollateral.sol | 30 ++++++++++++------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index e3bbc12e7..9edeaa66e 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -78,8 +78,9 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { - if (isCollateral && !_market[underlying].isCreated()) revert Errors.MarketNotCreated(); - if (!isCollateral && _market[underlying].isCreated()) _market[underlying].setAssetIsCollateral(isCollateral); + Types.Market storage market = _market[underlying]; + if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); + if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateral(); _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); } diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 5e90ea184..e94352d81 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -33,7 +33,9 @@ library Errors { error UnauthorizedWithdraw(); error UnauthorizedLiquidate(); + error AssetNotCollateral(); + error AssetIsCollateral(); error ExceedsMaxBasisPoints(); diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index e42e14919..f9cd62f51 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -71,6 +71,16 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { morpho.setAssetIsCollateralOnPool(link, true); } + function testSetAssetIsCollateralOnPoolShouldRevertWhenMarketIsCollateralOnMorpho() public { + morpho.setAssetIsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, true); + assertEq(_isUsingAsCollateral(dai), true); + + vm.expectRevert(Errors.AssetIsCollateral.selector); + morpho.setAssetIsCollateralOnPool(dai, false); + } + function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { vm.prank(address(morpho)); pool.setUserUseReserveAsCollateral(dai, true); @@ -121,19 +131,19 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), false); } - function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { - vm.prank(address(morpho)); - pool.setUserUseReserveAsCollateral(dai, true); - morpho.setAssetIsCollateral(dai, true); + // function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { + // vm.prank(address(morpho)); + // pool.setUserUseReserveAsCollateral(dai, true); + // morpho.setAssetIsCollateral(dai, true); - assertEq(morpho.market(dai).isCollateral, true); - assertEq(_isUsingAsCollateral(dai), true); + // assertEq(morpho.market(dai).isCollateral, true); + // assertEq(_isUsingAsCollateral(dai), true); - morpho.setAssetIsCollateralOnPool(dai, false); + // morpho.setAssetIsCollateralOnPool(dai, false); - assertEq(morpho.market(dai).isCollateral, false); - assertEq(_isUsingAsCollateral(dai), false); - } + // assertEq(morpho.market(dai).isCollateral, false); + // assertEq(_isUsingAsCollateral(dai), false); + // } function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { vm.prank(address(morpho)); From a1b05909a422b833533a4b84c6e0c2056009d0d8 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 2 Mar 2023 15:15:21 +0100 Subject: [PATCH 18/32] test: just remove the test --- Makefile | 2 +- src/MorphoSetters.sol | 12 +++++++++++- .../TestIntegrationAssetAsCollateral.sol | 14 -------------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 14be6a0ca..5a6f3de33 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ test-unit: @FOUNDRY_MATCH_CONTRACT=TestUnit make test test: - forge test -vvv + forge test -vvvvv test-integration-%: diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 9edeaa66e..1f2ec7807 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -9,6 +9,9 @@ import {Events} from "./libraries/Events.sol"; import {Errors} from "./libraries/Errors.sol"; import {MarketLib} from "./libraries/MarketLib.sol"; +import {DataTypes} from "@aave-v3-core/protocol/libraries/types/DataTypes.sol"; +import {UserConfiguration} from "@aave-v3-core/protocol/libraries/configuration/UserConfiguration.sol"; + import {MorphoInternal} from "./MorphoInternal.sol"; /// @title MorphoSetters @@ -18,6 +21,8 @@ import {MorphoInternal} from "./MorphoInternal.sol"; abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { using MarketLib for Types.Market; + using UserConfiguration for DataTypes.UserConfigurationMap; + /* MODIFIERS */ /// @notice Prevents to update a market not created yet. @@ -93,7 +98,12 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { isMarketCreated(underlying) { _market[underlying].setAssetIsCollateral(isCollateral); - if (isCollateral) _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + if (isCollateral) { + if (_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { + revert Errors.AssetIsCollateral(); + } + _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + } } /// @notice Sets the `underlying`'s reserve factor to `newReserveFactor` (in bps). diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index f9cd62f51..e69b6078c 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -131,20 +131,6 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), false); } - // function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { - // vm.prank(address(morpho)); - // pool.setUserUseReserveAsCollateral(dai, true); - // morpho.setAssetIsCollateral(dai, true); - - // assertEq(morpho.market(dai).isCollateral, true); - // assertEq(_isUsingAsCollateral(dai), true); - - // morpho.setAssetIsCollateralOnPool(dai, false); - - // assertEq(morpho.market(dai).isCollateral, false); - // assertEq(_isUsingAsCollateral(dai), false); - // } - function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { vm.prank(address(morpho)); pool.setUserUseReserveAsCollateral(dai, true); From 993b0a50b50c3aee1e13b65b575462b4d00678c7 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 2 Mar 2023 16:51:53 +0100 Subject: [PATCH 19/32] refactor: revert if asset not collateral on pool --- Makefile | 2 +- src/MorphoSetters.sol | 5 ++-- .../TestIntegrationAssetAsCollateral.sol | 23 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5a6f3de33..14be6a0ca 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ test-unit: @FOUNDRY_MATCH_CONTRACT=TestUnit make test test: - forge test -vvvvv + forge test -vvv test-integration-%: diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 1f2ec7807..e42c92d6d 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -98,9 +98,10 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { isMarketCreated(underlying) { _market[underlying].setAssetIsCollateral(isCollateral); + if (isCollateral) { - if (_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { - revert Errors.AssetIsCollateral(); + if (!_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { + revert Errors.AssetNotCollateral(); } _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); } diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index e69b6078c..bda4ac947 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -40,10 +40,21 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { morpho.setAssetIsCollateral(underlying, true); } - function testSetAssetIsCollateral() public { + function testSetAssetIsCollateralShouldRevertWhenMarketNotCollateralOnPool() public { assertEq(morpho.market(dai).isCollateral, false); assertEq(_isUsingAsCollateral(dai), false); + vm.expectRevert(Errors.AssetNotCollateral.selector); + morpho.setAssetIsCollateral(dai, true); + } + + function testSetAssetIsCollateral() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); + + assertEq(morpho.market(dai).isCollateral, false); + assertEq(_isUsingAsCollateral(dai), true); + morpho.setAssetIsCollateral(dai, true); assertEq(morpho.market(dai).isCollateral, true); @@ -72,6 +83,8 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { } function testSetAssetIsCollateralOnPoolShouldRevertWhenMarketIsCollateralOnMorpho() public { + vm.prank(address(morpho)); + pool.setUserUseReserveAsCollateral(dai, true); morpho.setAssetIsCollateral(dai, true); assertEq(morpho.market(dai).isCollateral, true); @@ -147,4 +160,12 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { function _isUsingAsCollateral(address underlying) internal view returns (bool) { return pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(underlying).id); } + + function testSetAssetIsCollateralLifecycle() public { + morpho.setAssetIsCollateralOnPool(dai, true); + morpho.setAssetIsCollateral(dai, true); + + morpho.setAssetIsCollateral(dai, false); + morpho.setAssetIsCollateralOnPool(dai, false); + } } From 39096823ea9afce435bbc127c36df8673e764ec0 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 2 Mar 2023 17:44:51 +0100 Subject: [PATCH 20/32] refactor: remove useless code --- src/MorphoSetters.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index e42c92d6d..0ff7b7abb 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -99,11 +99,11 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { { _market[underlying].setAssetIsCollateral(isCollateral); - if (isCollateral) { - if (!_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id)) { - revert Errors.AssetNotCollateral(); - } - _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + if ( + isCollateral + && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id) + ) { + revert Errors.AssetNotCollateral(); } } From 6ef27f00773f2ccd9af47b4e87c3197f2d1e8e3c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Fri, 3 Mar 2023 08:11:08 +0100 Subject: [PATCH 21/32] refactor: check first --- src/MorphoSetters.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 0ff7b7abb..4cabf960b 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -97,14 +97,14 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { onlyOwner isMarketCreated(underlying) { - _market[underlying].setAssetIsCollateral(isCollateral); - if ( isCollateral && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id) ) { revert Errors.AssetNotCollateral(); } + + _market[underlying].setAssetIsCollateral(isCollateral); } /// @notice Sets the `underlying`'s reserve factor to `newReserveFactor` (in bps). From aa7aaa30b452c9d89b91be1a729247137a504cf0 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 6 Mar 2023 17:47:08 +0100 Subject: [PATCH 22/32] docs: remove outdated comment --- src/MorphoSetters.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 4cabf960b..a083f20df 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -91,7 +91,6 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as `isCollateral` on Morpho, and updates Morpho's collateral status on pool if `isCollateral` is true. - /// @dev If the asset is set as collateral, the behavior is propagated to the pool. function setAssetIsCollateral(address underlying, bool isCollateral) external onlyOwner From bca59647bdfd49fd90a3a8df5a5c4c5f2489984c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Fri, 10 Mar 2023 16:11:03 +0100 Subject: [PATCH 23/32] fix: miss merge conflicts fixes --- src/MorphoSetters.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index a083f20df..c58716cd6 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -87,7 +87,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateral(); - _POOL.setUserUseReserveAsCollateral(underlying, isCollateral); + _pool.setUserUseReserveAsCollateral(underlying, isCollateral); } /// @notice Sets the `underlying` asset as `isCollateral` on Morpho, and updates Morpho's collateral status on pool if `isCollateral` is true. @@ -98,7 +98,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { { if ( isCollateral - && !_POOL.getUserConfiguration(address(this)).isUsingAsCollateral(_POOL.getReserveData(underlying).id) + && !_pool.getUserConfiguration(address(this)).isUsingAsCollateral(_pool.getReserveData(underlying).id) ) { revert Errors.AssetNotCollateral(); } From f35cf44156c25ba0fa3a1c50ab9d40f6e265f586 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 13 Mar 2023 13:47:00 +0100 Subject: [PATCH 24/32] refactor: correct formatting --- src/libraries/MarketLib.sol | 2 +- test/integration/TestIntegrationClaimToTreasury.sol | 2 +- test/unit/TestUnitDeltasLib.sol | 8 ++++---- test/unit/TestUnitMarketLib.sol | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libraries/MarketLib.sol b/src/libraries/MarketLib.sol index c20c3055a..5a870c3d6 100644 --- a/src/libraries/MarketLib.sol +++ b/src/libraries/MarketLib.sol @@ -165,7 +165,7 @@ library MarketLib { indexes.supply.p2pIndex, indexes.borrow.poolIndex, indexes.borrow.p2pIndex - ); + ); } function getSupplyIndexes(Types.Market storage market) diff --git a/test/integration/TestIntegrationClaimToTreasury.sol b/test/integration/TestIntegrationClaimToTreasury.sol index 895b42ab7..82dfd747b 100644 --- a/test/integration/TestIntegrationClaimToTreasury.sol +++ b/test/integration/TestIntegrationClaimToTreasury.sol @@ -136,7 +136,7 @@ contract TestIntegrationClaimToTreasury is IntegrationTest { emit Events.ReserveFeeClaimed( claimedUnderlyings[i], Math.min(claimedAmounts[i], balanceAmounts[i] - morpho.market(claimedUnderlyings[i]).idleSupply) - ); + ); } beforeBalanceTreasury[i] = ERC20(claimedUnderlyings[i]).balanceOf(treasuryVault); } diff --git a/test/unit/TestUnitDeltasLib.sol b/test/unit/TestUnitDeltasLib.sol index 30b344a7d..0f883c2ee 100644 --- a/test/unit/TestUnitDeltasLib.sol +++ b/test/unit/TestUnitDeltasLib.sol @@ -41,7 +41,7 @@ contract TestUnitDeltasLib is BaseTest { address(1), totalP2PSupply + promoted.rayDiv(indexes.supply.p2pIndex), totalP2PBorrow + amount.rayDiv(indexes.borrow.p2pIndex) - ); + ); uint256 p2pIncrease = this.increaseP2P(address(1), promoted, amount, false); assertEq(p2pIncrease, amount.rayDiv(indexes.supply.p2pIndex), "p2pIncrease"); @@ -64,7 +64,7 @@ contract TestUnitDeltasLib is BaseTest { address(1), totalP2PSupply + amount.rayDiv(indexes.supply.p2pIndex), totalP2PBorrow + promoted.rayDiv(indexes.borrow.p2pIndex) - ); + ); uint256 p2pIncrease = this.increaseP2P(address(1), promoted, amount, true); assertEq(p2pIncrease, amount.rayDiv(indexes.borrow.p2pIndex), "p2pIncrease"); @@ -99,7 +99,7 @@ contract TestUnitDeltasLib is BaseTest { address(1), totalP2PSupply.zeroFloorSub(demoted.rayDiv(indexes.supply.p2pIndex)), totalP2PBorrow.zeroFloorSub(amount.rayDiv(indexes.borrow.p2pIndex)) - ); + ); this.decreaseP2P(address(1), demoted, amount, false); assertEq(deltas.supply.scaledP2PTotal, totalP2PSupply.zeroFloorSub(demoted.rayDiv(indexes.supply.p2pIndex))); @@ -121,7 +121,7 @@ contract TestUnitDeltasLib is BaseTest { address(1), totalP2PSupply.zeroFloorSub(amount.rayDiv(indexes.supply.p2pIndex)), totalP2PBorrow.zeroFloorSub(demoted.rayDiv(indexes.borrow.p2pIndex)) - ); + ); this.decreaseP2P(address(1), demoted, amount, true); assertEq(deltas.supply.scaledP2PTotal, totalP2PSupply.zeroFloorSub(amount.rayDiv(indexes.supply.p2pIndex))); diff --git a/test/unit/TestUnitMarketLib.sol b/test/unit/TestUnitMarketLib.sol index f1fa0f42e..2ebf70cd2 100644 --- a/test/unit/TestUnitMarketLib.sol +++ b/test/unit/TestUnitMarketLib.sol @@ -234,7 +234,7 @@ contract TestUnitMarketLib is BaseTest { indexes.supply.p2pIndex, indexes.borrow.poolIndex, indexes.borrow.p2pIndex - ); + ); market.setIndexes(indexes); assertEq(market.indexes.supply.poolIndex, indexes.supply.poolIndex); From 482a46faeb99483e0dc999d2a4bb5aecf10dd05e Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Mon, 13 Mar 2023 14:50:16 +0100 Subject: [PATCH 25/32] test: remove useless test --- .../TestInternalPositionsManagerInternal.sol | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index a130b933b..2de581553 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -217,24 +217,6 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI this.validateLiquidate(dai, usdc, address(this)); } - function testAuthorizeLiquidateShouldPassIfCollateralAssetOnlyEnabledOnPool() public { - _pool.setUserUseReserveAsCollateral(usdc, true); - - _userCollaterals[address(this)].add(dai); - _userBorrows[address(this)].add(dai); - _market[dai].pauseStatuses.isDeprecated = true; - this.authorizeLiquidate(dai, dai); - } - - function testAuthorizeLiquidateShouldPassIfCollateralAssetOnlyEnabledOnMorpho() public { - _market[dai].isCollateral = true; - - _userCollaterals[address(this)].add(dai); - _userBorrows[address(this)].add(dai); - _market[dai].pauseStatuses.isDeprecated = true; - this.authorizeLiquidate(dai, dai); - } - function testAuthorizeLiquidateShouldReturnMaxCloseFactorIfDeprecatedBorrow() public { _market[dai].isCollateral = true; _userCollaterals[address(this)].add(dai); From 3df3587591ee349e4cac80c033f943b40f04136c Mon Sep 17 00:00:00 2001 From: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Date: Tue, 14 Mar 2023 21:09:54 +0100 Subject: [PATCH 26/32] docs: apply comment suggestions Co-authored-by: MathisGD <74971347+MathisGD@users.noreply.github.com> Signed-off-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> --- src/MorphoSetters.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index c58716cd6..3a68f77d3 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -79,7 +79,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { emit Events.TreasuryVaultSet(treasuryVault); } - /// @notice Sets the `underlying` asset as `isCollateral` on the pool, and updates Morpho to not accept the asset as collateral if `isCollateral` is false. + /// @notice Sets the `underlying` asset as `isCollateral` on the pool. /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { @@ -90,7 +90,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { _pool.setUserUseReserveAsCollateral(underlying, isCollateral); } - /// @notice Sets the `underlying` asset as `isCollateral` on Morpho, and updates Morpho's collateral status on pool if `isCollateral` is true. + /// @notice Sets the `underlying` asset as `isCollateral` on Morpho. function setAssetIsCollateral(address underlying, bool isCollateral) external onlyOwner From f11b3f2c4574ac8bd11875d369924f10e7307fd0 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 14 Mar 2023 21:28:38 +0100 Subject: [PATCH 27/32] refactor: revert earlier if isCollateral = false too --- src/MorphoSetters.sol | 5 +---- test/integration/TestIntegrationAssetAsCollateral.sol | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 3a68f77d3..8a0ef8600 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -96,10 +96,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { onlyOwner isMarketCreated(underlying) { - if ( - isCollateral - && !_pool.getUserConfiguration(address(this)).isUsingAsCollateral(_pool.getReserveData(underlying).id) - ) { + if (!_pool.getUserConfiguration(address(this)).isUsingAsCollateral(_pool.getReserveData(underlying).id)) { revert Errors.AssetNotCollateral(); } diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index bda4ac947..5aea8b340 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -41,11 +41,13 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { } function testSetAssetIsCollateralShouldRevertWhenMarketNotCollateralOnPool() public { - assertEq(morpho.market(dai).isCollateral, false); assertEq(_isUsingAsCollateral(dai), false); vm.expectRevert(Errors.AssetNotCollateral.selector); morpho.setAssetIsCollateral(dai, true); + + vm.expectRevert(Errors.AssetNotCollateral.selector); + morpho.setAssetIsCollateral(dai, false); } function testSetAssetIsCollateral() public { From a51ce76d108fe321fdfacab866de83b211173ca7 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 14 Mar 2023 21:41:23 +0100 Subject: [PATCH 28/32] refactor: more explicit errors --- src/MorphoSetters.sol | 4 ++-- src/PositionsManagerInternal.sol | 2 +- src/libraries/Errors.sol | 5 +++-- test/integration/TestIntegrationAssetAsCollateral.sol | 6 +++--- test/internal/TestInternalPositionsManagerInternal.sol | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 8a0ef8600..39fab6b6f 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -85,7 +85,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { Types.Market storage market = _market[underlying]; if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); - if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateral(); + if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateralOnMorpho(); _pool.setUserUseReserveAsCollateral(underlying, isCollateral); } @@ -97,7 +97,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { isMarketCreated(underlying) { if (!_pool.getUserConfiguration(address(this)).isUsingAsCollateral(_pool.getReserveData(underlying).id)) { - revert Errors.AssetNotCollateral(); + revert Errors.AssetNotCollateralOnPool(); } _market[underlying].setAssetIsCollateral(isCollateral); diff --git a/src/PositionsManagerInternal.sol b/src/PositionsManagerInternal.sol index ca0afa80e..69f7a3a7a 100644 --- a/src/PositionsManagerInternal.sol +++ b/src/PositionsManagerInternal.sol @@ -93,7 +93,7 @@ abstract contract PositionsManagerInternal is MatchingEngine { function _validateSupplyCollateral(address underlying, uint256 amount, address user) internal view { Types.Market storage market = _validateInput(underlying, amount, user); if (market.isSupplyCollateralPaused()) revert Errors.SupplyCollateralIsPaused(); - if (!market.isCollateral) revert Errors.AssetNotCollateral(); + if (!market.isCollateral) revert Errors.AssetNotCollateralOnMorpho(); } /// @dev Validates a borrow action. diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 6ba5ec766..617b2196b 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -41,8 +41,9 @@ library Errors { error UnauthorizedLiquidate(); error SentinelLiquidateNotEnabled(); - error AssetNotCollateral(); - error AssetIsCollateral(); + error AssetNotCollateralOnPool(); + error AssetNotCollateralOnMorpho(); + error AssetIsCollateralOnMorpho(); error ExceedsMaxBasisPoints(); diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index 5aea8b340..cedc50579 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -43,10 +43,10 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { function testSetAssetIsCollateralShouldRevertWhenMarketNotCollateralOnPool() public { assertEq(_isUsingAsCollateral(dai), false); - vm.expectRevert(Errors.AssetNotCollateral.selector); + vm.expectRevert(Errors.AssetNotCollateralOnPool.selector); morpho.setAssetIsCollateral(dai, true); - vm.expectRevert(Errors.AssetNotCollateral.selector); + vm.expectRevert(Errors.AssetNotCollateralOnPool.selector); morpho.setAssetIsCollateral(dai, false); } @@ -92,7 +92,7 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(morpho.market(dai).isCollateral, true); assertEq(_isUsingAsCollateral(dai), true); - vm.expectRevert(Errors.AssetIsCollateral.selector); + vm.expectRevert(Errors.AssetIsCollateralOnMorpho.selector); morpho.setAssetIsCollateralOnPool(dai, false); } diff --git a/test/internal/TestInternalPositionsManagerInternal.sol b/test/internal/TestInternalPositionsManagerInternal.sol index f943d5287..637104333 100644 --- a/test/internal/TestInternalPositionsManagerInternal.sol +++ b/test/internal/TestInternalPositionsManagerInternal.sol @@ -103,7 +103,7 @@ contract TestInternalPositionsManagerInternal is InternalTest, PositionsManagerI } function testValidateSupplyCollateralShouldRevertIfNotCollateral() public { - vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotCollateral.selector)); + vm.expectRevert(abi.encodeWithSelector(Errors.AssetNotCollateralOnMorpho.selector)); this.validateSupplyCollateral(dai, 1, address(1)); } From c65e0f61d708bb44e6ecd35642b891b54b942528 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 14 Mar 2023 22:00:20 +0100 Subject: [PATCH 29/32] refactor: scope to created market for setAssetIsCollateralOnPool --- src/MorphoSetters.sol | 8 +++++--- .../TestIntegrationAssetAsCollateral.sol | 16 +++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 39fab6b6f..2a1c0c4a4 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -80,9 +80,11 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as `isCollateral` on the pool. - /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. - /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. - function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { + function setAssetIsCollateralOnPool(address underlying, bool isCollateral) + external + onlyOwner + isMarketCreated(underlying) + { Types.Market storage market = _market[underlying]; if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateralOnMorpho(); diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index cedc50579..0034ed0ab 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -82,6 +82,9 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { vm.expectRevert(Errors.MarketNotCreated.selector); morpho.setAssetIsCollateralOnPool(link, true); + + vm.expectRevert(Errors.MarketNotCreated.selector); + morpho.setAssetIsCollateralOnPool(link, false); } function testSetAssetIsCollateralOnPoolShouldRevertWhenMarketIsCollateralOnMorpho() public { @@ -133,19 +136,6 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(_isUsingAsCollateral(dai), true); } - function testSetAssetIsNotCollateralOnPoolWhenMarketIsNotCreated() public { - vm.prank(address(morpho)); - pool.setUserUseReserveAsCollateral(link, true); - - assertEq(morpho.market(link).isCollateral, false); - assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), true); - - morpho.setAssetIsCollateralOnPool(link, false); - - assertEq(morpho.market(link).isCollateral, false); - assertEq(pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(link).id), false); - } - function testSetAssetIsNotCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { vm.prank(address(morpho)); pool.setUserUseReserveAsCollateral(dai, true); From b3de2fb6c89f94646ca53f6c4479176b2c6d9f63 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 15 Mar 2023 14:42:32 +0100 Subject: [PATCH 30/32] refactor: remove useless check + add invariant tests --- src/MorphoSetters.sol | 8 +++- .../TestIntegrationAssetAsCollateral.sol | 40 +++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 39fab6b6f..9dfb696b2 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -80,17 +80,23 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as `isCollateral` on the pool. + /// @dev The following invariants must always hold: + /// - isCollateral on Morpho => isCollateral on pool + /// - !isCollateral on pool => !isCollateral on Morpho /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { Types.Market storage market = _market[underlying]; if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); - if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateralOnMorpho(); + if (market.isCollateral) revert Errors.AssetIsCollateralOnMorpho(); _pool.setUserUseReserveAsCollateral(underlying, isCollateral); } /// @notice Sets the `underlying` asset as `isCollateral` on Morpho. + /// @dev The following invariants must always hold: + /// - isCollateral on Morpho => isCollateral on pool + /// - !isCollateral on pool => !isCollateral on Morpho function setAssetIsCollateral(address underlying, bool isCollateral) external onlyOwner diff --git a/test/integration/TestIntegrationAssetAsCollateral.sol b/test/integration/TestIntegrationAssetAsCollateral.sol index cedc50579..ae46f1543 100644 --- a/test/integration/TestIntegrationAssetAsCollateral.sol +++ b/test/integration/TestIntegrationAssetAsCollateral.sol @@ -8,9 +8,10 @@ import {UserConfiguration} from "@aave-v3-core/protocol/libraries/configuration/ import {Morpho} from "src/Morpho.sol"; +import {InvariantTest as ForgeInvariantTest} from "@forge-std/InvariantTest.sol"; import "test/helpers/IntegrationTest.sol"; -contract TestIntegrationAssetAsCollateral is IntegrationTest { +contract TestIntegrationAssetAsCollateral is IntegrationTest, ForgeInvariantTest { using UserConfiguration for DataTypes.UserConfigurationMap; function setUp() public override { @@ -33,6 +34,28 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { pool.setUserUseReserveAsCollateral(weth, false); pool.setUserUseReserveAsCollateral(link, false); vm.stopPrank(); + + targetSender(address(this)); + targetContract(address(morpho)); + + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = this.setAssetIsCollateralOnPool.selector; + selectors[1] = this.setAssetIsCollateral.selector; + + targetSelector(FuzzSelector({addr: address(this), selectors: selectors})); + } + + function setAssetIsCollateralOnPool(bool isCollateral) external { + morpho.setAssetIsCollateralOnPool(dai, isCollateral); + } + + function setAssetIsCollateral(bool isCollateral) external { + morpho.setAssetIsCollateral(dai, isCollateral); + } + + function invariantAssetAsCollateral() public { + if (morpho.market(dai).isCollateral) assertTrue(_isUsingAsCollateral(dai)); + if (!_isUsingAsCollateral(dai)) assertFalse(morpho.market(dai).isCollateral); } function testSetAssetIsCollateralShouldRevertWhenMarketNotCreated(address underlying) public { @@ -96,7 +119,7 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { morpho.setAssetIsCollateralOnPool(dai, false); } - function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsCollateralOnMorphoAndOnPool() public { + function testSetAssetIsCollateralOnPoolShouldRevertWhenMarketIsCreatedAndIsCollateralOnMorpho() public { vm.prank(address(morpho)); pool.setUserUseReserveAsCollateral(dai, true); morpho.setAssetIsCollateral(dai, true); @@ -104,10 +127,11 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(morpho.market(dai).isCollateral, true); assertEq(_isUsingAsCollateral(dai), true); + vm.expectRevert(Errors.AssetIsCollateralOnMorpho.selector); morpho.setAssetIsCollateralOnPool(dai, true); - assertEq(morpho.market(dai).isCollateral, true); - assertEq(_isUsingAsCollateral(dai), true); + vm.expectRevert(Errors.AssetIsCollateralOnMorpho.selector); + morpho.setAssetIsCollateralOnPool(dai, false); } function testSetAssetIsCollateralOnPoolWhenMarketIsCreatedAndIsNotCollateralOnMorphoOnly() public { @@ -159,10 +183,6 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { assertEq(_isUsingAsCollateral(dai), false); } - function _isUsingAsCollateral(address underlying) internal view returns (bool) { - return pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(underlying).id); - } - function testSetAssetIsCollateralLifecycle() public { morpho.setAssetIsCollateralOnPool(dai, true); morpho.setAssetIsCollateral(dai, true); @@ -170,4 +190,8 @@ contract TestIntegrationAssetAsCollateral is IntegrationTest { morpho.setAssetIsCollateral(dai, false); morpho.setAssetIsCollateralOnPool(dai, false); } + + function _isUsingAsCollateral(address underlying) internal view returns (bool) { + return pool.getUserConfiguration(address(morpho)).isUsingAsCollateral(pool.getReserveData(underlying).id); + } } From ad29aa5d86ae44e789b02fe797dd794ee18ab76a Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 15 Mar 2023 15:30:04 +0100 Subject: [PATCH 31/32] refactor: remove useless check --- src/MorphoSetters.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 2a1c0c4a4..ef568f1c4 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -85,9 +85,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { onlyOwner isMarketCreated(underlying) { - Types.Market storage market = _market[underlying]; - if (isCollateral && !market.isCreated()) revert Errors.MarketNotCreated(); - if (!isCollateral && market.isCollateral) revert Errors.AssetIsCollateralOnMorpho(); + if (_market[underlying].isCollateral) revert Errors.AssetIsCollateralOnMorpho(); _pool.setUserUseReserveAsCollateral(underlying, isCollateral); } From a57a7c0ac42598d48ab84d49b31e4751103ea343 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 15 Mar 2023 18:35:01 +0100 Subject: [PATCH 32/32] docs: improve doc --- src/MorphoSetters.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/MorphoSetters.sol b/src/MorphoSetters.sol index 9dfb696b2..b116c8d10 100644 --- a/src/MorphoSetters.sol +++ b/src/MorphoSetters.sol @@ -80,9 +80,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as `isCollateral` on the pool. - /// @dev The following invariants must always hold: - /// - isCollateral on Morpho => isCollateral on pool - /// - !isCollateral on pool => !isCollateral on Morpho + /// @dev The following invariant must hold: is collateral on Morpho => is collateral on pool. /// @dev Note that it is possible to set an asset as non-collateral even if the market is not created yet on Morpho. /// This is needed because an aToken with LTV = 0 can be sent to Morpho and would be set as collateral by default, thus blocking withdrawals from the pool. function setAssetIsCollateralOnPool(address underlying, bool isCollateral) external onlyOwner { @@ -94,9 +92,7 @@ abstract contract MorphoSetters is IMorphoSetters, MorphoInternal { } /// @notice Sets the `underlying` asset as `isCollateral` on Morpho. - /// @dev The following invariants must always hold: - /// - isCollateral on Morpho => isCollateral on pool - /// - !isCollateral on pool => !isCollateral on Morpho + /// @dev The following invariant must hold: is collateral on Morpho => is collateral on pool. function setAssetIsCollateral(address underlying, bool isCollateral) external onlyOwner