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

Pool.eliminateReserveDeficit(asset, amount)

Key Transformations

Amount → Scaled Balance

State Changes

reserve.deficit -= balanceWriteOff, _scaledBalance[user] -= scaledBalanceWriteOff

Events Emitted

DeficitCovered, ReserveUsedAsCollateralDisabled (conditional)


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:

  1. A borrower’s position is undercollateralized (Health Factor < 1)

  2. The liquidator seizes all available collateral

  3. The seized collateral is insufficient to cover the entire debt

  4. 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)

  • rayDivCeil rounds UP to ensure the user’s position is sufficiently reduced

  • Deficit 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

CallerNotUmbrella

msg.sender != ADDRESSES_PROVIDER.getAddress(UMBRELLA)

Pool.sol

InvalidAmount

amount == 0

LiquidationLogic.sol

ReserveNotInDeficit

reserve.deficit == 0

LiquidationLogic.sol

UserCannotHaveDebt

userConfig.isBorrowingAny() == true

LiquidationLogic.sol

ReserveInactive

!reserveCache.reserveConfiguration.getActive()

LiquidationLogic.sol

NotEnoughAvailableUserBalance

scaledBalanceWriteOff > userScaledBalance

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:

  1. Accumulates protocol revenue/fees over time

  2. Can use accumulated funds to cover deficits via deficit elimination

  3. Acts as a backstop for protocol solvency

The call flow is typically:

  1. Umbrella contract calls eliminateReserveDeficit

  2. A specified amount of Umbrella’s aTokens are burned

  3. The reserve’s deficit is reduced

  4. Protocol solvency is maintained