Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/isolation mode #120

Merged
merged 8 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc for asset param is missing

*/
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;
Copy link
Contributor

@stevenvaleri stevenvaleri Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to subtract one from this value I think.
4294967295


/**
* @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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has not been

**/
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)) {
Copy link
Contributor

@miguelmtzinf miguelmtzinf Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we can remove this case

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its better to store the amount of coins, for better precision and to be aligned with reserve.unbacked

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paybackAmount with incorrect decimals

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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the first supply of the isolation mode asset, I think it needs to be set as usingAsCollateral and emit an event (could be the same, or specific to IsolationModeCollateral)

}

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 <=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amount with incorrect decimals

params.isolationModeDebtCeiling,
Errors.VL_DEBT_CEILING_CROSSED
);
}

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