-
Notifications
You must be signed in to change notification settings - Fork 567
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
Feat/isolation mode #120
Changes from all commits
99aefbb
289f916
6fd1a5f
d4ddeb0
14358a7
20853a3
1e72d0b
68bf644
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to subtract one from this value I think. |
||
|
||
/** | ||
* @notice Sets the Loan to Value of the reserve | ||
|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
**/ | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
|
@@ -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; | ||
|
||
|
@@ -69,6 +71,12 @@ library BorrowLogic { | |
|
||
reserve.updateState(reserveCache); | ||
|
||
( | ||
bool isolationModeActive, | ||
address isolationModeCollateralAddress, | ||
uint256 isolationModeDebtCeiling | ||
) = userConfig.getIsolationModeState(reserves, reservesList); | ||
|
||
ValidationLogic.validateBorrow( | ||
reserves, | ||
reservesList, | ||
|
@@ -84,7 +92,10 @@ library BorrowLogic { | |
params.reservesCount, | ||
params.oracle, | ||
params.userEModeCategory, | ||
params.priceOracleSentinel | ||
params.priceOracleSentinel, | ||
isolationModeActive, | ||
isolationModeCollateralAddress, | ||
isolationModeDebtCeiling | ||
) | ||
); | ||
|
||
|
@@ -114,6 +125,12 @@ library BorrowLogic { | |
userConfig.setBorrowing(reserve.id, true); | ||
} | ||
|
||
if (isolationModeActive) { | ||
reserves[isolationModeCollateralAddress].isolationModeTotalDebt += Helpers.castUint128( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
params.amount / 10**reserveCache.reserveConfiguration.getDecimals() | ||
); | ||
} | ||
|
||
reserve.updateInterestRates( | ||
reserveCache, | ||
params.asset, | ||
|
@@ -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 | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
@@ -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); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 <= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
params.isolationModeDebtCeiling, | ||
Errors.VL_DEBT_CEILING_CROSSED | ||
); | ||
} | ||
|
||
if (params.userEModeCategory != 0) { | ||
require( | ||
params.reserveCache.reserveConfiguration.getEModeCategory() == params.userEModeCategory, | ||
|
There was a problem hiding this comment.
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