Skip to content

Commit

Permalink
Merge pull request #120 from aave/feat/isolation-mode
Browse files Browse the repository at this point in the history
Feat/isolation mode
  • Loading branch information
The-3D authored Sep 29, 2021
2 parents 927b973 + 68bf644 commit 50b0fdf
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 24 deletions.
13 changes: 13 additions & 0 deletions contracts/interfaces/IPoolConfigurator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ interface IPoolConfigurator {
address indexed implementation
);

/**
* @notice Emitted when the debt ceiling of an asset is set
* @param asset The address of the underlying asset of the reserve
* @param ceiling The new debt ceiling
**/
event DebtCeilingChanged(address indexed asset, uint256 ceiling);

/**
* @notice Emitted when a new risk admin is registered
* @param admin The newly registered admin
Expand Down Expand Up @@ -439,4 +446,10 @@ interface IPoolConfigurator {
* @param flashloanPremiumToProtocol The part of the premium sent to protocol
*/
function updateFlashloanPremiumToProtocol(uint256 flashloanPremiumToProtocol) external;

/**
* @notice Sets the debt ceiling for an asset
* @param ceiling The new debt ceiling
*/
function setDebtCeiling(address asset, uint256 ceiling) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ library ReserveConfiguration {
uint256 constant LIQUIDATION_PROTOCOL_FEE_MASK = 0xFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore
uint256 constant EMODE_CATEGORY_MASK = 0xFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore
uint256 constant UNBACKED_MINT_CAP_MASK = 0xFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore
uint256 constant DEBT_CEILING_MASK = 0xFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore

/// @dev For the LTV, the start bit is 0 (up to 15), hence no bitshifting is needed
uint256 constant LIQUIDATION_THRESHOLD_START_BIT_POSITION = 16;
Expand All @@ -42,6 +43,7 @@ library ReserveConfiguration {
uint256 constant LIQUIDATION_PROTOCOL_FEE_START_BIT_POSITION = 152;
uint256 constant EMODE_CATEGORY_START_BIT_POSITION = 168;
uint256 constant UNBACKED_MINT_CAP_START_BIT_POSITION = 176;
uint256 constant DEBT_CEILING_START_BIT_POSITION = 212;

uint256 constant MAX_VALID_LTV = 65535;
uint256 constant MAX_VALID_LIQUIDATION_THRESHOLD = 65535;
Expand All @@ -53,6 +55,7 @@ library ReserveConfiguration {
uint256 constant MAX_VALID_LIQUIDATION_PROTOCOL_FEE = 10000;
uint256 constant MAX_VALID_EMODE_CATEGORY = 255;
uint256 constant MAX_VALID_UNBACKED_MINT_CAP = 68719476735;
uint256 constant MAX_VALID_DEBT_CEILING = 4294967296;

/**
* @notice Sets the Loan to Value of the reserve
Expand Down Expand Up @@ -356,6 +359,33 @@ library ReserveConfiguration {
return (self.data & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION;
}

/**
* @notice Sets the debt ceiling in isolation mode for the asset
* @param self The reserve configuration
* @param ceiling The maximum debt ceiling for the asset
**/
function setDebtCeiling(DataTypes.ReserveConfigurationMap memory self, uint256 ceiling)
internal
pure
{
require(ceiling <= MAX_VALID_DEBT_CEILING, Errors.RC_INVALID_DEBT_CEILING);

self.data = (self.data & DEBT_CEILING_MASK) | (ceiling << DEBT_CEILING_START_BIT_POSITION);
}

/**
* @notice Gets the debt ceiling for the asset if the asset is in isolation mode
* @param self The reserve configuration
* @return The debt ceiling (0 = isolation mode disabled)
**/
function getDebtCeiling(DataTypes.ReserveConfigurationMap memory self)
internal
pure
returns (uint256)
{
return (self.data & ~DEBT_CEILING_MASK) >> DEBT_CEILING_START_BIT_POSITION;
}

/**
* @notice Sets the liquidation protocol fee of the reserve
* @param self The reserve configuration
Expand Down
82 changes: 80 additions & 2 deletions contracts/protocol/libraries/configuration/UserConfiguration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ pragma solidity 0.8.7;

import {Errors} from '../helpers/Errors.sol';
import {DataTypes} from '../types/DataTypes.sol';
import {ReserveConfiguration} from './ReserveConfiguration.sol';

/**
* @title UserConfiguration library
* @author Aave
* @notice Implements the bitmap logic to handle the user configuration
*/
library UserConfiguration {
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;

uint256 internal constant BORROWING_MASK =
0x5555555555555555555555555555555555555555555555555555555555555555;
uint256 internal constant COLLATERAL_MASK =
0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;

/**
* @notice Sets if the user is borrowing the reserve identified by reserveIndex
Expand Down Expand Up @@ -102,7 +107,35 @@ library UserConfiguration {
}

/**
* @notice Validate a user has been borrowing from any reserve
* @notice Checks if a user has been supplying only one reserve as collateral
* @dev this uses a simple trick - if a number is a power of two (only one bit set) then n & (n - 1) == 0
* @param self The configuration object
* @return True if the user has been supplying as collateral one reserve, false otherwise
**/
function isUsingAsCollateralOne(DataTypes.UserConfigurationMap memory self)
internal
pure
returns (bool)
{
uint256 collateralData = self.data & COLLATERAL_MASK;
return collateralData & (collateralData - 1) == 0;
}

/**
* @notice Checks if a user has been supplying any reserve as collateral
* @param self The configuration object
* @return True if the user has been supplying as collateral any reserve, false otherwise
**/
function isUsingAsCollateralAny(DataTypes.UserConfigurationMap memory self)
internal
pure
returns (bool)
{
return self.data & COLLATERAL_MASK != 0;
}

/**
* @notice Checks if a user has been borrowing from any reserve
* @param self The configuration object
* @return True if the user has been borrowing any reserve, false otherwise
**/
Expand All @@ -111,11 +144,56 @@ library UserConfiguration {
}

/**
* @notice Validate a user has not been using any reserve
* @notice Checks if a user has not been using any reserve for borrowing or supply
* @param self The configuration object
* @return True if the user has been borrowing any reserve, false otherwise
**/
function isEmpty(DataTypes.UserConfigurationMap memory self) internal pure returns (bool) {
return self.data == 0;
}

function getIsolationModeState(
DataTypes.UserConfigurationMap memory self,
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList
)
internal
view
returns (
bool,
address,
uint256
)
{
if (!isUsingAsCollateralAny(self)) {
return (false, address(0), 0);
}
if (isUsingAsCollateralOne(self)) {
uint256 assetId = _getFirstAssetAsCollateralId(self);

address assetAddress = reservesList[assetId];
uint256 ceiling = reservesData[assetAddress].configuration.getDebtCeiling();
if (ceiling > 0) {
return (true, assetAddress, ceiling);
}
}
return (false, address(0), 0);
}

function _getFirstAssetAsCollateralId(DataTypes.UserConfigurationMap memory self)
internal
pure
returns (uint256)
{
unchecked {
uint256 collateralData = self.data & COLLATERAL_MASK;
uint256 firstCollateralPosition = collateralData & ~(collateralData - 1);
uint256 id;

while ((firstCollateralPosition >>= 2) > 0) {
id += 2;
}
return id / 2;
}
}
}
5 changes: 5 additions & 0 deletions contracts/protocol/libraries/helpers/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,9 @@ library Errors {
string public constant RC_INVALID_UNBACKED_MINT_CAP = '103';
string public constant VL_UNBACKED_MINT_CAP_EXCEEDED = '104';
string public constant VL_PRICE_ORACLE_SENTINEL_CHECK_FAILED = '105';
string public constant RC_INVALID_DEBT_CEILING = '106';
string public constant PC_INVALID_DEBT_CEILING_ASSET_ALREADY_SUPPLIED = '107';
string public constant VL_INVALID_ISOLATION_MODE_BORROW_CATEGORY = '108';
string public constant VL_DEBT_CEILING_CROSSED = '109';
string public constant SL_USER_IN_ISOLATION_MODE = '110';
}
37 changes: 36 additions & 1 deletion contracts/protocol/libraries/logic/BorrowLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IVariableDebtToken} from '../../../interfaces/IVariableDebtToken.sol';
import {IAToken} from '../../../interfaces/IAToken.sol';
import {IFlashLoanReceiver} from '../../../flashloan/interfaces/IFlashLoanReceiver.sol';
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {Helpers} from '../helpers/Helpers.sol';
import {Errors} from '../helpers/Errors.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
Expand All @@ -26,6 +27,7 @@ library BorrowLogic {
using ReserveLogic for DataTypes.ReserveData;
using SafeERC20 for IERC20;
using UserConfiguration for DataTypes.UserConfigurationMap;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
using WadRayMath for uint256;
using PercentageMath for uint256;

Expand Down Expand Up @@ -69,6 +71,12 @@ library BorrowLogic {

reserve.updateState(reserveCache);

(
bool isolationModeActive,
address isolationModeCollateralAddress,
uint256 isolationModeDebtCeiling
) = userConfig.getIsolationModeState(reserves, reservesList);

ValidationLogic.validateBorrow(
reserves,
reservesList,
Expand All @@ -84,7 +92,10 @@ library BorrowLogic {
params.reservesCount,
params.oracle,
params.userEModeCategory,
params.priceOracleSentinel
params.priceOracleSentinel,
isolationModeActive,
isolationModeCollateralAddress,
isolationModeDebtCeiling
)
);

Expand Down Expand Up @@ -114,6 +125,12 @@ library BorrowLogic {
userConfig.setBorrowing(reserve.id, true);
}

if (isolationModeActive) {
reserves[isolationModeCollateralAddress].isolationModeTotalDebt += Helpers.castUint128(
params.amount / 10**reserveCache.reserveConfiguration.getDecimals()
);
}

reserve.updateInterestRates(
reserveCache,
params.asset,
Expand All @@ -139,6 +156,8 @@ library BorrowLogic {
}

function executeRepay(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.ReserveData storage reserve,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteRepayParams memory params
Expand Down Expand Up @@ -185,6 +204,22 @@ library BorrowLogic {
userConfig.setBorrowing(reserve.id, false);
}

(bool isolationModeActive, address isolationModeCollateralAddress, ) = userConfig
.getIsolationModeState(reserves, reservesList);

if (isolationModeActive) {
uint128 isolationModeTotalDebt = reserves[isolationModeCollateralAddress]
.isolationModeTotalDebt;
// since the debt ceiling does not take into account the interest accrued, it might happen that amount repaid > debt in isolation mode
if (isolationModeTotalDebt < paybackAmount) {
reserves[isolationModeCollateralAddress].isolationModeTotalDebt = 0;
} else {
reserves[isolationModeCollateralAddress].isolationModeTotalDebt =
isolationModeTotalDebt -
Helpers.castUint128(paybackAmount / 10**reserveCache.reserveConfiguration.getDecimals());
}
}

if (params.useATokens) {
IAToken(reserveCache.aTokenAddress).burn(
msg.sender,
Expand Down
22 changes: 16 additions & 6 deletions contracts/protocol/libraries/logic/SupplyLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ library SupplyLogic {

function executeSupply(
mapping(address => DataTypes.ReserveData) storage reserves,
mapping(uint256 => address) storage reservesList,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteSupplyParams memory params
) external {
Expand All @@ -66,8 +67,11 @@ library SupplyLogic {
);

if (isFirstSupply) {
userConfig.setUsingAsCollateral(reserve.id, true);
emit ReserveUsedAsCollateralEnabled(params.asset, params.onBehalfOf);
(bool isolationModeActive, , ) = userConfig.getIsolationModeState(reserves, reservesList);
if (!isolationModeActive) {
userConfig.setUsingAsCollateral(reserve.id, true);
emit ReserveUsedAsCollateralEnabled(params.asset, params.onBehalfOf);
}
}

emit Supply(params.asset, msg.sender, params.onBehalfOf, params.amount, params.referralCode);
Expand Down Expand Up @@ -168,8 +172,11 @@ library SupplyLogic {

if (params.balanceToBefore == 0 && params.amount != 0) {
DataTypes.UserConfigurationMap storage toConfig = usersConfig[params.to];
toConfig.setUsingAsCollateral(reserveId, true);
emit ReserveUsedAsCollateralEnabled(params.asset, params.to);
(bool isolationModeActive, , ) = toConfig.getIsolationModeState(reserves, reservesList);
if (!isolationModeActive) {
toConfig.setUsingAsCollateral(reserveId, true);
emit ReserveUsedAsCollateralEnabled(params.asset, params.to);
}
}
}
}
Expand All @@ -192,11 +199,14 @@ library SupplyLogic {

ValidationLogic.validateSetUseReserveAsCollateral(reserveCache, userBalance);

userConfig.setUsingAsCollateral(reserve.id, useAsCollateral);

if (useAsCollateral) {
(bool isolationModeActive, , ) = userConfig.getIsolationModeState(reserves, reservesList);
require(!isolationModeActive, Errors.SL_USER_IN_ISOLATION_MODE);

userConfig.setUsingAsCollateral(reserve.id, true);
emit ReserveUsedAsCollateralEnabled(asset, msg.sender);
} else {
userConfig.setUsingAsCollateral(reserve.id, false);
ValidationLogic.validateHFAndLtv(
reserves,
reservesList,
Expand Down
22 changes: 22 additions & 0 deletions contracts/protocol/libraries/logic/ValidationLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ library ValidationLogic {
uint256 public constant REBALANCE_UP_USAGE_RATIO_THRESHOLD = 0.95 * 1e27; //usage ratio of 95%
uint256 public constant MINIMUM_HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 0.95 * 1e18;

// for borrowings in isolation mode, we give for granted that the eMode category for stablecoins is the category with id 1.
// this MUST be kept into account when configuring the stablecoins eMode category, otherwise users depositing asset in isolation
// mode will NOT be able to borrow.
uint256 public constant DEFAULT_ISOLATION_MODE_BORROW_CATEGORY = 1;

/**
* @notice Validates a supply action
* @param reserveCache The cached data of the reserve
Expand Down Expand Up @@ -176,6 +181,23 @@ library ValidationLogic {
}
}

if (params.isolationModeActive) {
// check that the asset being borrowed belongs to the stablecoin category AND
// the total exposure is no bigger than the collateral debt ceiling
require(
params.reserveCache.reserveConfiguration.getEModeCategory() ==
DEFAULT_ISOLATION_MODE_BORROW_CATEGORY,
Errors.VL_INVALID_ISOLATION_MODE_BORROW_CATEGORY
);

require(
reservesData[params.isolationModeCollateralAddress].isolationModeTotalDebt +
params.amount <=
params.isolationModeDebtCeiling,
Errors.VL_DEBT_CEILING_CROSSED
);
}

if (params.userEModeCategory != 0) {
require(
params.reserveCache.reserveConfiguration.getEModeCategory() == params.userEModeCategory,
Expand Down
Loading

0 comments on commit 50b0fdf

Please sign in to comment.