IPerpetualProtectionSuite
Title: IPerpetualProtectionSuite
Author: Phylax Systems
Step-oriented interface for protocol-specific perpetual protection adapters.
Implementations expose the protocol-specific plumbing needed to assert a shared family of perpetual-protocol invariants:
- any non-liquidation user mutation must not create self-bad-debt and must leave the affected account healthy under the protocol’s own mark-to-market risk rule
- taker execution must stay at or worse than the protocol’s externally anchored mark
- open exposure must remain backed by explicit liquidity or liability accounting
- funding settlement must be derived from cumulative-state deltas rather than ad hoc values
- liquidation is the only path that may realize deficit, and it must be gated by pre-state unhealthiness while routing any realized loss into an explicit absorber
- risk-critical transitions must stay anchored to an external oracle or equivalent mark Expected assertion flow:
- Read monitored selectors from
getMonitoredSelectors(). - Resolve the caller-aware
TriggeredCall. - Decode the triggered call with
decodeOperation(...). - Read and enforce any suite-provided execution, liquidity, funding, liquidation, and oracle-anchor checks for the successful call.
- Filter to non-liquidation risk-preserving operations with
shouldCheckPostMutationRisk(...). - Read the post-mutation snapshot with
getPostMutationSnapshot(...). - Require
snapshot.risk.equity >= 0,snapshot.risk.hasBadDebt == false, andsnapshot.risk.isHealthy == true.
Functions
getMonitoredSelectors
Returns the adopter selectors that can participate in the shared perpetual invariants.
function getMonitoredSelectors() external view returns (bytes4[] memory selectors);
Returns
| Name | Type | Description |
|---|---|---|
selectors | bytes4[] | Selectors that should trigger the generic perpetual operation-safety assertion. |
enabledCheckKinds
Returns which optional check families this suite can populate.
The assertion skips disabled families, avoiding cross-contract calls that would only
decode empty arrays. Older suites that do not implement this function are treated as
having every family enabled by PerpetualBaseAssertion.
function enabledCheckKinds() external view returns (EnabledCheckKinds memory enabled);
decodeOperation
Decodes the triggered adopter call into a protocol-normalized operation context.
function decodeOperation(TriggeredCall calldata triggered) external view returns (OperationContext memory operation);
Parameters
| Name | Type | Description |
|---|---|---|
triggered | TriggeredCall | The exact adopter frame that caused the assertion to run. |
Returns
| Name | Type | Description |
|---|---|---|
operation | OperationContext | Protocol-normalized context used by downstream filtering and checks. |
shouldCheckPostMutationRisk
Returns whether the decoded action must preserve post-mutation health.
This should normally be true for non-liquidation user actions that can reduce effective account safety, such as increasing leverage, withdrawing collateral, realizing losses, or removing LP capital against an LP leverage bound.
function shouldCheckPostMutationRisk(OperationContext calldata operation) external view returns (bool shouldCheck);
Parameters
| Name | Type | Description |
|---|---|---|
operation | OperationContext | The decoded operation context returned by decodeOperation(...). |
Returns
| Name | Type | Description |
|---|---|---|
shouldCheck | bool | True when the assertion should read state and enforce the post-state risk rule. |
getExecutionPriceChecks
Returns suite-provided execution-price bounds for the decoded operation.
function getExecutionPriceChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (ExecutionPriceCheck[] memory checks);
getLiquidityCoverageChecks
Returns suite-provided liquidity or liability coverage bounds for the decoded operation.
function getLiquidityCoverageChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (LiquidityCoverageCheck[] memory checks);
getFundingDeltaChecks
Returns suite-provided cumulative-funding settlement bounds for the decoded operation.
function getFundingDeltaChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (FundingDeltaCheck[] memory checks);
getLiquidationChecks
Returns suite-provided liquidation-path bounds for the decoded operation.
function getLiquidationChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (LiquidationCheck[] memory checks);
getOracleAnchorChecks
Returns suite-provided oracle-anchor bounds for the decoded operation.
function getOracleAnchorChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (OracleAnchorCheck[] memory checks);
getAccountingConservationChecks
Returns suite-provided accounting-conservation bounds for the decoded operation.
These checks catch exploit families where a non-liquidation settlement or liquidity-removal path creates unjustified economic gain through LP accounting drift, stale share math, or double-counted PnL.
function getAccountingConservationChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (AccountingConservationCheck[] memory checks);
getPostMutationSnapshot
Reads the operation-aware snapshot used by the post-mutation risk assertion.
Implementations may return the same result as getAccountSnapshot(...) when their
post-state rule depends only on the account and fork. Protocols with action-specific
postconditions may override this to select the appropriate metric for the decoded
operation.
function getPostMutationSnapshot(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata fork
) external view returns (AccountSnapshot memory snapshot);
getAccountSnapshot
Reads the account snapshot used by the post-mutation risk assertion.
function getAccountSnapshot(address account, PhEvm.ForkId calldata fork)
external
view
returns (AccountSnapshot memory snapshot);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The account whose post-operation risk state is being checked. |
fork | PhEvm.ForkId | The post-call snapshot fork that should be queried. |
Returns
| Name | Type | Description |
|---|---|---|
snapshot | AccountSnapshot | Aggregate state, optional per-market positions, and the final risk result. |
getAccountState
Reads protocol-normalized aggregate account state at a given snapshot fork.
function getAccountState(address account, PhEvm.ForkId calldata fork)
external
view
returns (AccountState memory state);
getAccountPositions
Reads protocol-normalized per-market positions for an account at a snapshot fork.
function getAccountPositions(address account, PhEvm.ForkId calldata fork)
external
view
returns (PositionState[] memory positions);
evaluateRisk
Evaluates the protocol’s post-state risk rule from the decoded account snapshot.
function evaluateRisk(AccountState calldata state, PositionState[] calldata positions, PhEvm.ForkId calldata fork)
external
view
returns (RiskState memory risk);
Structs
TriggeredCall
Resolved information about the exact adopter call that triggered the assertion.
struct TriggeredCall {
/// @notice Function selector invoked on the adopter.
bytes4 selector;
/// @notice Immediate caller of the adopter frame.
address caller;
/// @notice Adopter target address that was called.
address target;
/// @notice Raw calldata for the adopter frame.
bytes input;
/// @notice Call identifier used to construct a PreCall snapshot.
uint256 callStart;
/// @notice Call identifier used to construct a PostCall snapshot.
uint256 callEnd;
}
OperationContext
Protocol-decoded context for a monitored perpetual call.
struct OperationContext {
/// @notice The adopter selector that produced this operation context.
bytes4 selector;
/// @notice The high-level action kind.
OperationKind kind;
/// @notice The immediate caller of the adopter frame.
address caller;
/// @notice The primary account whose risk state should be checked after the operation.
address account;
/// @notice The market, product, or pair being mutated, if any.
address market;
/// @notice Primary collateral or settlement asset involved in the action, if any.
address collateralAsset;
/// @notice Secondary account involved in the action, if any.
address counterparty;
/// @notice Position direction when the operation is market-directional.
bool isLong;
/// @notice Absolute exposure delta requested or realized by the action.
uint256 sizeDelta;
/// @notice Signed collateral delta in protocol-defined units, when known.
int256 collateralDelta;
/// @notice User-specified price bound or execution hint, if any.
uint256 limitPrice;
/// @notice True when the action mutates open exposure.
bool mutatesExposure;
/// @notice True when the action can reduce the account's post-state safety margin.
bool reducesAccountSafety;
/// @notice True when the action is a liquidation or other exceptional bad-debt path.
bool isLiquidation;
/// @notice Extension point for protocol-specific metadata.
bytes metadata;
}
AccountState
Protocol-normalized aggregate mark-to-market state for an account.
struct AccountState {
/// @notice The account whose state was read.
address account;
/// @notice Total collateral or margin value in protocol-defined accounting units.
uint256 collateralValue;
/// @notice Total open notional or equivalent exposure measure.
uint256 openNotional;
/// @notice Aggregate unrealized PnL at the protocol's mark price.
int256 unrealizedPnl;
/// @notice Aggregate unsettled or accrued funding at the protocol's mark state.
int256 accruedFunding;
/// @notice Whether the account currently has any open exposure.
bool hasOpenExposure;
/// @notice Extension point for protocol-specific aggregate data.
bytes metadata;
}
PositionState
Protocol-normalized per-market position data for an account.
struct PositionState {
/// @notice The market, product, or pair represented by this entry.
address market;
/// @notice Collateral or settlement asset associated with the position.
address collateralAsset;
/// @notice Direction of the position when applicable.
bool isLong;
/// @notice Position size in protocol-defined units.
uint256 size;
/// @notice Position notional in protocol-defined units.
uint256 openNotional;
/// @notice Position-level collateral or margin allocation.
uint256 collateralValue;
/// @notice Position PnL at the protocol's mark price.
int256 pnl;
/// @notice Position-level accrued funding at the snapshot.
int256 accruedFunding;
/// @notice Position mark price used by the protocol's risk engine.
uint256 markPrice;
/// @notice Position maintenance requirement in protocol-defined units.
uint256 maintenanceRequirement;
/// @notice Extension point for protocol-specific per-position metadata.
bytes metadata;
}
RiskState
Protocol-defined post-operation risk output for an account at a snapshot fork.
struct RiskState {
/// @notice Whether the account is healthy under the protocol's own post-state rules.
bool isHealthy;
/// @notice Whether the account has entered a self-bad-debt state.
bool hasBadDebt;
/// @notice Whether the protocol would consider the account liquidatable at this snapshot.
bool isLiquidatable;
/// @notice Identifier for the protocol's primary risk metric, e.g. "MARGIN_RATIO".
bytes32 metricName;
/// @notice Mark-to-market account equity after collateral, PnL, and funding.
int256 equity;
/// @notice Protocol-normalized post-state safety metric or equivalent.
int256 metricValue;
/// @notice Threshold compared against `metricValue`.
int256 thresholdValue;
/// @notice Comparison rule used to interpret `metricValue` vs `thresholdValue`.
ComparisonKind comparison;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
AccountSnapshot
Full post-operation snapshot for a monitored account.
struct AccountSnapshot {
/// @notice Aggregate state for the monitored account.
AccountState state;
/// @notice Per-market positions. Implementations may return an empty array on the hot path.
PositionState[] positions;
/// @notice Protocol-defined post-operation risk decision.
RiskState risk;
}
EnabledCheckKinds
Check families populated by this suite for the generic perpetual assertion.
struct EnabledCheckKinds {
bool executionPrice;
bool liquidityCoverage;
bool fundingDelta;
bool liquidation;
bool oracleAnchor;
bool accountingConservation;
}
ExecutionPriceCheck
One concrete taker-price bound that must hold for a successful operation.
struct ExecutionPriceCheck {
/// @notice Identifier for the bound being asserted, e.g. "TAKER_WORSE_THAN_MARK".
bytes32 checkName;
/// @notice The account whose trade is being bounded.
address account;
/// @notice The market whose execution is being inspected.
address market;
/// @notice Actual execution price in protocol-defined price units.
uint256 executionPrice;
/// @notice Inclusive lower bound on the allowed execution price.
uint256 minExecutionPrice;
/// @notice Inclusive upper bound on the allowed execution price.
uint256 maxExecutionPrice;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
LiquidityCoverageCheck
One concrete liquidity or liability coverage bound that must hold after a mutation.
struct LiquidityCoverageCheck {
/// @notice Identifier for the bound being asserted, e.g. "RESERVE_COVERAGE".
bytes32 checkName;
/// @notice The market whose liquidity bucket is being checked.
address market;
/// @notice The pool, vault, insurance fund, or liability bucket used as the source of coverage.
address accountingBucket;
/// @notice Required amount implied by the post-state exposure or liability.
uint256 requiredAmount;
/// @notice Available amount in the backing bucket.
uint256 availableAmount;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
FundingDeltaCheck
One concrete funding-settlement check derived from cumulative funding state.
struct FundingDeltaCheck {
/// @notice Identifier for the bound being asserted, e.g. "FUNDING_SETTLEMENT".
bytes32 checkName;
/// @notice The account whose funding is being inspected.
address account;
/// @notice The market whose funding state is being inspected.
address market;
/// @notice Actual funding charged or credited by the successful operation.
int256 actualFunding;
/// @notice Inclusive lower bound implied by cumulative funding deltas.
int256 minExpectedFunding;
/// @notice Inclusive upper bound implied by cumulative funding deltas.
int256 maxExpectedFunding;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
LiquidationCheck
One concrete liquidation-path check for exceptional bad-debt handling.
struct LiquidationCheck {
/// @notice Identifier for the bound being asserted, e.g. "ONLY_UNHEALTHY_LIQUIDATABLE".
bytes32 checkName;
/// @notice The account being liquidated.
address account;
/// @notice The market whose liquidation path is being inspected.
address market;
/// @notice Whether the account was unsafe before the liquidation executed.
bool wasLiquidatableBefore;
/// @notice Positive realized deficit that the liquidation created, if any.
int256 lossCreated;
/// @notice Amount explicitly absorbed by the loss-bearing account or bucket.
uint256 absorbedLoss;
/// @notice Loss-bearing account or bucket, if any.
address absorber;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
AccountingConservationCheck
One concrete accounting-conservation bound for a non-liquidation settlement path.
Catches exploit families where an operation creates unjustified economic gain through stale LP share math, double-counted PnL, or accounting drift — scenarios that pass a solvency-only check because the account never goes negative.
struct AccountingConservationCheck {
/// @notice Identifier for the bound being asserted, e.g. "EQUITY_CONSERVATION".
bytes32 checkName;
/// @notice The account whose accounting is being inspected.
address account;
/// @notice The market whose accounting path is being inspected.
address market;
/// @notice Actual economic delta observed across the operation (e.g. post-equity minus pre-equity).
int256 actualDelta;
/// @notice Inclusive lower bound on the allowed economic delta.
int256 minAllowedDelta;
/// @notice Inclusive upper bound on the allowed economic delta.
int256 maxAllowedDelta;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
OracleAnchorCheck
One concrete oracle-anchor bound for a risk-critical transition.
struct OracleAnchorCheck {
/// @notice Identifier for the bound being asserted, e.g. "RISK_MARK_ANCHORED".
bytes32 checkName;
/// @notice The market whose oracle anchoring is being inspected.
address market;
/// @notice Actual price used by the protocol for the checked path.
uint256 usedPrice;
/// @notice Inclusive lower bound implied by the external oracle or mark source.
uint256 minOraclePrice;
/// @notice Inclusive upper bound implied by the external oracle or mark source.
uint256 maxOraclePrice;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
Enums
OperationKind
The perpetual action being inspected for shared post-operation safety checks.
enum OperationKind {
Unknown,
IncreasePosition,
DecreasePosition,
DepositCollateral,
WithdrawCollateral,
AddLiquidity,
RemoveLiquidity,
SettleFunding,
RealizePnL,
Liquidation
}
ComparisonKind
Comparison rule for the protocol-defined post-mutation risk metric.
enum ComparisonKind {
Unknown,
Gte,
Gt
}