AaveV3LikeProtectionSuite
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
getUserAccountDataoutput 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
| Name | Type | Description |
|---|---|---|
pool_ | address | Pool address whose accounting and selectors this suite targets. |
addressesProvider_ | address | The 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;
}