Eliminate Asset Deficit Flow¶
End-to-end execution flow for covering an Asset’s deficit by burning aToken positions.
Quick Reference¶
Aspect |
Details |
|---|---|
Entry Point |
|
Key Transformations |
|
State Changes |
|
Events Emitted |
|
Flow Diagram¶
flowchart TD
%% Styling definitions
classDef validation fill:#ffcccc,stroke:#ff0000,stroke-width:2px
classDef transformation fill:#ccffcc,stroke:#00aa00,stroke-width:2px
classDef storage fill:#ccccff,stroke:#0000ff,stroke-width:2px
classDef event fill:#ffffcc,stroke:#aaaa00,stroke-width:2px
classDef error fill:#ff0000,stroke:#000000,color:#fff
classDef bridge fill:#ffccff,stroke:#aa00aa,stroke-width:2px
classDef critical stroke:#ff0000,stroke-width:3px
%% Entry point
Entry["Pool.eliminateReserveDeficit
asset, amount"] --> PermissionCheck["VALIDATION
Caller must be Umbrella"]
class PermissionCheck validation
PermissionCheck --> Execute["LiquidationLogic
executeEliminateDeficit"]
subgraph ValidationPhase ["1. Initial Validations"]
direction TB
CheckAmount["VALIDATION
amount != 0"]
class CheckAmount validation
CheckDeficit["VALIDATION
reserve.deficit != 0"]
class CheckDeficit validation
CheckDebt["VALIDATION
!userConfig.isBorrowingAny()"]
class CheckDebt validation
end
Execute --> ValidationPhase
subgraph StateUpdates ["2. State Updates"]
direction TB
UpdateState["ReserveLogic
updateState
Updates indexes,
accrues interest"] --> CheckActive["VALIDATION
reserve is active"]
class CheckActive validation
end
ValidationPhase --> StateUpdates
subgraph AmountCalculation ["3. Amount Calculations"]
direction TB
CapAmount["TRANSFORMATION
balanceWriteOff =
min(amount,
currentDeficit)"] --> GetScaledBalance["AToken
scaledBalanceOf(user)"]
class CapAmount transformation
CalcScaled["TRANSFORMATION
scaledBalanceWriteOff =
balanceWriteOff.
getATokenBurnScaledAmount
(nextLiquidityIndex)"]
class CalcScaled transformation
CheckUserBalance["VALIDATION
scaledBalanceWriteOff <=
userScaledBalance"]
class CheckUserBalance validation
end
StateUpdates --> AmountCalculation
subgraph CollateralCheck ["4. Collateral Configuration"]
direction TB
IsCollateral{"Is Using as
Collateral?"} -->|Yes| IsFullBalance{"scaledBalanceWriteOff ==
userScaledBalance?"}
IsFullBalance -->|Yes| DisableCollat["UserConfig
setUsingAsCollateral
(reserve.id, false)"]
class DisableCollat storage
DisableCollat --> CollatEvent["EMIT
ReserveUsedAsCollateralDisabled"]
class CollatEvent event
IsFullBalance -->|No| SkipCollat1[Continue]
IsCollateral -->|No| SkipCollat1
end
AmountCalculation --> CollateralCheck
subgraph TokenBurn ["5. Token Burn & Storage Updates"]
direction TB
Burn["AToken
burn
from: user,
receiverOfUnderlying: aTokenAddress"] --> UpdateDeficit["STORAGE UPDATE
reserve.deficit -=
balanceWriteOff.toUint128()"]
class UpdateDeficit storage
Burn --> UpdateScaled["STORAGE UPDATE
_scaledBalance[user] -=
scaledBalanceWriteOff"]
class UpdateScaled storage
end
CollateralCheck --> TokenBurn
subgraph FinalUpdates ["6. Interest Rate Updates"]
direction TB
UpdateRates["ReserveLogic
updateInterestRatesAndVirtualBalance
(liquidityAdded: 0,
liquidityTaken: 0)"] --> FinalEvent["EMIT
DeficitCovered"]
class FinalEvent event
end
TokenBurn --> FinalUpdates
%% Link styles for critical paths
linkStyle 0 stroke:#ff0000,stroke-width:3px
linkStyle 4 stroke:#ff0000,stroke-width:3px
linkStyle 8 stroke:#ff0000,stroke-width:3px
linkStyle 12 stroke:#ff0000,stroke-width:3px
Step-by-Step Execution¶
1. Entry Point¶
File: contracts/protocol/pool/Pool.sol
function eliminateReserveDeficit(
address asset,
uint256 amount
) external override onlyUmbrella returns (uint256) {
return
LiquidationLogic.executeEliminateDeficit(
_reserves,
_usersConfig[_msgSender()],
DataTypes.ExecuteEliminateDeficitParams({
user: _msgSender(),
asset: asset,
amount: amount,
interestRateStrategyAddress: RESERVE_INTEREST_RATE_STRATEGY
})
);
}
Permission Check: The onlyUmbrella modifier ensures only the registered Umbrella contract can call this function.
modifier onlyUmbrella() {
require(ADDRESSES_PROVIDER.getAddress(UMBRELLA) == _msgSender(), Errors.CallerNotUmbrella());
_;
}
2. Execute Eliminate Deficit¶
File: contracts/protocol/libraries/logic/LiquidationLogic.sol
function executeEliminateDeficit(
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteEliminateDeficitParams memory params
) external returns (uint256) {
require(params.amount != 0, Errors.InvalidAmount());
DataTypes.ReserveData storage reserve = reservesData[params.asset];
uint256 currentDeficit = reserve.deficit;
require(currentDeficit != 0, Errors.ReserveNotInDeficit());
require(!userConfig.isBorrowingAny(), Errors.UserCannotHaveDebt());
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
bool isActive = reserveCache.reserveConfiguration.getActive();
require(isActive, Errors.ReserveInactive());
uint256 balanceWriteOff = params.amount;
if (params.amount > currentDeficit) {
balanceWriteOff = currentDeficit;
}
uint256 userScaledBalance = IAToken(reserveCache.aTokenAddress).scaledBalanceOf(params.user);
uint256 scaledBalanceWriteOff = balanceWriteOff.getATokenBurnScaledAmount(
reserveCache.nextLiquidityIndex
);
require(scaledBalanceWriteOff <= userScaledBalance, Errors.NotEnoughAvailableUserBalance());
bool isCollateral = userConfig.isUsingAsCollateral(reserve.id);
if (isCollateral && scaledBalanceWriteOff == userScaledBalance) {
userConfig.setUsingAsCollateral(reserve.id, params.asset, params.user, false);
}
IAToken(reserveCache.aTokenAddress).burn({
from: params.user,
receiverOfUnderlying: reserveCache.aTokenAddress,
amount: balanceWriteOff,
scaledAmount: scaledBalanceWriteOff,
index: reserveCache.nextLiquidityIndex
});
reserve.deficit -= balanceWriteOff.toUint128();
reserve.updateInterestRatesAndVirtualBalance(
reserveCache,
params.asset,
0,
0,
params.interestRateStrategyAddress
);
emit IPool.DeficitCovered(params.asset, params.user, balanceWriteOff);
return balanceWriteOff;
}
3. What is an Asset Deficit?¶
An Asset deficit occurs when the protocol has bad debt that cannot be fully liquidated. This typically happens during liquidation when:
A borrower’s position is undercollateralized (Health Factor < 1)
The liquidator seizes all available collateral
The seized collateral is insufficient to cover the entire debt
The remaining unpaid debt becomes a deficit
How Deficit is Created:
// In liquidation, when borrower has no collateral left but still has debt:
uint256 outstandingDebt = borrowerReserveDebt - actualDebtToLiquidate;
if (hasNoCollateralLeft && outstandingDebt != 0) {
debtReserve.deficit += outstandingDebt.toUint128();
emit IPool.DeficitCreated(borrower, debtAsset, outstandingDebt);
}
4. Amount Transformations¶
File: contracts/protocol/libraries/logic/GenericLogic.sol
function getATokenBurnScaledAmount(
uint256 amount,
uint256 liquidityIndex
) internal pure returns (uint256) {
return amount.rayDivCeil(liquidityIndex);
}
Amount Transformations¶
Input → Storage¶
User Input (WAD decimals)
↓
amount = 1000 * 10^18 // Maximum aTokens to burn
↓
currentDeficit = reserve.deficit
↓
balanceWriteOff = min(amount, currentDeficit)
↓
liquidityIndex = 1.0001 * 10^27 // Current index
↓
scaledBalanceWriteOff = balanceWriteOff.rayDivCeil(liquidityIndex)
= ceil((balanceWriteOff * 10^27) / liquidityIndex)
// Rounded UP to ensure sufficient burn
↓
reserve.deficit -= balanceWriteOff
_scaledBalance[user] -= scaledBalanceWriteOff
Key Points:
User provides aToken amount (WAD decimals - typically 18)
Scaled balance uses RAY precision (27 decimals)
rayDivCeilrounds UP to ensure the user’s position is sufficiently reducedDeficit reduction is capped at current deficit amount
Underlying assets are sent to the aToken contract (not withdrawn by user)
Event Details¶
DeficitCovered Event¶
Emitted when deficit coverage is successfully executed.
event DeficitCovered(
address indexed reserve, // Asset address whose deficit is covered
address indexed caller, // User who provided coverage (burned aTokens)
uint256 amountCovered // Amount of deficit covered (in underlying Token)
);
ReserveUsedAsCollateralDisabled Event¶
Emitted conditionally when the user burns their entire aToken balance for a collateral Asset.
event ReserveUsedAsCollateralDisabled(
address indexed reserve,
address indexed user
);
Error Conditions¶
Error |
Condition |
File |
|---|---|---|
|
|
Pool.sol |
|
|
LiquidationLogic.sol |
|
|
LiquidationLogic.sol |
|
|
LiquidationLogic.sol |
|
|
LiquidationLogic.sol |
|
|
LiquidationLogic.sol |
Key Constraints:
Umbrella-only access: Only the protocol’s Umbrella contract can initiate deficit elimination
No debt allowed: The coverage provider must have no outstanding debt positions
Deficit required: The reserve must actually have a deficit to cover
Sufficient balance: The user must have enough aTokens to burn
Source File Locations¶
contracts/protocol/pool/Pool.sol
contracts/protocol/libraries/logic/LiquidationLogic.sol
contracts/protocol/libraries/logic/ReserveLogic.sol
contracts/protocol/libraries/logic/GenericLogic.sol
contracts/protocol/tokenization/AToken.sol
Permission Requirements¶
This function has restricted access - only callable by the Umbrella contract. The Umbrella is a protocol-level safety mechanism that:
Accumulates protocol revenue/fees over time
Can use accumulated funds to cover deficits via deficit elimination
Acts as a backstop for protocol solvency
The call flow is typically:
Umbrella contract calls
eliminateReserveDeficitA specified amount of Umbrella’s aTokens are burned
The reserve’s deficit is reduced
Protocol solvency is maintained