diff --git a/README.md b/README.md index e65d6c9b..67c239b0 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ bun install - [Aave v3 technical Paper](./techpaper/Aave_V3_Technical_Paper.pdf) - [v3 to v3.0.2 production upgrade](https://github.com/bgd-labs/proposal-3.0.2-upgrade/blob/main/README.md) - [Aave v3.1 features](./docs/Aave-v3.1-features.md) +- [Set Ltv to 0 on Freeze Feature State diagram](./docs/freeze-ltv0-states.png)
diff --git a/docs/Aave-v3.1-features.md b/docs/Aave-v3.1-features.md index 0e3a2b95..bbb7bb98 100644 --- a/docs/Aave-v3.1-features.md +++ b/docs/Aave-v3.1-features.md @@ -72,7 +72,7 @@ Given its implications and criticality, virtual accounting can be considered the - [ConfigurationInputTypes](../src/core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol) - Added flag `useVirtualBalance` for a new listing to use or not virtual balance. - [ReserveConfiguration](../src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol) - - Added logic to store and set/get the new `VIRTUAL_ACC_ACTIVE` flag. + - Added logic to set/get the new configuration flag for if a reserve has virtual account active. - [Errors](../src/core/contracts/protocol/libraries/helpers/Errors.sol) - In relation with this feature, added the `WITHDRAW_TO_ATOKEN` and `SUPPLY_TO_ATOKEN` errors. - [Pool](../src/core/contracts/protocol/pool/Pool.sol) diff --git a/docs/freeze-ltv0-states.png b/docs/freeze-ltv0-states.png new file mode 100644 index 00000000..471f0f35 Binary files /dev/null and b/docs/freeze-ltv0-states.png differ diff --git a/src/core/contracts/interfaces/IPool.sol b/src/core/contracts/interfaces/IPool.sol index f746d172..4e49aa40 100644 --- a/src/core/contracts/interfaces/IPool.sol +++ b/src/core/contracts/interfaces/IPool.sol @@ -381,10 +381,10 @@ interface IPool { function swapBorrowRateMode(address asset, uint256 interestRateMode) external; /** - * @notice Allows a borrower to swap his debt between stable and variable mode, - * @dev introduce in a flavor stable rate deprecation + * @notice Permissionless method which allows anyone to swap a users stable debt to variable debt + * @dev Introduced in favor of stable rate deprecation * @param asset The address of the underlying asset borrowed - * @param user The address of the user to be swapped + * @param user The address of the user whose debt will be swapped from stable to variable */ function swapToVariable(address asset, address user) external; diff --git a/src/core/contracts/interfaces/IPoolConfigurator.sol b/src/core/contracts/interfaces/IPoolConfigurator.sol index beab809a..4c39d44e 100644 --- a/src/core/contracts/interfaces/IPoolConfigurator.sol +++ b/src/core/contracts/interfaces/IPoolConfigurator.sol @@ -47,12 +47,6 @@ interface IPoolConfigurator { */ event PendingLtvChanged(address indexed asset, uint256 ltv); - /** - * @dev Emitted when the asset is unfrozen and pending ltv is removed. - * @param asset The address of the underlying asset of the reserve - */ - event PendingLtvRemoved(address indexed asset); - /** * @dev Emitted when the collateralization risk parameters for the specified asset are updated. * @param asset The address of the underlying asset of the reserve @@ -144,6 +138,12 @@ interface IPoolConfigurator { */ event LiquidationGracePeriodChanged(address indexed asset, uint40 gracePeriodUntil); + /** + * @dev Emitted when the liquidation grace period is disabled. + * @param asset The address of the underlying asset of the reserve + */ + event LiquidationGracePeriodDisabled(address indexed asset); + /** * @dev Emitted when the unbacked mint cap of a reserve is updated. * @param asset The address of the underlying asset of the reserve @@ -289,6 +289,8 @@ interface IPoolConfigurator { /** * @notice Initializes multiple reserves. + * @dev param useVirtualBalance of the input struct should be true for all normal assets and should be false + * only in special cases (ex. GHO) where an asset is minted instead of supplied. * @param input The array of initialization parameters */ function initReserves(ConfiguratorInputTypes.InitReserveInput[] calldata input) external; @@ -401,6 +403,13 @@ interface IPoolConfigurator { */ function setReservePause(address asset, bool paused) external; + /** + * @notice Disables liquidation grace period for the asset. The liquidation grace period is set in the past + * so that liquidations are allowed for the asset. + * @param asset The address of the underlying asset of the reserve + */ + function disableLiquidationGracePeriod(address asset) external; + /** * @notice Updates the reserve factor of a reserve. * @param asset The address of the underlying asset of the reserve @@ -549,10 +558,10 @@ interface IPoolConfigurator { function setSiloedBorrowing(address asset, bool siloed) external; /** - * @notice Gets pending ltv value and flag if it is set + * @notice Gets pending ltv value * @param asset The new siloed borrowing state */ - function getPendingLtv(address asset) external returns (uint256, bool); + function getPendingLtv(address asset) external returns (uint256); /** * @notice Gets the address of the external ConfiguratorLogic diff --git a/src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol b/src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol index 700b129a..0408f3ce 100644 --- a/src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol +++ b/src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol @@ -29,7 +29,7 @@ library ReserveConfiguration { uint256 internal constant EMODE_CATEGORY_MASK = 0xFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore uint256 internal constant UNBACKED_MINT_CAP_MASK = 0xFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore uint256 internal constant DEBT_CEILING_MASK = 0xF0000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore - uint256 internal constant VIRTUAL_ACC_ACTIVE = 0xEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore + uint256 internal constant VIRTUAL_ACC_ACTIVE_MASK = 0xEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore /// @dev For the LTV, the start bit is 0 (up to 15), hence no bitshifting is needed uint256 internal constant LIQUIDATION_THRESHOLD_START_BIT_POSITION = 16; @@ -556,19 +556,21 @@ library ReserveConfiguration { bool active ) internal pure { self.data = - (self.data & VIRTUAL_ACC_ACTIVE) | + (self.data & VIRTUAL_ACC_ACTIVE_MASK) | (uint256(active ? 1 : 0) << VIRTUAL_ACC_START_BIT_POSITION); } /** * @notice Gets the virtual account active/not state of the reserve + * @dev The state should be true for all normal assets and should be false + * only in special cases (ex. GHO) where an asset is minted instead of supplied. * @param self The reserve configuration * @return The active state */ function getIsVirtualAccActive( DataTypes.ReserveConfigurationMap memory self ) internal pure returns (bool) { - return (self.data & ~VIRTUAL_ACC_ACTIVE) != 0; + return (self.data & ~VIRTUAL_ACC_ACTIVE_MASK) != 0; } /** diff --git a/src/core/contracts/protocol/libraries/helpers/Errors.sol b/src/core/contracts/protocol/libraries/helpers/Errors.sol index 2e4521b4..18547a88 100644 --- a/src/core/contracts/protocol/libraries/helpers/Errors.sol +++ b/src/core/contracts/protocol/libraries/helpers/Errors.sol @@ -104,4 +104,5 @@ library Errors { string public constant CALLER_NOT_RISK_OR_POOL_OR_EMERGENCY_ADMIN = '96'; // 'The caller of the function is not a risk, pool or emergency admin' string public constant LIQUIDATION_GRACE_SENTINEL_CHECK_FAILED = '97'; // 'Liquidation grace sentinel validation failed' string public constant INVALID_GRACE_PERIOD = '98'; // Grace period above a valid range + string public constant INVALID_FREEZE_STATE = '99'; // Reserve is already in the passed freeze state } diff --git a/src/core/contracts/protocol/libraries/logic/BorrowLogic.sol b/src/core/contracts/protocol/libraries/logic/BorrowLogic.sol index 91ea0fa6..cdcf0101 100644 --- a/src/core/contracts/protocol/libraries/logic/BorrowLogic.sol +++ b/src/core/contracts/protocol/libraries/logic/BorrowLogic.sol @@ -308,6 +308,7 @@ library BorrowLogic { * @param reserve The of the reserve of the asset being repaid * @param userConfig The user configuration mapping that tracks the supplied/borrowed assets * @param asset The asset of the position being swapped + * @param user The user whose debt position is being swapped * @param interestRateMode The current interest rate mode of the position being swapped */ function executeSwapBorrowRateMode( diff --git a/src/core/contracts/protocol/libraries/logic/ConfiguratorLogic.sol b/src/core/contracts/protocol/libraries/logic/ConfiguratorLogic.sol index 39a448b4..01580d9e 100644 --- a/src/core/contracts/protocol/libraries/logic/ConfiguratorLogic.sol +++ b/src/core/contracts/protocol/libraries/logic/ConfiguratorLogic.sol @@ -8,7 +8,9 @@ import {InitializableImmutableAdminUpgradeabilityProxy} from '../aave-upgradeabi import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {DataTypes} from '../types/DataTypes.sol'; +import {Errors} from '../helpers/Errors.sol'; import {ConfiguratorInputTypes} from '../types/ConfiguratorInputTypes.sol'; +import {IERC20Detailed} from '../../../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; /** * @title ConfiguratorLogic library @@ -52,6 +54,10 @@ library ConfiguratorLogic { IPool pool, ConfiguratorInputTypes.InitReserveInput calldata input ) external { + // It is an assumption that the asset listed is non-malicious, and the external call doesn't create re-entrancies + uint8 underlyingAssetDecimals = IERC20Detailed(input.underlyingAsset).decimals(); + require(underlyingAssetDecimals > 5, Errors.INVALID_DECIMALS); + address aTokenProxyAddress = _initTokenWithProxy( input.aTokenImpl, abi.encodeWithSelector( @@ -60,7 +66,7 @@ library ConfiguratorLogic { input.treasury, input.underlyingAsset, input.incentivesController, - input.underlyingAssetDecimals, + underlyingAssetDecimals, input.aTokenName, input.aTokenSymbol, input.params @@ -74,7 +80,7 @@ library ConfiguratorLogic { pool, input.underlyingAsset, input.incentivesController, - input.underlyingAssetDecimals, + underlyingAssetDecimals, input.stableDebtTokenName, input.stableDebtTokenSymbol, input.params @@ -88,7 +94,7 @@ library ConfiguratorLogic { pool, input.underlyingAsset, input.incentivesController, - input.underlyingAssetDecimals, + underlyingAssetDecimals, input.variableDebtTokenName, input.variableDebtTokenSymbol, input.params @@ -105,7 +111,7 @@ library ConfiguratorLogic { DataTypes.ReserveConfigurationMap memory currentConfig = DataTypes.ReserveConfigurationMap(0); - currentConfig.setDecimals(input.underlyingAssetDecimals); + currentConfig.setDecimals(underlyingAssetDecimals); currentConfig.setActive(true); currentConfig.setPaused(false); diff --git a/src/core/contracts/protocol/libraries/logic/EModeLogic.sol b/src/core/contracts/protocol/libraries/logic/EModeLogic.sol index 82b08a15..b61e440c 100644 --- a/src/core/contracts/protocol/libraries/logic/EModeLogic.sol +++ b/src/core/contracts/protocol/libraries/logic/EModeLogic.sol @@ -5,7 +5,6 @@ import {GPv2SafeERC20} from '../../../dependencies/gnosis/contracts/GPv2SafeERC2 import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol'; import {UserConfiguration} from '../configuration/UserConfiguration.sol'; -import {Errors} from '../helpers/Errors.sol'; import {WadRayMath} from '../math/WadRayMath.sol'; import {PercentageMath} from '../math/PercentageMath.sol'; import {DataTypes} from '../types/DataTypes.sol'; diff --git a/src/core/contracts/protocol/libraries/logic/FlashLoanLogic.sol b/src/core/contracts/protocol/libraries/logic/FlashLoanLogic.sol index 615b2fdd..f6edf532 100644 --- a/src/core/contracts/protocol/libraries/logic/FlashLoanLogic.sol +++ b/src/core/contracts/protocol/libraries/logic/FlashLoanLogic.sol @@ -9,7 +9,6 @@ import {IPool} from '../../../interfaces/IPool.sol'; import {IFlashLoanReceiver} from '../../../flashloan/interfaces/IFlashLoanReceiver.sol'; import {IFlashLoanSimpleReceiver} from '../../../flashloan/interfaces/IFlashLoanSimpleReceiver.sol'; import {IPoolAddressesProvider} from '../../../interfaces/IPoolAddressesProvider.sol'; -import {UserConfiguration} from '../configuration/UserConfiguration.sol'; import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol'; import {Errors} from '../helpers/Errors.sol'; import {WadRayMath} from '../math/WadRayMath.sol'; diff --git a/src/core/contracts/protocol/libraries/logic/ValidationLogic.sol b/src/core/contracts/protocol/libraries/logic/ValidationLogic.sol index 2486db31..79c734c0 100644 --- a/src/core/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/src/core/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -5,8 +5,6 @@ import {IERC20} from '../../../dependencies/openzeppelin/contracts/IERC20.sol'; import {Address} from '../../../dependencies/openzeppelin/contracts/Address.sol'; import {GPv2SafeERC20} from '../../../dependencies/gnosis/contracts/GPv2SafeERC20.sol'; import {IReserveInterestRateStrategy} from '../../../interfaces/IReserveInterestRateStrategy.sol'; -import {IStableDebtToken} from '../../../interfaces/IStableDebtToken.sol'; -import {IScaledBalanceToken} from '../../../interfaces/IScaledBalanceToken.sol'; import {IPriceOracleGetter} from '../../../interfaces/IPriceOracleGetter.sol'; import {IAToken} from '../../../interfaces/IAToken.sol'; import {IPriceOracleSentinel} from '../../../interfaces/IPriceOracleSentinel.sol'; @@ -471,6 +469,9 @@ library ValidationLogic { ) internal view { require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS); for (uint256 i = 0; i < assets.length; i++) { + for (uint256 j = i + 1; j < assets.length; j++) { + require(assets[i] != assets[j], Errors.INCONSISTENT_FLASHLOAN_PARAMS); + } validateFlashloanSimple(reservesData[assets[i]], amounts[i]); } } diff --git a/src/core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol b/src/core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol index ad297064..7a80594e 100644 --- a/src/core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol +++ b/src/core/contracts/protocol/libraries/types/ConfiguratorInputTypes.sol @@ -6,7 +6,6 @@ library ConfiguratorInputTypes { address aTokenImpl; address stableDebtTokenImpl; address variableDebtTokenImpl; - uint8 underlyingAssetDecimals; bool useVirtualBalance; address interestRateStrategyAddress; address underlyingAsset; diff --git a/src/core/contracts/protocol/libraries/types/DataTypes.sol b/src/core/contracts/protocol/libraries/types/DataTypes.sol index 22920a32..64f3e891 100644 --- a/src/core/contracts/protocol/libraries/types/DataTypes.sol +++ b/src/core/contracts/protocol/libraries/types/DataTypes.sol @@ -56,7 +56,7 @@ library DataTypes { uint40 lastUpdateTimestamp; //the id of the reserve. Represents the position in the list of the active reserves uint16 id; - //timestamp in the future, until when liquidations are not allowed on the reserve + //timestamp until when liquidations are not allowed on the reserve, if set to past liquidations will be allowed uint40 liquidationGracePeriodUntil; //aToken address address aTokenAddress; diff --git a/src/core/contracts/protocol/pool/DefaultReserveInterestRateStrategyV2.sol b/src/core/contracts/protocol/pool/DefaultReserveInterestRateStrategyV2.sol index 5620eef0..cd7dcf64 100644 --- a/src/core/contracts/protocol/pool/DefaultReserveInterestRateStrategyV2.sol +++ b/src/core/contracts/protocol/pool/DefaultReserveInterestRateStrategyV2.sol @@ -117,7 +117,7 @@ contract DefaultReserveInterestRateStrategyV2 is IDefaultInterestRateStrategyV2 ) external view virtual override returns (uint256, uint256, uint256) { InterestRateDataRay memory rateData = _rayifyRateData(_interestRateData[params.reserve]); - // @note This is a short circuit to allow mintable assets, which by definition cannot be supplied + // @note This is a short circuit to allow mintable assets (ex. GHO), which by definition cannot be supplied // and thus do not use virtual underlying balances. if (!params.usingVirtualBalance) { return (0, 0, rateData.baseVariableBorrowRate); diff --git a/src/core/contracts/protocol/pool/PoolConfigurator.sol b/src/core/contracts/protocol/pool/PoolConfigurator.sol index e023dc6d..9a6ced2c 100644 --- a/src/core/contracts/protocol/pool/PoolConfigurator.sol +++ b/src/core/contracts/protocol/pool/PoolConfigurator.sol @@ -30,7 +30,6 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator IPool internal _pool; mapping(address => uint256) internal _pendingLtv; - mapping(address => bool) internal _isPendingLtvSet; uint40 public constant MAX_GRACE_PERIOD = 4 hours; @@ -81,9 +80,8 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator ConfiguratorInputTypes.InitReserveInput[] calldata input ) external override onlyAssetListingOrPoolAdmins { IPool cachedPool = _pool; - for (uint256 i = 0; i < input.length; i++) { - require(IERC20Detailed(input[i].underlyingAsset).decimals() > 5, Errors.INVALID_DECIMALS); + for (uint256 i = 0; i < input.length; i++) { ConfiguratorLogic.executeInitReserve(cachedPool, input[i]); emit ReserveInterestRateDataChanged( input[i].underlyingAsset, @@ -164,9 +162,11 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator _checkNoSuppliers(asset); } + uint256 newLtv = ltv; + if (currentConfig.getFrozen()) { _pendingLtv[asset] = ltv; - _isPendingLtvSet[asset] = true; + newLtv = 0; emit PendingLtvChanged(asset, ltv); } else { @@ -178,7 +178,7 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator _pool.setConfiguration(asset, currentConfig); - emit CollateralConfigurationChanged(asset, ltv, liquidationThreshold, liquidationBonus); + emit CollateralConfigurationChanged(asset, newLtv, liquidationThreshold, liquidationBonus); } /// @inheritdoc IPoolConfigurator @@ -222,24 +222,32 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator bool freeze ) external override onlyRiskOrPoolOrEmergencyAdmins { DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); + + require(freeze != currentConfig.getFrozen(), Errors.INVALID_FREEZE_STATE); + currentConfig.setFrozen(freeze); + uint256 ltvSet; + uint256 pendingLtvSet; + if (freeze) { - _pendingLtv[asset] = currentConfig.getLtv(); - _isPendingLtvSet[asset] = true; + pendingLtvSet = currentConfig.getLtv(); + _pendingLtv[asset] = pendingLtvSet; currentConfig.setLtv(0); - - emit PendingLtvChanged(asset, currentConfig.getLtv()); - } else if (_isPendingLtvSet[asset]) { - uint256 ltv = _pendingLtv[asset]; - currentConfig.setLtv(ltv); - + } else { + ltvSet = _pendingLtv[asset]; + currentConfig.setLtv(ltvSet); delete _pendingLtv[asset]; - delete _isPendingLtvSet[asset]; - - emit PendingLtvRemoved(asset); } + emit PendingLtvChanged(asset, pendingLtvSet); + emit CollateralConfigurationChanged( + asset, + ltvSet, + currentConfig.getLiquidationThreshold(), + currentConfig.getLiquidationBonus() + ); + _pool.setConfiguration(asset, currentConfig); emit ReserveFrozen(asset, freeze); } @@ -280,6 +288,14 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator setReservePause(asset, paused, 0); } + /// @inheritdoc IPoolConfigurator + function disableLiquidationGracePeriod(address asset) external override onlyEmergencyOrPoolAdmin { + // set the liquidation grace period in the past to disable liquidation grace period + _pool.setLiquidationGracePeriod(asset, 0); + + emit LiquidationGracePeriodDisabled(asset); + } + /// @inheritdoc IPoolConfigurator function setReserveFactor( address asset, @@ -408,7 +424,11 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator for (uint256 i = 0; i < reserves.length; i++) { DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(reserves[i]); if (categoryId == currentConfig.getEModeCategory()) { - require(ltv > currentConfig.getLtv(), Errors.INVALID_EMODE_CATEGORY_PARAMS); + uint256 currentLtv = currentConfig.getFrozen() + ? _pendingLtv[reserves[i]] + : currentConfig.getLtv(); + require(ltv > currentLtv, Errors.INVALID_EMODE_CATEGORY_PARAMS); + require( liquidationThreshold > currentConfig.getLiquidationThreshold(), Errors.INVALID_EMODE_CATEGORY_PARAMS @@ -537,8 +557,8 @@ abstract contract PoolConfigurator is VersionedInitializable, IPoolConfigurator } /// @inheritdoc IPoolConfigurator - function getPendingLtv(address asset) external view override returns (uint256, bool) { - return (_pendingLtv[asset], _isPendingLtvSet[asset]); + function getPendingLtv(address asset) external view override returns (uint256) { + return _pendingLtv[asset]; } /// @inheritdoc IPoolConfigurator diff --git a/src/periphery/contracts/v3-config-engine/libraries/ListingEngine.sol b/src/periphery/contracts/v3-config-engine/libraries/ListingEngine.sol index 15550697..4e1e8a7f 100644 --- a/src/periphery/contracts/v3-config-engine/libraries/ListingEngine.sol +++ b/src/periphery/contracts/v3-config-engine/libraries/ListingEngine.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import {IERC20Metadata} from 'solidity-utils/contracts/oz-common/interfaces/IERC20Metadata.sol'; import {IAaveV3ConfigEngine as IEngine, IPoolConfigurator, IPool, IDefaultInterestRateStrategyV2} from '../IAaveV3ConfigEngine.sol'; import {PriceFeedEngine} from './PriceFeedEngine.sol'; import {CapsEngine} from './CapsEngine.sol'; @@ -177,14 +176,10 @@ library ListingEngine { memory initReserveInputs = new ConfiguratorInputTypes.InitReserveInput[](ids.length); for (uint256 i = 0; i < ids.length; i++) { - uint8 decimals = IERC20Metadata(ids[i]).decimals(); - require(decimals > 0, 'INVALID_ASSET_DECIMALS'); - initReserveInputs[i] = ConfiguratorInputTypes.InitReserveInput({ aTokenImpl: basics[i].implementations.aToken, stableDebtTokenImpl: basics[i].implementations.sToken, variableDebtTokenImpl: basics[i].implementations.vToken, - underlyingAssetDecimals: decimals, interestRateStrategyAddress: rateStrategy, interestRateData: abi.encode(rates[i]), underlyingAsset: ids[i], diff --git a/tests/core/Pool.FlashLoans.t.sol b/tests/core/Pool.FlashLoans.t.sol index ea9c2e84..60985f9f 100644 --- a/tests/core/Pool.FlashLoans.t.sol +++ b/tests/core/Pool.FlashLoans.t.sol @@ -144,6 +144,30 @@ contract PoolFlashLoansTests is TestnetProcedures { ); } + function test_reverts_flashLoan_same_asset_more_then_once(uint8 length) public { + vm.assume(length > 1); + bytes memory emptyParams; + address[] memory assets = new address[](length); + uint256[] memory amounts = new uint256[](length); + uint256[] memory modes = new uint256[](length); + for (uint256 i = 0; i < length; i++) { + assets[i] = tokenList.weth; + amounts[i] = 1; + } + + vm.expectRevert(bytes(Errors.INCONSISTENT_FLASHLOAN_PARAMS)); + vm.prank(alice); + contracts.poolProxy.flashLoan( + address(mockFlashReceiver), + assets, + amounts, + modes, + alice, + emptyParams, + 0 + ); + } + function test_reverts_flashLoan_simple_invalid_return() public { bytes memory emptyParams; diff --git a/tests/core/Pool.Liquidations.t.sol b/tests/core/Pool.Liquidations.t.sol index 12c332da..65391f74 100644 --- a/tests/core/Pool.Liquidations.t.sol +++ b/tests/core/Pool.Liquidations.t.sol @@ -930,6 +930,58 @@ contract PoolLiquidationTests is TestnetProcedures { contracts.poolProxy.liquidationCall(tokenList.usdx, tokenList.wbtc, bob, 100e6, false); } + function test_liquidation_when_grace_period_disabled(uint40 liquidationGracePeriod) public { + vm.assume( + liquidationGracePeriod <= contracts.poolConfiguratorProxy.MAX_GRACE_PERIOD() && + liquidationGracePeriod != 0 + ); + address[] memory assetsInGrace = new address[](1); + assetsInGrace[0] = tokenList.usdx; + + _setLiquidationGracePeriod(assetsInGrace, liquidationGracePeriod); + + vm.startPrank(alice); + contracts.poolProxy.supply(tokenList.wbtc, 1.25e8, alice, 0); + contracts.poolProxy.borrow(tokenList.usdx, 22_500e6, 2, 0, alice); + vm.stopPrank(); + + LiquidationInput memory params = _loadLiquidationInput( + alice, + tokenList.wbtc, // collateral + tokenList.usdx, // debt + 5_000e6, + tokenList.wbtc, + 25_00 + ); + + vm.startPrank(bob); + // check that liquidations are not allowed after grace period activation + vm.expectRevert(bytes(Errors.LIQUIDATION_GRACE_SENTINEL_CHECK_FAILED)); + contracts.poolProxy.liquidationCall( + params.collateralAsset, + params.debtAsset, + params.user, + params.liquidationAmountInput, + params.receiveAToken + ); + vm.stopPrank(); + + vm.startPrank(poolAdmin); + contracts.poolConfiguratorProxy.disableLiquidationGracePeriod(assetsInGrace[0]); + vm.stopPrank(); + + vm.startPrank(bob); + // check that liquidations are allowed after grace period disabled + contracts.poolProxy.liquidationCall( + params.collateralAsset, + params.debtAsset, + params.user, + params.liquidationAmountInput, + params.receiveAToken + ); + vm.stopPrank(); + } + function test_liquidation_with_liquidation_grace_period_collateral_active( uint40 liquidationGracePeriod ) public { diff --git a/tests/core/PoolConfigurator.ACLModifiers.t.sol b/tests/core/PoolConfigurator.ACLModifiers.t.sol index aad612d2..4a20bb75 100644 --- a/tests/core/PoolConfigurator.ACLModifiers.t.sol +++ b/tests/core/PoolConfigurator.ACLModifiers.t.sol @@ -328,6 +328,22 @@ contract PoolConfiguratorACLModifiersTest is TestnetProcedures { contracts.poolConfiguratorProxy.setReservePause(asset, paused); } + function test_reverts_disableLiquidationGracePeriod_on_unauth( + address caller, + address asset + ) public { + vm.assume( + !contracts.aclManager.isPoolAdmin(caller) && + !contracts.aclManager.isEmergencyAdmin(caller) && + caller != address(contracts.poolAddressesProvider) + ); + + vm.prank(caller); + + vm.expectRevert(bytes(Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN)); + contracts.poolConfiguratorProxy.disableLiquidationGracePeriod(asset); + } + function test_reverts_setPoolPause_unauth( address caller, bool paused, diff --git a/tests/core/PoolConfigurator.eMode.sol b/tests/core/PoolConfigurator.eMode.sol index 25351978..6f19d5c0 100644 --- a/tests/core/PoolConfigurator.eMode.sol +++ b/tests/core/PoolConfigurator.eMode.sol @@ -239,6 +239,39 @@ contract PoolConfiguratorEModeConfigTests is TestnetProcedures { vm.stopPrank(); } + function test_reverts_configureEmodeCategory_input_ltv_lt_reserve_emode_pendingLtv() public { + EModeCategoryInput memory ct = _genCategoryOne(); + vm.startPrank(poolAdmin); + + contracts.poolConfiguratorProxy.setEModeCategory( + ct.id, + ct.ltv, + ct.lt, + ct.lb, + ct.oracle, + ct.label + ); + contracts.poolConfiguratorProxy.setAssetEModeCategory(tokenList.usdx, ct.id); + + (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + + // freeze asset + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + vm.expectRevert(bytes(Errors.INVALID_EMODE_CATEGORY_PARAMS)); + contracts.poolConfiguratorProxy.setEModeCategory( + ct.id, + uint16(ltv) - 1, + ct.lt, + ct.lb, + ct.oracle, + ct.label + ); + vm.stopPrank(); + } + function test_reverts_configureEmodeCategory_input_lt_lt_reserve_emode_lt() public { EModeCategoryInput memory ct = _genCategoryOne(); vm.startPrank(poolAdmin); diff --git a/tests/core/PoolConfigurator.initReserves.t.sol b/tests/core/PoolConfigurator.initReserves.t.sol index a6159b64..6b859094 100644 --- a/tests/core/PoolConfigurator.initReserves.t.sol +++ b/tests/core/PoolConfigurator.initReserves.t.sol @@ -51,7 +51,6 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { report.aToken, report.stableDebtToken, report.variableDebtToken, - newToken.decimals(), isVirtualAccActive, t.rateStrategy, address(newToken), @@ -167,7 +166,10 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { assertEq(AToken(aTokenProxy).name(), reserveInput.aTokenName); assertEq(AToken(aTokenProxy).symbol(), reserveInput.aTokenSymbol); - assertEq(AToken(aTokenProxy).decimals(), reserveInput.underlyingAssetDecimals); + assertEq( + AToken(aTokenProxy).decimals(), + TestnetERC20(reserveInput.underlyingAsset).decimals() + ); assertEq(AToken(aTokenProxy).RESERVE_TREASURY_ADDRESS(), reserveInput.treasury); assertEq(AToken(aTokenProxy).UNDERLYING_ASSET_ADDRESS(), reserveInput.underlyingAsset); assertEq( @@ -177,7 +179,10 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { assertEq(AToken(stableDebtProxy).name(), reserveInput.stableDebtTokenName); assertEq(AToken(stableDebtProxy).symbol(), reserveInput.stableDebtTokenSymbol); - assertEq(AToken(stableDebtProxy).decimals(), reserveInput.underlyingAssetDecimals); + assertEq( + AToken(stableDebtProxy).decimals(), + TestnetERC20(reserveInput.underlyingAsset).decimals() + ); assertEq(AToken(stableDebtProxy).UNDERLYING_ASSET_ADDRESS(), reserveInput.underlyingAsset); assertEq( address(AToken(stableDebtProxy).getIncentivesController()), @@ -186,7 +191,10 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { assertEq(AToken(variableDebtProxy).name(), reserveInput.variableDebtTokenName); assertEq(AToken(variableDebtProxy).symbol(), reserveInput.variableDebtTokenSymbol); - assertEq(AToken(variableDebtProxy).decimals(), reserveInput.underlyingAssetDecimals); + assertEq( + AToken(variableDebtProxy).decimals(), + TestnetERC20(reserveInput.underlyingAsset).decimals() + ); assertEq( AToken(variableDebtProxy).UNDERLYING_ASSET_ADDRESS(), reserveInput.underlyingAsset @@ -205,7 +213,7 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { assertEq(c.isActive, true); assertEq(c.isFrozen, false); assertEq(c.isPaused, false); - assertEq(c.decimals, reserveInput.underlyingAssetDecimals); + assertEq(c.decimals, TestnetERC20(reserveInput.underlyingAsset).decimals()); assertEq(c.ltv, 0); assertEq(c.liquidationThreshold, 0); @@ -274,7 +282,6 @@ contract PoolConfiguratorInitReservesTest is TestnetProcedures { report.aToken, report.stableDebtToken, report.variableDebtToken, - newToken.decimals(), true, t.rateStrategy, address(newToken), diff --git a/tests/core/PoolConfigurator.pendingLTV.t.sol b/tests/core/PoolConfigurator.pendingLTV.t.sol index 5c3582cf..b76eb958 100644 --- a/tests/core/PoolConfigurator.pendingLTV.t.sol +++ b/tests/core/PoolConfigurator.pendingLTV.t.sol @@ -9,15 +9,27 @@ import {DataTypes} from 'aave-v3-core/contracts/protocol/libraries/types/DataTyp import {TestnetProcedures} from '../utils/TestnetProcedures.sol'; contract PoolConfiguratorPendingLtvTests is TestnetProcedures { + event PendingLtvChanged(address indexed asset, uint256 ltv); + + event CollateralConfigurationChanged( + address indexed asset, + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus + ); + function setUp() public { initTestEnvironment(); } function test_freezeReserve_ltvSetTo0() public { // check current ltv - (, uint256 ltv, , , , , , , , bool isFrozen) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + ( + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + bool isFrozen + ) = _getReserveParams(); assertTrue(ltv > 0); assertEq(isFrozen, false); @@ -27,66 +39,58 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); // check ltv = 0 - (, uint256 updatedltv, , , , , , , , bool updatedIsFrozen) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + (uint256 updatedltv, , , bool updatedIsFrozen) = _getReserveParams(); assertEq(updatedltv, 0); assertEq(updatedIsFrozen, true); // check pending ltv is set - (uint256 pendingLtv, bool isPendingLtvSet) = contracts.poolConfiguratorProxy.getPendingLtv( - tokenList.usdx - ); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(pendingLtv, ltv); - assertEq(isPendingLtvSet, true); } function test_unfreezeReserve_pendingSetToLtv() public { // check ltv - (, uint256 originalLtv, , , , , , , , ) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + ( + uint256 originalLtv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + + ) = _getReserveParams(); // freeze reserve vm.startPrank(poolAdmin); contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); // check ltv - (, uint256 ltv, , , , , , , , bool isFrozen) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + (uint256 ltv, , , bool isFrozen) = _getReserveParams(); assertEq(ltv, 0); assertEq(isFrozen, true); // check pending ltv - (uint256 pendingLtv, ) = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); // unfreeze reserve contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); // check ltv is set back - (, uint256 updatedLtv, , , , , , , , bool updatedIsFrozen) = contracts - .protocolDataProvider - .getReserveConfigurationData(tokenList.usdx); + (uint256 updatedLtv, , , bool updatedIsFrozen) = _getReserveParams(); assertEq(updatedLtv, originalLtv); assertEq(updatedLtv, pendingLtv); assertEq(updatedIsFrozen, false); // check pending ltv is set to zero - (uint256 updatedPendingLtv, bool updatedIsPendingLtvSet) = contracts - .poolConfiguratorProxy - .getPendingLtv(tokenList.usdx); + uint256 updatedPendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(updatedPendingLtv, 0); - assertEq(updatedIsPendingLtvSet, false); vm.stopPrank(); } - function test_setLtvToFrozen_ltvSetToPending(uint256 originalLtv, uint256 ltvToSet) public { + // freeze reserve, set ltv, unfreeze reserve + function test_setLtv_ltvSetPendingLtvSet(uint256 originalLtv, uint256 ltvToSet) public { uint256 liquidationThreshold = 86_00; uint256 liquidationBonus = 10_500; @@ -97,8 +101,6 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { vm.assume(ltvToSet < liquidationThreshold); vm.assume(ltvToSet != originalLtv); - console.logUint(ltvToSet); - // set original ltv vm.startPrank(poolAdmin); contracts.poolConfiguratorProxy.configureReserveAsCollateral( @@ -112,9 +114,16 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); // check pending ltv - (uint256 pendingLtv, ) = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + uint256 pendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(pendingLtv, originalLtv); + // expect events to be emitted + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit PendingLtvChanged(tokenList.usdx, ltvToSet); + + vm.expectEmit(address(contracts.poolConfiguratorProxy)); + emit CollateralConfigurationChanged(tokenList.usdx, 0, liquidationThreshold, liquidationBonus); + // setLtv contracts.poolConfiguratorProxy.configureReserveAsCollateral( tokenList.usdx, @@ -124,48 +133,41 @@ contract PoolConfiguratorPendingLtvTests is TestnetProcedures { ); // check ltv is still 0 - (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( - tokenList.usdx - ); - + (uint256 ltv, , , ) = _getReserveParams(); assertEq(ltv, 0); // check pending ltv - (uint256 updatedPendingLtv, bool updatedIsPendingLtvSet) = contracts - .poolConfiguratorProxy - .getPendingLtv(tokenList.usdx); - + uint256 updatedPendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); assertEq(updatedPendingLtv, ltvToSet); - assertEq(updatedIsPendingLtvSet, true); - vm.stopPrank(); - } - - function test_setLtv_ltvSet(uint256 ltvToSet) public { - uint256 liquidationThreshold = 86_00; - uint256 liquidationBonus = 10_500; - - vm.assume(ltvToSet > 0); - vm.assume(ltvToSet < liquidationThreshold); - - // freeze reserve - vm.startPrank(poolAdmin); - - // setLtv - contracts.poolConfiguratorProxy.configureReserveAsCollateral( - tokenList.usdx, - ltvToSet, - liquidationThreshold, - liquidationBonus - ); + // unfreeze reserve + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); - // check ltv is updated - (, uint256 ltv, , , , , , , , ) = contracts.protocolDataProvider.getReserveConfigurationData( - tokenList.usdx - ); + // check ltv is set + (uint256 updatedLtv, , , ) = _getReserveParams(); + assertEq(updatedLtv, ltvToSet); - assertEq(ltv, ltvToSet); + // check pending ltv is set to zero + uint256 finalPendingLtv = contracts.poolConfiguratorProxy.getPendingLtv(tokenList.usdx); + assertEq(finalPendingLtv, 0); vm.stopPrank(); } + + function _getReserveParams() internal returns (uint256, uint256, uint256, bool) { + ( + , + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + , + , + , + , + , + bool isFrozen + ) = contracts.protocolDataProvider.getReserveConfigurationData(tokenList.usdx); + + return (ltv, liquidationThreshold, liquidationBonus, isFrozen); + } } diff --git a/tests/core/PoolConfigurator.reserveRiskConfig.t.sol b/tests/core/PoolConfigurator.reserveRiskConfig.t.sol index c6f5fc1b..002a6cea 100644 --- a/tests/core/PoolConfigurator.reserveRiskConfig.t.sol +++ b/tests/core/PoolConfigurator.reserveRiskConfig.t.sol @@ -377,15 +377,48 @@ contract PoolConfiguratorReserveRiskConfigs is TestnetProcedures { } function test_setReserveFreeze_false() public { + vm.startPrank(poolAdmin); + // freeze reserve + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + (, , , , , , , , , bool isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + assertEq(isFrozen, true); + + // unfreeze reserve vm.expectEmit(address(contracts.poolConfiguratorProxy)); emit ReserveFrozen(tokenList.usdx, false); + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); + (, , , , , , , , , isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( + tokenList.usdx + ); + assertEq(isFrozen, false); + + vm.stopPrank(); + } + + function test_setUnfrozenReserveFreeze_false_revert() public { + vm.expectRevert(bytes(Errors.INVALID_FREEZE_STATE)); + vm.prank(poolAdmin); contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, false); + } + + function test_setFrozenReserveFreeze_true_revert() public { + vm.startPrank(poolAdmin); + + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); (, , , , , , , , , bool isFrozen) = contracts.protocolDataProvider.getReserveConfigurationData( tokenList.usdx ); - assertEq(isFrozen, false); + assertEq(isFrozen, true); + + vm.expectRevert(bytes(Errors.INVALID_FREEZE_STATE)); + contracts.poolConfiguratorProxy.setReserveFreeze(tokenList.usdx, true); + + vm.stopPrank(); } function test_setBorrowableInIsolation_true() public { @@ -766,6 +799,22 @@ contract PoolConfiguratorReserveRiskConfigs is TestnetProcedures { vm.stopPrank(); } + function test_disableLiquidationGracePeriod() public { + uint40 gracePeriod = 2 hours; + address asset = tokenList.usdx; + uint40 until = uint40(block.timestamp) + 2 hours; + + vm.startPrank(poolAdmin); + + contracts.poolConfiguratorProxy.setReservePause(asset, false, gracePeriod); + assertEq(contracts.poolProxy.getLiquidationGracePeriod(asset), until); + + contracts.poolConfiguratorProxy.disableLiquidationGracePeriod(asset); + assertEq(contracts.poolProxy.getLiquidationGracePeriod(asset), block.timestamp - 1); + + vm.stopPrank(); + } + function test_setLiquidationGracePeriodPool(uint40 gracePeriod) public { vm.assume(gracePeriod <= contracts.poolConfiguratorProxy.MAX_GRACE_PERIOD()); diff --git a/tests/utils/BatchTestProcedures.sol b/tests/utils/BatchTestProcedures.sol index 00b9efa3..b91f0e96 100644 --- a/tests/utils/BatchTestProcedures.sol +++ b/tests/utils/BatchTestProcedures.sol @@ -280,7 +280,6 @@ contract BatchTestProcedures is Test, DeployUtils, FfiUtils, DefaultMarketInput r.aToken, r.stableDebtToken, r.variableDebtToken, - listingToken.decimals(), true, t.rateStrategy, address(listingToken), diff --git a/tests/utils/TestnetProcedures.sol b/tests/utils/TestnetProcedures.sol index 5d6f39c2..ab6ee5e3 100644 --- a/tests/utils/TestnetProcedures.sol +++ b/tests/utils/TestnetProcedures.sol @@ -312,7 +312,6 @@ contract TestnetProcedures is Test, DeployUtils, FfiUtils, DefaultMarketInput { r.aToken, r.stableDebtToken, r.variableDebtToken, - listingToken.decimals(), true, t.rateStrategy, address(listingToken),