Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AaveV3LikeProtectionSuite

Git Source

Inherits: LendingProtectionSuiteBase

Title: AaveV3LikeProtectionSuite

Author: Phylax Systems

Shared ILendingProtectionSuite implementation for Aave v3-compatible lending forks.

This adapter matches the interface and accounting model used by forks such as the local Aave v3 Horizon deployment and SparkLend v1. The suite protects the lending surface that can directly worsen an account:

  • new borrows and collateral withdrawals must leave the account above Aave’s health-factor liquidation threshold;
  • disabling collateral, transferring aTokens, or changing e-mode must not turn a healthy account into a liquidatable one;
  • withdrawals and liquidations are bounded by the user’s pre-call claim, debt, and collateral so a locally successful call cannot consume more value than the pre-state allowed. These checks intentionally use the pool’s own getUserAccountData output as the risk source of truth. A failure means the transaction passed protocol execution but left external lending accounting in a state users would experience as bad debt, over-withdrawal, or over-liquidation.

Constants

HEALTH_FACTOR_METRIC

bytes32 internal constant HEALTH_FACTOR_METRIC = 0x4845414c54485f464143544f5200000000000000000000000000000000000000

WITHDRAW_CLAIM_CHECK

bytes32 internal constant WITHDRAW_CLAIM_CHECK = "WITHDRAW_CLAIM"

LIQUIDATION_DEBT_CHECK

bytes32 internal constant LIQUIDATION_DEBT_CHECK = "LIQUIDATION_DEBT"

LIQUIDATION_COLLATERAL_CHECK

bytes32 internal constant LIQUIDATION_COLLATERAL_CHECK = "LIQUIDATION_COLLATERAL"

HEALTH_FACTOR_THRESHOLD

uint256 internal constant HEALTH_FACTOR_THRESHOLD = 1e18

HEALTH_FACTOR_THRESHOLD_INT

int256 internal constant HEALTH_FACTOR_THRESHOLD_INT = 1e18

POOL

address internal immutable POOL

ADDRESSES_PROVIDER

address internal immutable ADDRESSES_PROVIDER

Functions

constructor

Creates an Aave v3-like suite bound to a specific pool.

constructor(address pool_, address addressesProvider_) ;

Parameters

NameTypeDescription
pool_addressPool address whose accounting and selectors this suite targets.
addressesProvider_addressThe pool’s ADDRESSES_PROVIDER. Passed in explicitly because assertions are deployed against an empty state where calling the pool would fail.

getMonitoredSelectors

Returns the Aave v3-like pool selectors relevant to the shared lending invariants.

The list is intentionally limited to operations that can change debt, effective collateral, liquidation settlement, or the user’s risk category. Supply, repay, and other risk-improving paths are left out to keep the example focused and low noise.

function getMonitoredSelectors() external pure override returns (bytes4[] memory selectors);

decodeOperation

Decodes an Aave v3-like pool call into the shared lending operation model.

The generic lending base only needs a normalized operation: whose risk changed, which asset moved, and whether the call increased debt or reduced effective collateral. Keeping the Aave ABI details here lets the base assertion express protocol-agnostic safety rules.

function decodeOperation(TriggeredCall calldata triggered)
    external
    pure
    override
    returns (OperationContext memory operation);

_baseOperation

function _baseOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeBorrowOperation

function _decodeBorrowOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeWithdrawOperation

function _decodeWithdrawOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeLiquidationOperation

function _decodeLiquidationOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeCollateralToggleOperation

function _decodeCollateralToggleOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeFinalizeTransferOperation

function _decodeFinalizeTransferOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

_decodeSetUserEModeOperation

function _decodeSetUserEModeOperation(TriggeredCall calldata triggered)
    internal
    pure
    returns (OperationContext memory operation);

shouldCheckPostOperationSolvency

Filters decoded Aave v3-like operations down to the ones that must preserve solvency.

A post-operation health-factor check is only meaningful for calls that can worsen the account. This avoids tripping on harmless operations and makes each failure actionable: a previously healthy account ended the triggering call below the liquidation threshold.

function shouldCheckPostOperationSolvency(OperationContext calldata operation)
    external
    pure
    override
    returns (bool shouldCheck);

getConsumptionChecks

Returns the bounded-consumption checks implied by the decoded Aave v3-like operation.

Solvency alone does not catch value extraction bugs. These checks also assert that a withdraw cannot return more assets than the caller’s pre-call aToken claim, and that a liquidation cannot repay/seize more debt or collateral than existed immediately before it.

function getConsumptionChecks(
    TriggeredCall calldata triggered,
    OperationContext calldata operation,
    PhEvm.ForkId calldata beforeFork,
    PhEvm.ForkId calldata afterFork
) external view override returns (ConsumptionCheck[] memory checks);

getAccountSnapshot

Reads the post-call snapshot needed by the health-factor solvency invariant.

The lending base calls this at precise pre-call and post-call forks. Using the same pool aggregate view that integrators rely on makes the failure easy to interpret: the protocol’s own account data says the user is no longer solvent.

function getAccountSnapshot(address account, PhEvm.ForkId calldata fork)
    external
    view
    virtual
    override
    returns (AccountSnapshot memory snapshot);

getAccountState

Reads aggregate account metrics from Pool.getUserAccountData(...).

function getAccountState(address account, PhEvm.ForkId calldata fork)
    external
    view
    override
    returns (AccountState memory state);

getAccountBalances

Enumerates reserve balances for the account.

function getAccountBalances(address account, PhEvm.ForkId calldata fork)
    external
    view
    override
    returns (AccountBalance[] memory balances);

evaluateSolvency

Evaluates solvency from aggregate account state.

function evaluateSolvency(
    AccountState calldata state,
    AccountBalance[] calldata balances,
    PhEvm.ForkId calldata fork
) external pure override returns (SolvencyState memory solvency);

_getAccountState

Internal helper that reads and normalizes aggregate account data.

function _getAccountState(address account, PhEvm.ForkId memory fork)
    internal
    view
    returns (AccountState memory state);

_getAccountBalances

Internal helper that expands the account into reserve-level balances and values.

function _getAccountBalances(address account, PhEvm.ForkId memory fork)
    internal
    view
    returns (AccountBalance[] memory balances);

_getWithdrawClaimCheck

Builds the withdraw bounded-consumption check from call output and pre-state.

withdraw returns the actual asset amount sent. Comparing that return value with the caller’s pre-call aToken balance catches accounting bugs where a successful withdrawal consumes more collateral claim than the account had before the call.

function _getWithdrawClaimCheck(
    TriggeredCall calldata triggered,
    OperationContext calldata operation,
    PhEvm.ForkId calldata beforeFork
) internal view returns (ConsumptionCheck memory check);

_getLiquidationDebtCheck

Builds the liquidation debt-consumption check from actual debt-asset transfers.

The liquidation input can request type(uint256).max, so the assertion observes the actual debt-asset transfer instead of trusting calldata. A failure means the liquidator repaid more debt than the borrower owed in the pre-call snapshot.

function _getLiquidationDebtCheck(
    OperationContext calldata operation,
    PhEvm.ForkId calldata beforeFork,
    PhEvm.ForkId calldata afterFork
) internal view returns (ConsumptionCheck memory check);

_getLiquidationCollateralCheck

Builds the liquidation collateral-consumption check from actual collateral transfers.

The seized side differs depending on receiveAToken: liquidators can receive aTokens or underlying collateral. The check observes the token that actually moved and bounds it by the borrower’s pre-call collateral claim, protecting users from over-seizure.

function _getLiquidationCollateralCheck(
    OperationContext calldata operation,
    PhEvm.ForkId calldata beforeFork,
    PhEvm.ForkId calldata afterFork
) internal view returns (ConsumptionCheck memory check);

_transferredValueDuringCall

Reads token movement for only the triggered call window.

ERC20 transfer precompiles return cumulative logs up to the requested fork, so subtract the pre-call amount from the post-call amount to avoid counting earlier same-tx transfers.

function _transferredValueDuringCall(
    address token,
    address from,
    address to,
    PhEvm.ForkId calldata beforeFork,
    PhEvm.ForkId calldata afterFork
) internal view returns (uint256 value);

_evaluateHealthFactor

Converts aggregate metrics into the common solvency representation.

function _evaluateHealthFactor(AccountState memory state) internal pure returns (SolvencyState memory solvency);

_buildAccountBalance

Builds a single reserve-level balance entry for the account.

function _buildAccountBalance(
    address asset,
    address account,
    uint256 userConfigData,
    address oracle,
    PhEvm.ForkId memory fork
) internal view returns (bool include, AccountBalance memory balance);

_getReserveData

Reads reserve metadata for a single asset at the requested snapshot fork.

function _getReserveData(address asset, PhEvm.ForkId memory fork)
    internal
    view
    returns (AaveV3LikeTypes.ReserveData memory reserveData);

_getUserReserveDebt

Reads the user’s total debt for one reserve from the debt-token balances.

function _getUserReserveDebt(address asset, address account, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256 debtBalance);

_readOptionalBalance

Reads a token balance when the token address may be unset.

function _readOptionalBalance(address token, address account, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256 balance);

_valueInBase

Converts an asset-denominated balance into the pool base currency.

function _valueInBase(address oracle, address asset, uint256 balance, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256);

_isUsingAsCollateral

Returns whether the reserve is enabled as collateral in the user config bitset.

function _isUsingAsCollateral(uint256 userConfigData, uint256 reserveId) internal pure returns (bool);

_toInt256

Safely casts a uint256 metric to int256 for SolvencyState.

function _toInt256(uint256 value) internal pure returns (int256);

Structs

AaveAccountMetrics

Extra aggregate Aave v3-like metrics kept in AccountState.metadata.

struct AaveAccountMetrics {
    uint256 availableBorrowsBase;
    uint256 currentLiquidationThreshold;
    uint256 ltv;
    uint256 healthFactor;
}