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

credible-std

Standard library for implementing assertions in the Phylax Credible Layer (PCL). Provides the core contracts and interfaces needed to create, test, and validate assertions for smart contract security monitoring.

Documentation

Full API documentation is available at: https://phylaxsystems.github.io/credible-std

Examples

Assertion Book examples live in examples/assertions-book; micro-pattern examples live in examples/micro-patterns.

Installation

Install the latest stable release:

forge install phylaxsystems/credible-std@0.4.0

Or install from master (latest development version):

forge install phylaxsystems/credible-std

Add the remapping to your remappings.txt:

credible-std/=lib/credible-std/src/

Overview

The Phylax Credible Layer (PCL) is a security framework that enables real-time monitoring and validation of smart contract behavior through assertions. credible-std provides the foundational contracts and utilities needed to implement these assertions.

Key Components

ContractDescription
Assertion.solBase contract for creating assertions with trigger registration
Credible.solProvides access to the PhEvm precompile for transaction state inspection
PhEvm.solInterface for the PhEvm precompile (state forking, logs, call inputs)
StateChanges.solType-safe utilities for tracking contract state changes
TriggerRecorder.solInterface for registering assertion triggers
CredibleTest.solBase contract for testing assertions with Forge
CredibleTestWithBacktesting.solExtended test contract with historical transaction backtesting

Features

  • Trigger System: Register triggers for function calls, storage changes, and balance changes
  • State Inspection: Fork to pre/post transaction state, inspect logs, call inputs, and storage
  • Type-Safe State Changes: Built-in converters for uint256, address, bool, and bytes32 state changes
  • Testing Framework: Test assertions locally with Forge before deployment
  • Backtesting: Validate assertions against historical blockchain transactions
  • Internal Call Detection: Automatically detect transactions that call your contract internally (not just direct calls)

Quick Start

1. Create an Assertion

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Assertion} from "credible-std/Assertion.sol";

contract MyAssertion is Assertion {
    // Register when this assertion should run
    function triggers() external view override {
        // Run on any call to the adopter contract
        registerCallTrigger(this.checkInvariant.selector);

        // Or run only on specific function calls
        // registerCallTrigger(this.checkInvariant.selector, ITarget.deposit.selector);
    }

    // Implement your invariant check
    function checkInvariant() external {
        address target = ph.getAssertionAdopter();

        ph.forkPreTx();
        uint256 balanceBefore = target.balance;

        ph.forkPostTx();
        uint256 balanceAfter = target.balance;

        require(balanceAfter >= balanceBefore, "Balance decreased unexpectedly");
    }
}

2. Test Your Assertion

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {CredibleTest} from "credible-std/CredibleTest.sol";
import {Test} from "forge-std/Test.sol";
import {MyAssertion} from "./MyAssertion.sol";
import {MyContract} from "./MyContract.sol";

contract TestMyAssertion is CredibleTest, Test {
    MyContract target;

    function setUp() public {
        target = new MyContract();
    }

    function test_assertionPasses() public {
        // Register the assertion
        cl.assertion({
            adopter: address(target),
            createData: type(MyAssertion).creationCode,
            fnSelector: MyAssertion.checkInvariant.selector
        });

        // Execute a transaction - assertion runs automatically
        target.deposit{value: 1 ether}();
    }

    function test_assertionFails() public {
        cl.assertion({
            adopter: address(target),
            createData: type(MyAssertion).creationCode,
            fnSelector: MyAssertion.checkInvariant.selector
        });

        // This should revert because the assertion fails
        vm.expectRevert("Balance decreased unexpectedly");
        target.withdraw(1 ether);
    }
}

Run tests with:

pcl test

PhEvm Cheatcodes

Access these via the ph instance inherited from Credible:

FunctionDescription
forkPreTx()Fork to state before the transaction
forkPostTx()Fork to state after the transaction
forkPreCall(uint256 id)Fork to state before a specific call
forkPostCall(uint256 id)Fork to state after a specific call
load(address, bytes32)Load a storage slot value
getLogs()Get all logs emitted in the transaction
getCallInputs(address, bytes4)Get CALL inputs for target/selector
getStaticCallInputs(address, bytes4)Get STATICCALL inputs
getDelegateCallInputs(address, bytes4)Get DELEGATECALL inputs
getAllCallInputs(address, bytes4)Get all call types
callinputAt(uint256)Get full recorded calldata for one call id
callOutputAt(uint256)Get full recorded return or revert bytes for one call id
getStateChanges(address, bytes32)Get state changes for a slot
getAssertionAdopter()Get the adopter contract address

Trigger Types

Register triggers in your triggers() function:

function triggers() external view override {
    // Trigger on any call to the adopter
    registerCallTrigger(this.myAssertion.selector);

    // Trigger on specific function call
    registerCallTrigger(this.myAssertion.selector, ITarget.transfer.selector);

    // Trigger on any storage change
    registerStorageChangeTrigger(this.myAssertion.selector);

    // Trigger on specific storage slot change
    registerStorageChangeTrigger(this.myAssertion.selector, bytes32(uint256(0)));

    // Trigger on balance change
    registerBalanceChangeTrigger(this.myAssertion.selector);
}

Backtesting

Test your assertions against historical blockchain transactions to validate correctness before deployment. The backtesting framework automatically detects both direct calls AND internal/nested calls to your target contract.

Setup

Add to your foundry.toml:

[profile.backtesting]
src = "src"
test = "test"
ffi = true
gas_limit = 100000000

Block Range Backtesting

Test all transactions in a block range:

import {CredibleTestWithBacktesting} from "credible-std/CredibleTestWithBacktesting.sol";
import {BacktestingTypes} from "credible-std/utils/BacktestingTypes.sol";

contract MyBacktest is CredibleTestWithBacktesting {
    function testHistoricalTransactions() public {
        BacktestingTypes.BacktestingResults memory results = executeBacktest(
            BacktestingTypes.BacktestingConfig({
                targetContract: 0x1234...,           // Contract to monitor
                endBlock: 1000000,                   // End block
                blockRange: 100,                     // Number of blocks to test
                assertionCreationCode: type(MyAssertion).creationCode,
                assertionSelector: MyAssertion.check.selector,
                rpcUrl: "https://eth.llamarpc.com",
                detailedBlocks: false,               // Verbose block output
                forkByTxHash: true                   // Fork by tx hash for accurate state
            })
        );

        // Check no assertions failed
        assertEq(results.assertionFailures, 0, "Assertions failed on historical data");
    }
}

Single Transaction Backtesting

Test a specific transaction by hash:

contract MyBacktest is CredibleTestWithBacktesting {
    function testSpecificTransaction() public {
        bytes32 txHash = 0xabc123...;

        BacktestingTypes.BacktestingResults memory results = executeBacktestForTransaction(
            txHash,
            0x1234...,                              // Target contract
            type(MyAssertion).creationCode,
            MyAssertion.check.selector,
            "https://eth.llamarpc.com"
        );

        assertEq(results.assertionFailures, 0);
    }
}

Running Backtests

# Run with verbose output
pcl test --ffi -vvvv --match-test testHistoricalTransactions

# Or with the backtesting profile
FOUNDRY_PROFILE=backtesting pcl test -vvvv

Internal Call Detection

The backtesting framework automatically detects transactions that call your target contract internally (e.g., through a router or aggregator). It tries multiple trace APIs with automatic fallback:

  1. trace_filter - Fastest, requires Erigon or archive node with trace API
  2. debug_traceBlockByNumber - Slower but widely supported
  3. debug_traceTransaction - Slowest, per-transaction tracing
  4. Direct calls only - Fallback when no trace APIs available

Example output:

=== TRANSACTION DISCOVERY ===
Target: 0x1234...
Blocks: 1000000 to 1000100

[INFO] Detecting both direct calls AND internal/nested calls to target
[INFO] Trying trace APIs with automatic fallback...

[TRACE] Using trace_filter API (fastest method for internal call detection)
[TRACE] trace_filter not supported by this RPC endpoint
[TRACE] Falling back to debug_traceBlockByNumber (slower but widely supported)

=== DISCOVERY COMPLETE ===
[INFO] Detection method: debug_traceBlockByNumber
[INFO] Internal calls: ENABLED

Understanding Results

The backtesting framework provides detailed categorization:

ResultDescription
SuccessTransaction passed assertion validation
SkippedTransaction didn’t trigger the assertion (selector mismatch)
Assertion FailedReal protocol violation detected
Replay FailureTransaction reverted before assertion could run
Unknown ErrorUnexpected failure

When an assertion fails, the framework automatically replays the transaction with full Foundry tracing enabled, showing the complete execution path for debugging.

State Change Helpers

The StateChanges contract provides type-safe helpers for inspecting storage changes:

// Get state changes as specific types
uint256[] memory uintChanges = getStateChangesUint(target, slot);
address[] memory addrChanges = getStateChangesAddress(target, slot);
bool[] memory boolChanges = getStateChangesBool(target, slot);
bytes32[] memory rawChanges = getStateChangesBytes32(target, slot);

// With mapping key support
uint256[] memory balanceChanges = getStateChangesUint(target, balancesSlot, userKey);

// With slot offset for struct fields
uint256[] memory fieldChanges = getStateChangesUint(target, structSlot, key, fieldOffset);

Resources

License

MIT

Contents

Contents

AccessControlBaseAssertion

Git Source

Inherits: Assertion

Title: AccessControlBaseAssertion

Author: Phylax Systems

Base contract for access-control assertions (V2 syntax).

Provides the protected target address and shared helpers for the access-control suite. Inherit from this (and one or more invariant contracts), then implement triggers(). Each inherited mixin must have its _register*Triggers() helper called explicitly; forgetting one disables that mixin’s check without a runtime warning. Example – combine slot protection and balance conservation:

contract MyProtocolGuard is SlotProtectionAssertion, BalanceConservationAssertion {
constructor(address _target)
AccessControlBaseAssertion(_target)
{}
function _protectedSlots() internal pure override returns (bytes32[] memory) { ... }
function _conservedBalances() internal view override returns (ConservedBalance[] memory) { ... }
function triggers() external view override {
_registerSlotProtectionTriggers();
_registerBalanceConservationTriggers();
}
}

Constants

target

The contract whose access control is being protected (assertion adopter).

address internal immutable target

Functions

constructor

constructor(address _target) ;

BalanceConservationAssertion

Git Source

Inherits: AccessControlBaseAssertion

Title: BalanceConservationAssertion

Author: Phylax Systems

Asserts that specified account token balances do not change during the transaction, catching unauthorized balance changes for accounts that should remain untouched. Invariants covered:

  • Treasury / reserve protection: treasury or reserve account balances remain unchanged unless an authorized governance path is followed.
  • Escrow protection: escrow contract balances are conserved across the transaction.
  • Unauthorized transfer detection: catches privilege misuse, reentrancy side effects, or unexpected execution paths that drain protected accounts.
  • Custodial leg verification: for RWA protocols, outflows only along known subscription/redemption legs.

Uses the V2 conserveBalance(fork0, fork1, token, account) precompile to compare balanceOf(account) at PreTx vs PostTx for each protected (token, account) pair. The assertion makes one precompile call per protected pair, so keep the watched set small enough for the adopter’s assertion gas budget until a batched balance conservation precompile is available. Implementers must override _conservedBalances() to declare which (token, account) pairs to protect. For selector-aware outflow caps (e.g., looser limits on approved withdraw/redeem selectors), use per-function triggers and custom assertion logic instead of this mixin.

Functions

_conservedBalances

Returns the (token, account) pairs whose balances must not change.

Override to declare the protocol-specific protected balances. Common targets:

  • Protocol treasury / DAO treasury USDC/ETH balances
  • Pool cash reserves (Maple pool, Centrifuge escrow)
  • Vault reserves and fee-leftover accounts (Lido stVault)
  • Collateral and burner flows (Symbiotic vault)
  • RWAHub / subscription-redemption custodial balances (Ondo)
function _conservedBalances() internal view virtual returns (ConservedBalance[] memory balances);

Returns

NameTypeDescription
balancesConservedBalance[]Array of (token, account) pairs to conserve.

_registerBalanceConservationTriggers

Register the default trigger set for balance conservation.

Uses registerTxEndTrigger so the check fires once after the transaction completes. Call this inside your triggers().

function _registerBalanceConservationTriggers() internal view;

assertBalanceConservation

Verifies that all protected account balances are unchanged across the transaction.

Checks each (token, account) pair using the conserveBalance precompile at PreTx vs PostTx. Reverts on the first pair whose balance changed.

function assertBalanceConservation() external view;

Errors

BalanceChanged

error BalanceChanged(address token, address account);

Structs

ConservedBalance

A (token, account) pair whose balance must be conserved across the transaction.

struct ConservedBalance {
    /// @notice The ERC20 token address.
    address token;
    /// @notice The account whose balance should remain unchanged.
    address account;
}

SharePriceAssertion

Git Source

Inherits: AccessControlBaseAssertion

Title: SharePriceAssertion

Author: Phylax Systems

Asserts that ERC-4626 vault share prices do not deviate beyond a configurable tolerance, protecting against admin-driven share price manipulation. Invariants covered:

  • Share price stability: the ratio totalAssets / totalSupply must not shift more than toleranceBps across any fork point in the transaction.
  • Donation attack prevention: catches inflated totalAssets without proportional share minting.
  • First-depositor exploit prevention: detects exchange rate manipulation with tiny initial deposits followed by large donations.
  • Flash-loan manipulation: flags temporary share price distortion within a transaction.

Uses the V2 assetsMatchSharePrice precompile for a comprehensive all-forks check. This is a simpler mixin than the full ERC4626SharePriceAssertion – it omits per-call triggers and focuses on tx-wide share price envelope protection. Use this when the access-control concern is preventing admin manipulation of share prices, rather than enforcing full ERC-4626 compliance. Implementers must override _protectedVaults() to declare which vault addresses and tolerances to check. Each configured vault produces one all-forks precompile call, so large vault families should split coverage or move to a batched primitive when one is available.

Functions

_protectedVaults

Returns the vaults whose share prices must remain stable across the transaction.

Override to declare the protocol-specific vault addresses and tolerances. Tighter tolerances (e.g., 10 bps) are appropriate for stablecoin vaults; wider tolerances (e.g., 50-100 bps) may be needed for volatile-asset vaults or vaults with rebasing underlying assets.

function _protectedVaults() internal view virtual returns (ProtectedVault[] memory vaults);

Returns

NameTypeDescription
vaultsProtectedVault[]Array of (vault, toleranceBps) pairs to protect.

_registerSharePriceTriggers

Register the default trigger set for share price protection.

Uses registerTxEndTrigger so the check fires once after the transaction completes. Call this inside your triggers().

function _registerSharePriceTriggers() internal view;

assertSharePrice

Verifies that all protected vault share prices are stable across the transaction.

Uses ph.assetsMatchSharePrice() for each vault, which reads totalAssets() and totalSupply() at every fork point and checks for deviation beyond the tolerance. Reverts on the first vault whose share price moved beyond tolerance.

function assertSharePrice() external view;

Errors

SharePriceChanged

error SharePriceChanged(address vault, uint256 toleranceBps);

Structs

ProtectedVault

A vault and its maximum acceptable share-price deviation.

struct ProtectedVault {
    /// @notice The ERC-4626 vault address.
    address vault;
    /// @notice Maximum allowed share price deviation in basis points. 100 = 1%.
    uint256 toleranceBps;
}

SlotProtectionAssertion

Git Source

Inherits: AccessControlBaseAssertion

Title: SlotProtectionAssertion

Author: Phylax Systems

Asserts that critical storage slots on the assertion adopter are not modified during the transaction. Invariants covered:

  • Ownership immutability: proxy admin, owner, sentinel, and implementation slots cannot be changed outside expected governance choreography.
  • Timelock integrity: delay values, proposer/executor/canceller role slots are frozen so an attacker cannot shorten a delay before exploiting a governance path.
  • Role stability: admin roles, operator roles, manager roles remain unchanged unless an authorized governance action modifies them.
  • Configuration guards: fee parameters, oracle addresses, whitelist/blocklist settings, and other safety-critical configuration slots.

Uses the V2 forbidChangeForSlots precompile which checks the transaction journal for any SSTORE to the specified slots. A write is flagged even if it sets the same value (conservative – a write is suspicious regardless of whether the value changed). Writes inside reverted internal calls are rolled back in the journal and do not trigger a violation. Implementers must override _protectedSlots() to declare which slots to protect. For mapping entries (e.g., roles[account]), compute the slot off-chain via keccak256(abi.encode(key, mappingSlot)). The policy enforced is: fail by default on any watched-slot mutation. Protocols that need conditional slot changes should use per-function triggers instead and verify the change follows the expected governance path.

Functions

_protectedSlots

Returns the storage slots that must not be modified during the transaction.

Override to declare the protocol-specific critical slots. Common examples:

  • EIP-1967 admin slot: 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
  • EIP-1967 implementation slot: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
  • Gnosis Safe owner sentinel (slot 2), threshold (slot 4), singleton (slot 0)
  • Timelock delay parameters, proposer/executor/canceller roles
  • Protocol-specific admin, fee, oracle, and permission level slots
function _protectedSlots() internal pure virtual returns (bytes32[] memory slots);

Returns

NameTypeDescription
slotsbytes32[]Array of storage slot identifiers to protect.

_registerSlotProtectionTriggers

Register the default trigger set for slot protection.

Registers one storage-change trigger per protected slot, so the check runs only when a watched slot is actually written during the transaction instead of on every adopter transaction. This narrows execution to the exact risk surface without weakening the invariant: assertSlotProtection still inspects the whole journal via forbidChangeForSlots, which flags any SSTORE to a protected slot. A storage-change trigger fires off that same journal, so same-value writes (which forbidChangeForSlots treats as suspicious) still arm the assertion. Call this inside your triggers().

function _registerSlotProtectionTriggers() internal view;

assertSlotProtection

Verifies that none of the protected storage slots were written to during the tx.

Uses ph.forbidChangeForSlots() for a single precompile call covering all slots. Reverts if any protected slot was modified.

function assertSlotProtection() external view;

Errors

ProtectedSlotChanged

error ProtectedSlotChanged(bytes32[] slots);

Contents

CredibleBlockGuard

Git Source

Title: CredibleBlockGuard

Author: Phylax Systems

Reusable mixin that gates functions on block credibility. Inherit it and apply the onlyCredibleBlock modifier to any function that should only execute while the current block is credible, i.e. built by a Credible Layer builder that enforces assertions. When the credible builder set is offline the guard fails open so the protected contract is never bricked.

This is the general-purpose form of the credibility gate. {CredibleSafeGuard} is the same decision wired into a Safe transaction guard; protocols that want to protect their own functions directly should inherit this contract instead. Decision in _checkCredibleBlock (run by onlyCredibleBlock before the function body):

  1. If the credible builder set looks offline (the most recent credible block is more than failOpenBlockThreshold blocks behind the current block), FAIL OPEN and allow the call. This prevents a stalled builder set from permanently locking the contract.
  2. Otherwise the builder set is live, so the current block MUST be credible; if it is not, the call is blocked with NonCredibleBlock.
  3. If the current block is itself credible, the call is always allowed. Fail-open window. The product requirement is “fail open after ~15 minutes with no credible blocks”. The {ICredibleRegistry} records credibility by block number and does not expose timestamps, so the window is expressed as a block count that the credible builder set would produce in ~15 minutes on the target chain, e.g.:
  • ~12s blocks (Ethereum mainnet): 15 min ~= 75 blocks
  • ~2s blocks (typical L2): 15 min ~= 450 blocks
  • ~1s blocks: 15 min ~= 900 blocks Both the registry address and the fail-open threshold are immutable; re-pointing or re-tuning means redeploying the inheriting contract. (Configurable per deployment.)

Constants

credibleRegistry

The on-chain Credible Registry queried for block credibility.

ICredibleRegistry public immutable credibleRegistry

failOpenBlockThreshold

Number of blocks the most recent credible block may lag the current block before the guard fails open. Should approximate the chain’s 15-minute block budget.

uint256 public immutable failOpenBlockThreshold

Functions

constructor

constructor(ICredibleRegistry credibleRegistry_, uint256 failOpenBlockThreshold_) ;

Parameters

NameTypeDescription
credibleRegistry_ICredibleRegistryThe Credible Registry address (configurable per deployment).
failOpenBlockThreshold_uint256Blocks of builder silence tolerated before failing open.

onlyCredibleBlock

Reverts the call unless the current block is credible or the guard is failing open.

Apply to any function that must only run under credible-block protection.

modifier onlyCredibleBlock() ;

isCurrentBlockAllowed

Whether a call guarded by onlyCredibleBlock would currently be allowed.

View helper mirroring _checkCredibleBlock’s decision for off-chain inspection.

function isCurrentBlockAllowed() public view returns (bool);

Returns

NameTypeDescription
<none>boolTrue if the current block is credible or the guard is failing open.

failOpenActive

Whether the guard is currently failing open because the builder set looks offline.

function failOpenActive() public view returns (bool);

Returns

NameTypeDescription
<none>boolTrue if the most recent credible block lags the current block beyond the threshold.

_checkCredibleBlock

Core gate: fail open when the builder set is offline, otherwise require credibility.

function _checkCredibleBlock() internal view;

_failOpenActive

Fail-open is active when the current block is strictly more than failOpenBlockThreshold blocks ahead of the last credible block. The block.number > lastCredibleBlock_ guard avoids underflow if the registry ever reports a last credible block at or beyond the current block.

function _failOpenActive() internal view returns (bool);

Errors

NonCredibleBlock

Thrown when the current block is not credible and the builder set is live.

error NonCredibleBlock();

ZeroCredibleRegistryAddress

Thrown when constructed with the zero address as the registry.

error ZeroCredibleRegistryAddress();

ZeroFailOpenBlockThreshold

Thrown when constructed with a zero fail-open threshold.

error ZeroFailOpenBlockThreshold();

ICredibleRegistry

Git Source

Title: ICredibleRegistry

Author: Phylax Systems

Read interface for the on-chain Credible Registry that tracks which blocks were marked credible by authorized credible block builders.

Mirrors the read surface of phylaxsystems/credible-registry so consumers such as {CredibleBlockGuard} are drop-in compatible with the deployed registry. The registry records block credibility by block number; it does not expose timestamps.

Functions

isCredibleBlock

Returns whether the given block number was marked credible by a whitelisted builder.

function isCredibleBlock(uint256 blockNumber) external view returns (bool);

Parameters

NameTypeDescription
blockNumberuint256The block number to query.

Returns

NameTypeDescription
<none>boolTrue if blockNumber was marked credible, false otherwise.

lastCredibleBlock

Returns the most recent block number that was marked credible.

Returns 0 if no block has ever been marked credible.

function lastCredibleBlock() external view returns (uint256);

Returns

NameTypeDescription
<none>uint256The highest block number marked credible so far.

Contents

Contents

IERC20MetadataLike

Git Source

Minimal ERC20 metadata surface used by the Aave v3 Horizon example.

Functions

balanceOf

function balanceOf(address account) external view returns (uint256);

decimals

function decimals() external view returns (uint8);

AaveV3Types

Git Source

Structs

UserConfigurationMap

struct UserConfigurationMap {
    uint256 data;
}

ReserveDataLegacy

struct ReserveDataLegacy {
    uint256 configurationData;
    uint128 liquidityIndex;
    uint128 currentLiquidityRate;
    uint128 variableBorrowIndex;
    uint128 currentVariableBorrowRate;
    uint128 currentStableBorrowRate;
    uint40 lastUpdateTimestamp;
    uint16 id;
    address aTokenAddress;
    address stableDebtTokenAddress;
    address variableDebtTokenAddress;
    address interestRateStrategyAddress;
    uint128 accruedToTreasury;
    uint128 unbacked;
    uint128 isolationModeTotalDebt;
}

IAaveV3PoolLike

Git Source

Minimal pool surface matching the local Aave v3 Horizon pool interface.

This example was derived against ~/Documents/code/solidity/aave-v3-horizon/.

Functions

borrow

function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)
    external;

withdraw

function withdraw(address asset, uint256 amount, address to) external returns (uint256);

liquidationCall

function liquidationCall(
    address collateralAsset,
    address debtAsset,
    address user,
    uint256 debtToCover,
    bool receiveAToken
) external;

setUserUseReserveAsCollateral

function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

finalizeTransfer

function finalizeTransfer(
    address asset,
    address from,
    address to,
    uint256 amount,
    uint256 balanceFromBefore,
    uint256 balanceToBefore
) external;

getUserAccountData

function getUserAccountData(address user)
    external
    view
    returns (
        uint256 totalCollateralBase,
        uint256 totalDebtBase,
        uint256 availableBorrowsBase,
        uint256 currentLiquidationThreshold,
        uint256 ltv,
        uint256 healthFactor
    );

getUserConfiguration

function getUserConfiguration(address user) external view returns (AaveV3Types.UserConfigurationMap memory);

getReserveData

function getReserveData(address asset) external view returns (AaveV3Types.ReserveDataLegacy memory);

getReservesList

function getReservesList() external view returns (address[] memory);

ADDRESSES_PROVIDER

function ADDRESSES_PROVIDER() external view returns (address);

IAaveV3AddressesProviderLike

Git Source

Functions

getPriceOracle

function getPriceOracle() external view returns (address);

IAaveV3OracleLike

Git Source

Functions

getAssetPrice

function getAssetPrice(address asset) external view returns (uint256);

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;
}

IERC20MetadataLike

Git Source

Minimal ERC20 metadata surface used by the Aave v3-like lending examples.

Functions

balanceOf

function balanceOf(address account) external view returns (uint256);

decimals

function decimals() external view returns (uint8);

AaveV3LikeTypes

Git Source

Minimal reserve and user structs shared by Aave v3-like pool forks.

Structs

UserConfigurationMap

struct UserConfigurationMap {
    uint256 data;
}

ReserveData

struct ReserveData {
    uint256 configurationData;
    uint128 liquidityIndex;
    uint128 currentLiquidityRate;
    uint128 variableBorrowIndex;
    uint128 currentVariableBorrowRate;
    uint128 currentStableBorrowRate;
    uint40 lastUpdateTimestamp;
    uint16 id;
    address aTokenAddress;
    address stableDebtTokenAddress;
    address variableDebtTokenAddress;
    address interestRateStrategyAddress;
    uint128 accruedToTreasury;
    uint128 unbacked;
    uint128 isolationModeTotalDebt;
}

IAaveV3LikePool

Git Source

Minimal pool surface shared by Aave v3-compatible lending markets.

Functions

borrow

function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)
    external;

withdraw

function withdraw(address asset, uint256 amount, address to) external returns (uint256);

liquidationCall

function liquidationCall(
    address collateralAsset,
    address debtAsset,
    address user,
    uint256 debtToCover,
    bool receiveAToken
) external;

setUserUseReserveAsCollateral

function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;

setUserEMode

function setUserEMode(uint8 categoryId) external;

finalizeTransfer

function finalizeTransfer(
    address asset,
    address from,
    address to,
    uint256 amount,
    uint256 balanceFromBefore,
    uint256 balanceToBefore
) external;

getUserAccountData

function getUserAccountData(address user)
    external
    view
    returns (
        uint256 totalCollateralBase,
        uint256 totalDebtBase,
        uint256 availableBorrowsBase,
        uint256 currentLiquidationThreshold,
        uint256 ltv,
        uint256 healthFactor
    );

getUserConfiguration

function getUserConfiguration(address user) external view returns (AaveV3LikeTypes.UserConfigurationMap memory);

getReserveData

function getReserveData(address asset) external view returns (AaveV3LikeTypes.ReserveData memory);

getReservesList

function getReservesList() external view returns (address[] memory);

ADDRESSES_PROVIDER

function ADDRESSES_PROVIDER() external view returns (address);

IAaveV3LikeAddressesProvider

Git Source

Functions

getPriceOracle

function getPriceOracle() external view returns (address);

IAaveV3LikeOracle

Git Source

Functions

getAssetPrice

function getAssetPrice(address asset) external view returns (uint256);

BASE_CURRENCY

function BASE_CURRENCY() external view returns (address);

BASE_CURRENCY_UNIT

function BASE_CURRENCY_UNIT() external view returns (uint256);

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.

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"

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.

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

decodeOperation

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

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

shouldCheckPostOperationSolvency

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

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.

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.

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.

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.

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

_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;
}

AaveV3LikeOperationSafetyAssertionBase

Git Source

Inherits: LendingBaseAssertion

Title: AaveV3LikeOperationSafetyAssertionBase

Author: Phylax Systems

Shared assertion wrapper for Aave v3-like lending suites.

Constants

SUITE

ILendingProtectionSuite internal immutable SUITE

Functions

constructor

constructor(ILendingProtectionSuite suite_) ;

_suite

function _suite() internal view override returns (ILendingProtectionSuite);

AaveV3HorizonProtectionSuite

Git Source

Inherits: AaveV3LikeProtectionSuite

Title: AaveV3HorizonProtectionSuite

Author: Phylax Systems

Example ILendingProtectionSuite targeting a local Aave v3 Horizon deployment.

The underlying logic lives in AaveV3LikeProtectionSuite so Base Aave, local Horizon deployments, and close forks can reuse the same operation-safety model. The deployment wrapper exists to keep the public example small: pass the pool and addresses provider, then the shared suite supplies health-factor and bounded-consumption checks.

Functions

constructor

constructor(address pool_, address addressesProvider_) AaveV3LikeProtectionSuite(pool_, addressesProvider_);

AaveV3HorizonOperationSafetyAssertion

Git Source

Inherits: AaveV3LikeOperationSafetyAssertionBase

Title: AaveV3HorizonOperationSafetyAssertion

Author: Phylax Systems

Example single-entry assertion bundle for Aave v3 Horizon.

Usage: cl.assertion({ adopter: aaveV3HorizonPool, createData: abi.encodePacked(type(AaveV3HorizonOperationSafetyAssertion).creationCode, abi.encode(aaveV3HorizonPool, addressesProvider)), fnSelector: AaveV3HorizonOperationSafetyAssertion.assertOperationSafety.selector }) The assertion is registered against the pool because the pool is the entry point for borrow, withdraw, liquidation, collateral-flag, aToken transfer-finalization, and e-mode changes. A revert from this bundle means a risk-increasing pool call either made a healthy user liquidatable or consumed more user debt/collateral claim than the pre-call state allowed.

Functions

constructor

constructor(address pool_, address addressesProvider_)
    AaveV3LikeOperationSafetyAssertionBase(ILendingProtectionSuite(
            address(new AaveV3HorizonProtectionSuite(pool_, addressesProvider_))
        ));

AaveV3ProtectionSuite

Git Source

Inherits: AaveV3HorizonProtectionSuite

Title: AaveV3ProtectionSuite

Author: Phylax Systems

Compatibility alias preserving the old generic Aave v3 suite name.

Functions

constructor

constructor(address pool_, address addressesProvider_) AaveV3HorizonProtectionSuite(pool_, addressesProvider_);

AaveV3OperationSafetyAssertion

Git Source

Inherits: AaveV3HorizonOperationSafetyAssertion

Title: AaveV3OperationSafetyAssertion

Author: Phylax Systems

Compatibility alias preserving the old generic Aave v3 assertion name.

Functions

constructor

constructor(address pool_, address addressesProvider_)
    AaveV3HorizonOperationSafetyAssertion(pool_, addressesProvider_);

AaveV3PostOperationSolvencyAssertion

Git Source

Inherits: AaveV3HorizonOperationSafetyAssertion

Title: AaveV3PostOperationSolvencyAssertion

Author: Phylax Systems

Deprecated compatibility alias for the pre-operation-safety contract name.

Functions

constructor

constructor(address pool_, address addressesProvider_)
    AaveV3HorizonOperationSafetyAssertion(pool_, addressesProvider_);

SparkLendV1ProtectionSuite

Git Source

Inherits: AaveV3LikeProtectionSuite

Title: SparkLendV1ProtectionSuite

Author: Phylax Systems

ILendingProtectionSuite implementation for SparkLend v1.

SparkLend v1 preserves the Aave v3 pool interface and health-factor accounting closely enough to reuse the shared Aave v3-like lending adapter directly.

Functions

constructor

constructor(address pool_, address addressesProvider_) AaveV3LikeProtectionSuite(pool_, addressesProvider_);

SparkLendV1OperationSafetyAssertion

Git Source

Inherits: AaveV3LikeOperationSafetyAssertionBase

Title: SparkLendV1OperationSafetyAssertion

Author: Phylax Systems

Single-entry assertion bundle for SparkLend v1 pools.

Usage: cl.assertion({ adopter: sparkPool, createData: abi.encodePacked(type(SparkLendV1OperationSafetyAssertion).creationCode, abi.encode(sparkPool, addressesProvider)), fnSelector: SparkLendV1OperationSafetyAssertion.assertOperationSafety.selector })

Functions

constructor

constructor(address pool_, address addressesProvider_)
    AaveV3LikeOperationSafetyAssertionBase(ILendingProtectionSuite(
            address(new SparkLendV1ProtectionSuite(pool_, addressesProvider_))
        ));

SparkLendV1PostOperationSolvencyAssertion

Git Source

Inherits: SparkLendV1OperationSafetyAssertion

Title: SparkLendV1PostOperationSolvencyAssertion

Author: Phylax Systems

Deprecated compatibility alias for the pre-operation-safety contract name.

Functions

constructor

constructor(address pool_, address addressesProvider_)
    SparkLendV1OperationSafetyAssertion(pool_, addressesProvider_);

ILendingProtectionSuite

Git Source

Title: ILendingProtectionSuite

Author: Phylax Systems

Step-oriented interface for protocol-specific lending protection suites.

Implementations expose the protocol-specific plumbing needed to assert a shared family of lending-protocol invariants:

  • any successful action that increases debt or reduces effective collateral must not move a solvent account into insolvency under the protocol’s own risk metric
  • successful withdrawals must not consume more claim than the account had before the call
  • successful liquidations must not consume more debt or collateral than existed before the call The bounded-consumption portion should be measured from the successful call’s actual effect, such as traced return data, ERC20 transfer deltas, or protocol-emitted events, rather than from the requested input amount alone. Expected assertion flow:
  1. Read monitored selectors from getMonitoredSelectors().
  2. Resolve the caller-aware TriggeredCall.
  3. Decode the triggered call with decodeOperation(...).
  4. Read operation-specific consumption checks with getConsumptionChecks(...) and require consumed <= availableBefore for each returned check.
  5. Filter to solvency-relevant operations with shouldCheckPostOperationSolvency(...).
  6. Read the account snapshot with getAccountSnapshot(...).
  7. Require that a solvent pre-call account remains solvent after the call. Different protocols can encode different solvency metrics while preserving the same invariant:
  • Aave-like systems can expose metric = healthFactor, threshold = 1e18, comparison = Gte.
  • Euler-like systems can expose metric = collateralValue - liabilityValue, threshold = 0, comparison = Gt.

Functions

getMonitoredSelectors

Returns the adopter selectors that can participate in the shared lending invariants.

Implementations should return the selectors that the generic assertion must subscribe to on the adopter. These selectors do not all need to be solvency-worsening on every invocation; decodeOperation(...) and shouldCheckPostOperationSolvency(...) can later discard benign or no-op cases. The returned list is typically protocol-entrypoint specific.

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

Returns

NameTypeDescription
selectorsbytes4[]Selectors that should trigger the generic lending operation-safety assertion.

decodeOperation

Decodes the triggered adopter call into a protocol-normalized operation context.

Caller-aware decoding is necessary because some protocols encode the affected account in msg.sender instead of calldata. Implementations should populate operation.account with the account whose post-operation solvency must be checked and mark whether the call increased debt and/or reduced effective collateral. Unsupported or irrelevant selectors should normally return the zero-value OperationContext rather than revert.

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

Parameters

NameTypeDescription
triggeredTriggeredCallThe exact adopter frame that caused the assertion to run.

Returns

NameTypeDescription
operationOperationContextProtocol-normalized context used by downstream filtering and checks.

shouldCheckPostOperationSolvency

Returns whether the decoded action must preserve post-operation solvency.

This is the last protocol-specific filter before snapshot reads happen. Implementations should return false for actions that are not risk-increasing in the shared sense, such as enabling collateral, zero-amount paths, or protocol no-ops.

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

Parameters

NameTypeDescription
operationOperationContextThe decoded operation context returned by decodeOperation(...).

Returns

NameTypeDescription
shouldCheckboolTrue when the assertion should read state and enforce solvency.

getConsumptionChecks

Returns the bounded-consumption checks implied by the decoded operation.

Each returned entry is enforced as consumed <= availableBefore. Implementations should return an empty array when the operation has no shared bounded-consumption invariant. consumed must reflect the actual effect of the successful call, not merely the requested amount. This lets suites support clipped operations such as partial withdraws or capped liquidations without bespoke assertion logic in the base contract. Suitable data sources include traced call output, ERC20 transfer introspection, or decoded logs.

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

Parameters

NameTypeDescription
triggeredTriggeredCallThe exact adopter frame that caused the assertion to run.
operationOperationContextThe decoded operation context returned by decodeOperation(...).
beforeForkPhEvm.ForkIdThe pre-call snapshot fork.
afterForkPhEvm.ForkIdThe post-call snapshot fork.

Returns

NameTypeDescription
checksConsumptionCheck[]Operation-specific bounds to enforce for the successful call.

getAccountSnapshot

Reads the account snapshot used by the post-operation solvency assertion.

Implementations can override this with a protocol-optimized hot path instead of forcing the assertion to always compute per-asset balances. The returned snapshot must describe the post-operation state at fork. The shared suite default in LendingBaseAssertion.sol composes this from getAccountState(...), getAccountBalances(...), and evaluateSolvency(...).

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

Parameters

NameTypeDescription
accountaddressThe account whose post-operation solvency is being checked.
forkPhEvm.ForkIdThe post-call snapshot fork that should be queried.

Returns

NameTypeDescription
snapshotAccountSnapshotAggregate state, optional per-asset balances, and the final solvency result.

getAccountState

Reads protocol-normalized aggregate account state at a given snapshot fork.

Implementations should return enough aggregate information for downstream solvency logic and debugging. Protocol-specific fields that do not fit the common shape belong in state.metadata.

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

Parameters

NameTypeDescription
accountaddressThe account whose risk state should be inspected.
forkPhEvm.ForkIdThe snapshot fork to read from.

Returns

NameTypeDescription
stateAccountStateAggregate account state in protocol-defined accounting units.

getAccountBalances

Reads protocol-normalized per-asset balances for an account at a snapshot fork.

Implementations may return an empty array when balances are not required for the hot-path solvency decision and getAccountSnapshot(...) already exposes an optimized path. When provided, balances should use the same valuation units as AccountState.

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

Parameters

NameTypeDescription
accountaddressThe account whose positions should be inspected.
forkPhEvm.ForkIdThe snapshot fork to read from.

Returns

NameTypeDescription
balancesAccountBalance[]Per-asset collateral and debt entries relevant to the account.

evaluateSolvency

Evaluates the protocol’s solvency rule from the decoded account snapshot.

Implementations should encode the exact rule the protocol uses to decide whether an account is solvent or liquidatable. state and balances are passed separately so simple protocols can decide from aggregate state alone while more complex protocols can inspect per-asset positions.

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

Parameters

NameTypeDescription
stateAccountStateAggregate account state returned by getAccountState(...).
balancesAccountBalance[]Per-asset balances returned by getAccountBalances(...).
forkPhEvm.ForkIdThe snapshot fork used for the solvency evaluation.

Returns

NameTypeDescription
solvencySolvencyStateProtocol-defined solvency decision and supporting metric data.

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 lending 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 solvency should be checked after the operation.
    address account;
    /// @notice The primary asset involved in the action, if any.
    address asset;
    /// @notice Optional second asset involved in the action, if any.
    /// @dev This is primarily useful for multi-asset operations such as liquidation, where
    ///      `asset` can represent the debt asset and `relatedAsset` the collateral asset.
    address relatedAsset;
    /// @notice Secondary account involved in the action, if any (receiver, liquidator, etc.).
    address counterparty;
    /// @notice Protocol-decoded requested or declared amount associated with the action, if any.
    /// @dev For clipped operations, this may differ from the actual amount later enforced by
    ///      `getConsumptionChecks(...)`.
    uint256 amount;
    /// @notice True when the action increases the account's debt exposure.
    bool increasesDebt;
    /// @notice True when the action reduces the account's effective collateral.
    bool reducesEffectiveCollateral;
    /// @notice Extension point for protocol-specific metadata.
    bytes metadata;
}

AccountState

Protocol-normalized aggregate state for an account at a snapshot fork.

struct AccountState {
    /// @notice The account whose state was read.
    address account;
    /// @notice Aggregate collateral value using the implementation's protocol-defined accounting units.
    uint256 totalCollateralValue;
    /// @notice Aggregate debt value using the implementation's protocol-defined accounting units.
    uint256 totalDebtValue;
    /// @notice Whether the account currently has any open debt.
    bool hasDebt;
    /// @notice Extension point for protocol-specific aggregate data.
    bytes metadata;
}

AccountBalance

Per-asset balance and value data for an account at a snapshot fork.

struct AccountBalance {
    /// @notice The reserve, market, or asset address represented by this balance entry.
    address asset;
    /// @notice Raw collateral or supplied balance tracked for the account.
    uint256 collateralBalance;
    /// @notice Raw debt balance tracked for the account.
    uint256 debtBalance;
    /// @notice Protocol-normalized collateral value for this asset.
    uint256 collateralValue;
    /// @notice Protocol-normalized debt value for this asset.
    uint256 debtValue;
    /// @notice Whether this asset currently counts as collateral for the account.
    bool countsAsCollateral;
    /// @notice Extension point for protocol-specific per-asset metadata.
    bytes metadata;
}

SolvencyState

Protocol-defined solvency output for an account at a snapshot fork.

struct SolvencyState {
    /// @notice Whether the account is solvent under the protocol's own rules.
    bool isSolvent;
    /// @notice Whether the protocol would consider the account liquidatable at this snapshot.
    bool isLiquidatable;
    /// @notice Identifier for the solvency metric, e.g. "HEALTH_FACTOR" or "LIQUIDITY_EXCESS".
    bytes32 metricName;
    /// @notice Protocol-normalized solvency metric.
    int256 metric;
    /// @notice Threshold that the metric is compared against.
    int256 threshold;
    /// @notice Comparison rule used to interpret `metric` vs `threshold`.
    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-asset balances. Implementations may return an empty array on the hot path.
    AccountBalance[] balances;
    /// @notice Protocol-defined solvency decision for the snapshot.
    SolvencyState solvency;
}

ConsumptionCheck

One concrete resource-consumption bound that must hold for a successful operation.

struct ConsumptionCheck {
    /// @notice Identifier for the bound being asserted, e.g. "WITHDRAW_CLAIM".
    bytes32 checkName;
    /// @notice The account whose pre-operation resource balance caps the consumption.
    address account;
    /// @notice The asset whose pre-operation balance or claim is being bounded.
    address asset;
    /// @notice Resource available before the operation in protocol-defined accounting units.
    uint256 availableBefore;
    /// @notice Actual resource consumed by the successful operation in the same units.
    uint256 consumed;
    /// @notice Extension point for protocol-specific evidence or decoded fields.
    bytes metadata;
}

Enums

OperationKind

The lending action being inspected for shared post-operation safety checks.

enum OperationKind {
    Unknown,
    Borrow,
    WithdrawCollateral,
    DisableCollateral,
    TransferCollateral,
    Liquidation,
    SetEMode
}

ComparisonKind

Comparison rule for the protocol-defined solvency metric.

enum ComparisonKind {
    Unknown,
    Gte,
    Gt
}

LendingProtectionSuiteBase

Git Source

Inherits: ForkUtils, ILendingProtectionSuite

Title: LendingProtectionSuiteBase

Author: Phylax Systems

Shared default implementations for lending protection suites.

Functions

getConsumptionChecks

Default bounded-consumption implementation for suites with no extra resource checks.

Override this when the protocol needs withdraw, liquidation, or other consumption bounds. Returning an empty array keeps solvency-only suites source-compatible with the generic lending assertion.

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

Parameters

NameTypeDescription
triggeredTriggeredCallThe exact adopter frame that caused the assertion to run.
operationOperationContextThe decoded operation context.
beforeForkPhEvm.ForkIdThe pre-call snapshot fork.
afterForkPhEvm.ForkIdThe post-call snapshot fork.

Returns

NameTypeDescription
checksConsumptionCheck[]Empty by default.

getAccountSnapshot

Composes a full account snapshot from the step-oriented suite functions.

This is the default implementation for step-based suites. Override it only when the protocol exposes a materially cheaper or more direct way to answer the invariant than calling getAccountState(...), getAccountBalances(...), and evaluateSolvency(...) separately.

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

Parameters

NameTypeDescription
accountaddressThe account whose post-operation state should be read.
forkPhEvm.ForkIdThe snapshot fork to query.

Returns

NameTypeDescription
snapshotAccountSnapshotAggregate state, balances, and solvency produced by the suite steps.

_viewFailureMessage

Returns the suite-specific revert string for failed fork-time static calls.

function _viewFailureMessage() internal pure virtual override returns (string memory);

LendingBaseAssertion

Git Source

Inherits: Assertion

Title: LendingBaseAssertion

Author: Phylax Systems

Generic lending operation-safety assertion for lending protocols.

Inherit this together with a concrete ILendingProtectionSuite implementation. The base contract handles one decode pass per triggered call, then enforces both:

  • any bounded-consumption checks returned by the suite
  • healthy accounts are not made insolvent by risk-increasing operations Both checks run per call so an account that is made insolvent by an individual operation is caught even if a later operation in the same transaction repairs it.

Functions

_suite

Returns the protocol-specific lending suite that powers this assertion.

Concrete assertions typically inherit both this base contract and a suite contract, then return ILendingProtectionSuite(address(this)). Returning a different contract is also valid if the assertion delegates protocol logic elsewhere.

function _suite() internal view virtual returns (ILendingProtectionSuite);

Returns

NameTypeDescription
<none>ILendingProtectionSuitesuite The suite used to decode operations and evaluate solvency.

constructor

constructor() ;

triggers

Registers one generic lending operation-safety check for every monitored selector.

This is the only trigger wiring most lending assertions need to implement. The suite decides which selectors matter through getMonitoredSelectors(), and this base maps all of them to assertOperationSafety(). Both the bounded-consumption checks and post- operation solvency run per call: solvency genuinely needs the per-call boundary so a risk-increasing operation that makes a healthy account insolvent is caught at that exact call, even if a later operation in the same transaction repairs the account before tx end.

function triggers() external view virtual override;

assertOperationSafety

Enforces the shared lending operation-safety invariants for a successful call.

Assertion authors should usually point Credible at this selector. The method resolves the triggering adopter frame, decodes the protocol operation once, enforces any bounded- consumption checks returned by the suite, and then enforces post-operation solvency when the suite marks the operation as risk-increasing.

function assertOperationSafety() external view;

assertPostOperationSolvency

Backwards-compatible alias for the legacy solvency-only entrypoint name.

Older bundles may still reference this selector directly. It now runs the full generic lending operation-safety pipeline rather than only the solvency portion.

function assertPostOperationSolvency() external view;

_assertOperationSafety

Internal implementation shared by the public lending assertion entrypoints.

Runs all shared lending checks exposed by the suite against the triggered adopter call.

function _assertOperationSafety() internal view;

_assertConsumptionChecks

Enforces the suite-provided bounded-consumption checks for the triggered operation.

Suites may return zero, one, or many bounds depending on the operation kind. Each bound is enforced as consumed <= availableBefore, where both values must already be expressed in the same protocol-defined units.

function _assertConsumptionChecks(
    ILendingProtectionSuite suite,
    ILendingProtectionSuite.TriggeredCall memory triggered,
    ILendingProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

Parameters

NameTypeDescription
suiteILendingProtectionSuiteThe protocol-specific lending suite.
triggeredILendingProtectionSuite.TriggeredCallThe exact adopter frame that caused the assertion to run.
operationILendingProtectionSuite.OperationContextThe decoded lending operation.
beforeForkPhEvm.ForkIdThe pre-call snapshot fork.
afterForkPhEvm.ForkIdThe post-call snapshot fork.

_assertPostOperationSolvency

Enforces that risk-increasing operations do not newly make an account insolvent.

Operations that do not increase debt or reduce effective collateral are skipped. Accounts already insolvent before the call are also skipped so existing bad debt or liquidatable positions do not cause false positives on unrelated account-management actions.

function _assertPostOperationSolvency(
    ILendingProtectionSuite suite,
    ILendingProtectionSuite.TriggeredCall memory triggered,
    ILendingProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

Parameters

NameTypeDescription
suiteILendingProtectionSuiteThe protocol-specific lending suite.
triggeredILendingProtectionSuite.TriggeredCallThe exact adopter frame that caused the assertion to run.
operationILendingProtectionSuite.OperationContextThe decoded lending operation.
beforeForkPhEvm.ForkIdThe pre-call snapshot fork used for the baseline solvency read.
afterForkPhEvm.ForkIdThe post-call snapshot fork used for the solvency read.

_resolveTriggeredCall

Resolves the exact adopter frame that caused the current assertion execution.

Credible exposes the selector and call identifiers in ph.context(), but the suite also needs raw calldata and caller information for correct protocol decoding. This helper reconstructs that frame from getAllCallInputs(...) and packages it into ILendingProtectionSuite.TriggeredCall.

function _resolveTriggeredCall() internal view returns (ILendingProtectionSuite.TriggeredCall memory triggered);

Returns

NameTypeDescription
triggeredILendingProtectionSuite.TriggeredCallCaller-aware information about the adopter call being checked.

Errors

LendingTriggeredCallNotFound

error LendingTriggeredCallNotFound(bytes4 selector, uint256 callStart);

LendingOperationAccountMissing

error LendingOperationAccountMissing(bytes4 selector);

LendingConsumptionCheckViolated

error LendingConsumptionCheckViolated(
    address account,
    bytes4 selector,
    ILendingProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address asset,
    uint256 consumed,
    uint256 availableBefore
);

LendingPostOperationSolvencyViolated

error LendingPostOperationSolvencyViolated(
    address account,
    bytes4 selector,
    ILendingProtectionSuite.OperationKind kind,
    bytes32 metricName,
    int256 metric,
    int256 threshold
);

Contents

IPerpetualProtectionSuite

Git Source

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:
  1. Read monitored selectors from getMonitoredSelectors().
  2. Resolve the caller-aware TriggeredCall.
  3. Decode the triggered call with decodeOperation(...).
  4. Read and enforce any suite-provided execution, liquidity, funding, liquidation, and oracle-anchor checks for the successful call.
  5. Filter to non-liquidation risk-preserving operations with shouldCheckPostMutationRisk(...).
  6. Read the post-mutation snapshot with getPostMutationSnapshot(...).
  7. Require snapshot.risk.equity >= 0, snapshot.risk.hasBadDebt == false, and snapshot.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

NameTypeDescription
selectorsbytes4[]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

NameTypeDescription
triggeredTriggeredCallThe exact adopter frame that caused the assertion to run.

Returns

NameTypeDescription
operationOperationContextProtocol-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

NameTypeDescription
operationOperationContextThe decoded operation context returned by decodeOperation(...).

Returns

NameTypeDescription
shouldCheckboolTrue 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

NameTypeDescription
accountaddressThe account whose post-operation risk state is being checked.
forkPhEvm.ForkIdThe post-call snapshot fork that should be queried.

Returns

NameTypeDescription
snapshotAccountSnapshotAggregate 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
}

PerpetualProtectionSuiteBase

Git Source

Inherits: ForkUtils, IPerpetualProtectionSuite

Title: PerpetualProtectionSuiteBase

Author: Phylax Systems

Shared default implementations for perpetual protection suites.

Functions

enabledCheckKinds

Default suites populate no optional check families.

Override this when implementing any get*Checks(...) family so the assertion can avoid calling base methods that only allocate empty arrays.

function enabledCheckKinds()
    external
    view
    virtual
    override
    returns (IPerpetualProtectionSuite.EnabledCheckKinds memory enabled);

getExecutionPriceChecks

Default execution-price implementation for suites with no shared execution check.

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

getLiquidityCoverageChecks

Default liquidity-coverage implementation for suites with no shared coverage check.

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

getFundingDeltaChecks

Default funding-delta implementation for suites with no shared funding check.

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

getLiquidationChecks

Default liquidation implementation for suites with no shared liquidation checks.

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

getOracleAnchorChecks

Default oracle-anchor implementation for suites with no shared oracle check.

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

getAccountingConservationChecks

Default accounting-conservation implementation for suites with no shared accounting check.

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

getPostMutationSnapshot

Default post-mutation snapshot implementation for suites with account-only risk reads.

function getPostMutationSnapshot(
    TriggeredCall calldata triggered,
    OperationContext calldata operation,
    PhEvm.ForkId calldata fork
) external view virtual override returns (AccountSnapshot memory snapshot);

getAccountSnapshot

Composes a full account snapshot from the step-oriented suite functions.

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

_viewFailureMessage

Returns the suite-specific revert string for failed fork-time static calls.

function _viewFailureMessage() internal pure virtual override returns (string memory);

PerpetualBaseAssertion

Git Source

Inherits: Assertion

Title: PerpetualBaseAssertion

Author: Phylax Systems

Generic operation-safety assertion for perpetual protocols.

Inherit this together with a concrete IPerpetualProtectionSuite implementation. The base contract handles one decode pass per triggered call, then enforces any suite-provided execution, oracle, funding, liquidity, and liquidation checks before applying the shared post-mutation risk gate for non-liquidation operations.

Functions

_suite

Returns the protocol-specific perpetual suite that powers this assertion.

function _suite() internal view virtual returns (IPerpetualProtectionSuite);

triggers

Registers one generic perpetual operation-safety check for every monitored selector.

function triggers() external view virtual override;

assertOperationSafety

Enforces the shared perpetual operation-safety invariants for a successful call.

function assertOperationSafety() external view;

assertPostMutationRisk

Backwards-compatible alias for integrations that only reference the risk-gate name.

function assertPostMutationRisk() external view;

_assertOperationSafety

Internal implementation shared by the public perpetual assertion entrypoints.

function _assertOperationSafety() internal view;

_enabledCheckKinds

function _enabledCheckKinds(IPerpetualProtectionSuite suite)
    internal
    view
    returns (IPerpetualProtectionSuite.EnabledCheckKinds memory enabled);

_assertExecutionPriceChecks

Enforces suite-provided taker execution bounds for the triggered operation.

function _assertExecutionPriceChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertLiquidityCoverageChecks

Enforces suite-provided liquidity and liability coverage bounds.

function _assertLiquidityCoverageChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertFundingDeltaChecks

Enforces suite-provided cumulative-funding settlement bounds.

function _assertFundingDeltaChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertLiquidationChecks

Enforces suite-provided liquidation gating and loss-accounting bounds.

function _assertLiquidationChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertOracleAnchorChecks

Enforces suite-provided oracle-anchor bounds for risk-critical transitions.

function _assertOracleAnchorChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertAccountingConservationChecks

Enforces suite-provided accounting-conservation bounds for settlement paths.

Solvency-only checks are insufficient for exploit families where stale LP share math, double-counted PnL, or accounting drift create unjustified economic gain while the account remains non-negative. These checks compare the actual economic delta of an operation against protocol-derived bounds.

function _assertAccountingConservationChecks(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory beforeFork,
    PhEvm.ForkId memory afterFork
) internal view;

_assertPostMutationRisk

Enforces the shared post-mutation risk gate for non-liquidation operations.

function _assertPostMutationRisk(
    IPerpetualProtectionSuite suite,
    IPerpetualProtectionSuite.TriggeredCall memory triggered,
    IPerpetualProtectionSuite.OperationContext memory operation,
    PhEvm.ForkId memory afterFork
) internal view;

_resolveTriggeredCall

Resolves the exact adopter frame that caused the current assertion execution.

function _resolveTriggeredCall() internal view returns (IPerpetualProtectionSuite.TriggeredCall memory triggered);

_isWithinUintRange

Returns whether value lies within the inclusive [minimum, maximum] range.

function _isWithinUintRange(uint256 value, uint256 minimum, uint256 maximum) internal pure returns (bool);

_isWithinIntRange

Returns whether value lies within the inclusive [minimum, maximum] range.

function _isWithinIntRange(int256 value, int256 minimum, int256 maximum) internal pure returns (bool);

_positivePart

Returns the non-negative component of a signed deficit.

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

Errors

PerpetualTriggeredCallNotFound

error PerpetualTriggeredCallNotFound(bytes4 selector, uint256 callStart);

PerpetualOperationAccountMissing

error PerpetualOperationAccountMissing(bytes4 selector);

PerpetualExecutionPriceViolated

error PerpetualExecutionPriceViolated(
    address account,
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address market,
    uint256 executionPrice,
    uint256 minExecutionPrice,
    uint256 maxExecutionPrice
);

PerpetualLiquidityCoverageViolated

error PerpetualLiquidityCoverageViolated(
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address market,
    uint256 requiredAmount,
    uint256 availableAmount
);

PerpetualFundingDeltaViolated

error PerpetualFundingDeltaViolated(
    address account,
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address market,
    int256 actualFunding,
    int256 minExpectedFunding,
    int256 maxExpectedFunding
);

PerpetualLiquidationViolated

error PerpetualLiquidationViolated(
    address account,
    bytes4 selector,
    bytes32 checkName,
    address market,
    bool wasLiquidatableBefore,
    int256 lossCreated,
    uint256 absorbedLoss
);

PerpetualOracleAnchorViolated

error PerpetualOracleAnchorViolated(
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address market,
    uint256 usedPrice,
    uint256 minOraclePrice,
    uint256 maxOraclePrice
);

PerpetualAccountingConservationViolated

error PerpetualAccountingConservationViolated(
    address account,
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 checkName,
    address market,
    int256 actualDelta,
    int256 minAllowedDelta,
    int256 maxAllowedDelta
);

PerpetualSelfBadDebtCreated

error PerpetualSelfBadDebtCreated(
    address account, bytes4 selector, IPerpetualProtectionSuite.OperationKind kind, int256 equity
);

PerpetualPostMutationRiskViolated

error PerpetualPostMutationRiskViolated(
    address account,
    bytes4 selector,
    IPerpetualProtectionSuite.OperationKind kind,
    bytes32 metricName,
    int256 metricValue,
    int256 thresholdValue
);

Contents

Enum

Git Source

Minimal subset of Safe’s Enum library, vendored so the guard does not depend on the Safe contracts package.

Enums

Operation

enum Operation {
    Call,
    DelegateCall
}

IERC165

Git Source

ERC-165 interface (EIP-165 / OpenZeppelin IERC165).

Functions

supportsInterface

function supportsInterface(bytes4 interfaceId) external view returns (bool);

ITransactionGuard

Git Source

Inherits: IERC165

Safe transaction guard interface, identical to Safe’s Guard (v1.3.0/v1.4.1) and ITransactionGuard (v1.5.0).

type(ITransactionGuard).interfaceId == 0xe6d7a83a, the same value Safe’s GuardManager.setGuard checks via ERC-165 (error GS300 otherwise), so a {CredibleSafeGuard} can be installed on any Safe that supports transaction guards.

Functions

checkTransaction

Called by the Safe before executing an owner-authorized transaction.

Reverting here blocks the Safe transaction from executing.

function checkTransaction(
    address to,
    uint256 value,
    bytes memory data,
    Enum.Operation operation,
    uint256 safeTxGas,
    uint256 baseGas,
    uint256 gasPrice,
    address gasToken,
    address payable refundReceiver,
    bytes memory signatures,
    address msgSender
) external;

checkAfterExecution

Called by the Safe after executing the transaction.

function checkAfterExecution(bytes32 hash, bool success) external;

CredibleSafeGuard

Git Source

Inherits: ITransactionGuard

Title: CredibleSafeGuard

Author: Phylax Systems

Safe transaction guard that only allows owner/multisig Safe transactions while the current block is credible, i.e. built by a Credible Layer builder that enforces assertions. When the credible builder set is offline the guard fails open so the Safe is never bricked.

Installed on a Safe via setGuard(address(thisGuard)). The Safe calls checkTransaction before every owner-path execution; a revert blocks that execution. Scope. This guard implements only Safe’s transaction-guard interface ({ITransactionGuard}), so it gates the owner/multisig execTransaction path only. Module executions (execTransactionFromModule/...ReturnData) do not run transaction guards, so an enabled module can still execute while the current block is not credible. Gating module executions requires a separate Safe module guard (the v1.5.0 checkModuleTransaction hook) or a Credible Layer assertion such as {SafeTxShapeAssertion}. Decision in checkTransaction:

  1. If the current block is credible, the transaction is always allowed.
  2. Otherwise, if the credible builder set looks offline (the most recent credible block is more than failOpenBlockThreshold blocks behind the current block), FAIL OPEN and allow the transaction. This prevents a stalled builder set from permanently locking the Safe.
  3. Otherwise the builder set is live and the current block is not credible, so the transaction is blocked with NonCredibleBlock. Fail-open window. The product requirement is “fail open after ~15 minutes with no credible blocks”. The {ICredibleRegistry} records credibility by block number and does not expose timestamps, so the window is expressed as a block count that the credible builder set would produce in ~15 minutes on the target chain, e.g.:
  • ~12s blocks (Ethereum mainnet): 15 min ~= 75 blocks
  • ~2s blocks (typical L2): 15 min ~= 450 blocks
  • ~1s blocks: 15 min ~= 900 blocks Both the registry address and the fail-open threshold are immutable; re-pointing or re-tuning means deploying a new guard and calling setGuard again.

Constants

credibleRegistry

The on-chain Credible Registry queried for block credibility.

ICredibleRegistry public immutable credibleRegistry

failOpenBlockThreshold

Number of blocks the most recent credible block may lag the current block before the guard fails open. Should approximate the chain’s 15-minute block budget.

uint256 public immutable failOpenBlockThreshold

Functions

constructor

constructor(ICredibleRegistry credibleRegistry_, uint256 failOpenBlockThreshold_) ;

Parameters

NameTypeDescription
credibleRegistry_ICredibleRegistryThe Credible Registry address (configurable per deployment).
failOpenBlockThreshold_uint256Blocks of builder silence tolerated before failing open.

checkTransaction

Called by the Safe before executing an owner-authorized transaction.

Reverts with NonCredibleBlock to block a Safe transaction. All transaction fields are ignored: the guard only gates on whether the executing block is credible.

function checkTransaction(
    address,
    uint256,
    bytes memory,
    Enum.Operation,
    uint256,
    uint256,
    uint256,
    address,
    address payable,
    bytes memory,
    address
) external view override;

checkAfterExecution

Called by the Safe after executing the transaction.

No post-execution checks are performed.

function checkAfterExecution(bytes32, bool) external pure override;

supportsInterface

function supportsInterface(bytes4 interfaceId) external pure override returns (bool);

isCurrentBlockAllowed

Whether a Safe transaction would currently be allowed by this guard.

View helper mirroring checkTransaction’s decision for off-chain inspection.

function isCurrentBlockAllowed() external view returns (bool);

Returns

NameTypeDescription
<none>boolTrue if the current block is credible or the guard is failing open.

failOpenActive

Whether the guard is currently failing open because the builder set looks offline.

function failOpenActive() external view returns (bool);

Returns

NameTypeDescription
<none>boolTrue if the most recent credible block lags the current block beyond the threshold.

_checkCredibleBlock

Core gate: allow credible blocks, otherwise fail open only when the builder set is offline. The credible-block check runs first so the expected hot path (credible block, live builder set) costs a single registry call.

function _checkCredibleBlock() internal view;

_failOpenActive

Fail-open is active when the current block is strictly more than failOpenBlockThreshold blocks ahead of the last credible block. The block.number > lastCredibleBlock_ guard avoids underflow if the registry ever reports a last credible block at or beyond the current block.

function _failOpenActive() internal view returns (bool);

Errors

NonCredibleBlock

Thrown when the current block is not credible and the builder set is live.

error NonCredibleBlock();

ZeroCredibleRegistryAddress

Thrown when constructed with the zero address as the registry.

error ZeroCredibleRegistryAddress();

ZeroFailOpenBlockThreshold

Thrown when constructed with a zero fail-open threshold.

error ZeroFailOpenBlockThreshold();

ICredibleRegistry

Git Source

Title: ICredibleRegistry

Author: Phylax Systems

Read interface for the on-chain Credible Registry that tracks which blocks were marked credible by authorized credible block builders.

Mirrors the read surface of phylaxsystems/credible-registry so consumers such as {CredibleSafeGuard} are drop-in compatible with the deployed registry. The registry records block credibility by block number; it does not expose timestamps.

Functions

isCredibleBlock

Returns whether the given block number was marked credible by a whitelisted builder.

function isCredibleBlock(uint256 blockNumber) external view returns (bool);

Parameters

NameTypeDescription
blockNumberuint256The block number to query.

Returns

NameTypeDescription
<none>boolTrue if blockNumber was marked credible, false otherwise.

lastCredibleBlock

Returns the most recent block number that was marked credible.

Returns 0 if no block has ever been marked credible.

function lastCredibleBlock() external view returns (uint256);

Returns

NameTypeDescription
<none>uint256The highest block number marked credible so far.

SafeConfigLockAssertion

Git Source

Inherits: SafeConfigLockHelpers

Title: SafeConfigLockAssertion

Author: Phylax Systems

Locks the critical configuration envelope for a Safe multisig.

The assertion checks the Safe after each monitored transaction:

  • threshold and owner count stay above configured minimums;
  • owner and module sets match one of the approved set hashes;
  • transaction guard, module guard, and fallback handler match expected addresses. Address set hashes are computed by sorting addresses ascending and then hashing abi.encode(sortedAddresses). For modules, bytes32(0) in the approved hash list is a sentinel meaning “modules must be disabled”.

Constants

minThreshold

uint256 public immutable minThreshold

minOwners

uint256 public immutable minOwners

expectedGuard

address public immutable expectedGuard

expectedModuleGuard

address public immutable expectedModuleGuard

expectedFallbackHandler

address public immutable expectedFallbackHandler

State Variables

approvedOwnerSetHashes

bytes32[] public approvedOwnerSetHashes

approvedModuleSetHashes

bytes32[] public approvedModuleSetHashes

Functions

constructor

constructor(
    uint256 minThreshold_,
    uint256 minOwners_,
    bytes32[] memory approvedOwnerSetHashes_,
    bytes32[] memory approvedModuleSetHashes_,
    address expectedGuard_,
    address expectedModuleGuard_,
    address expectedFallbackHandler_
) ;

triggers

function triggers() external view override;

assertSafeConfiguration

Checks the Safe config after the triggering transaction has completed.

Fails when a Safe transaction leaves owners, modules, guards, or fallback handling outside the deployment-time policy. A zero module-set hash in the approved list only approves the empty module set.

function assertSafeConfiguration() external view;

approvedOwnerSetHashCount

function approvedOwnerSetHashCount() external view returns (uint256);

approvedModuleSetHashCount

function approvedModuleSetHashCount() external view returns (uint256);

ISafeConfigLockTarget

Git Source

Functions

getThreshold

function getThreshold() external view returns (uint256);

getOwners

function getOwners() external view returns (address[] memory);

getModulesPaginated

function getModulesPaginated(address start, uint256 pageSize)
    external
    view
    returns (address[] memory array, address next);

SafeConfigLockHelpers

Git Source

Inherits: Assertion

Title: SafeConfigLockHelpers

Author: Phylax Systems

Shared constants and snapshot readers for Safe configuration assertions.

Constants

SENTINEL_MODULES

address internal constant SENTINEL_MODULES = address(0x1)

MODULE_PAGE_SIZE

uint256 internal constant MODULE_PAGE_SIZE = 256

MODULE_PAGE_VIEW_GAS

uint64 internal constant MODULE_PAGE_VIEW_GAS = 2_000_000

FALLBACK_HANDLER_STORAGE_SLOT

bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT =
    0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5

GUARD_STORAGE_SLOT

bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8

MODULE_GUARD_STORAGE_SLOT

bytes32 internal constant MODULE_GUARD_STORAGE_SLOT =
    0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947

Functions

hashAddressSet

Computes the deterministic hash used by owner and module allow lists.

Sorts a copy before hashing, so Safe linked-list order does not affect the resulting set hash and the caller’s memory array is left unchanged.

function hashAddressSet(address[] memory accounts) public pure returns (bytes32);

_ownersAt

function _ownersAt(address safe, PhEvm.ForkId memory fork) internal view returns (address[] memory owners);

_thresholdAt

function _thresholdAt(address safe, PhEvm.ForkId memory fork) internal view returns (uint256);

_modulesAt

function _modulesAt(address safe, PhEvm.ForkId memory fork) internal view returns (address[] memory modules);

_guardAt

function _guardAt(address safe, PhEvm.ForkId memory fork) internal view returns (address);

_moduleGuardAt

function _moduleGuardAt(address safe, PhEvm.ForkId memory fork) internal view returns (address);

_fallbackHandlerAt

function _fallbackHandlerAt(address safe, PhEvm.ForkId memory fork) internal view returns (address);

_addressSlotAt

function _addressSlotAt(address safe, bytes32 slot, PhEvm.ForkId memory fork) internal view returns (address);

_isApprovedHash

function _isApprovedHash(bytes32 actualHash, bytes32[] storage approvedHashes, bool emptySet)
    internal
    view
    returns (bool);

_sortAddresses

function _sortAddresses(address[] memory accounts) internal pure;

_quickSortAddresses

function _quickSortAddresses(address[] memory accounts, uint256 left, uint256 right) private pure;

_appendAddresses

function _appendAddresses(address[] memory left, address[] memory right)
    private
    pure
    returns (address[] memory combined);

_viewFailureMessage

function _viewFailureMessage() internal pure override returns (string memory);

_registerReshiramSpec

function _registerReshiramSpec() internal;

SafeTxShapeAssertion

Git Source

Inherits: SafeTxShapeHelpers

Title: SafeTxShapeAssertion

Author: Phylax Systems

Enforces direct Safe action-shape policy for owner and module executions.

Validates the Safe transaction tuple before settlement: known targets, exact selectors, delegatecall restrictions, approved MultiSend batch contents, and token approval spender/operator policy.

Functions

constructor

constructor(
    TargetPolicy[] memory targetPolicies_,
    SelectorPolicy[] memory selectorPolicies_,
    BatchExecutorPolicy[] memory batchExecutorPolicies_,
    ApprovalPolicy[] memory approvalPolicies_,
    bool moduleExecutionEnabled_,
    address[] memory allowedModules_
)
    SafeTxShapeHelpers(
        targetPolicies_,
        selectorPolicies_,
        batchExecutorPolicies_,
        approvalPolicies_,
        moduleExecutionEnabled_,
        allowedModules_
    );

triggers

function triggers() external view override;

assertSafeModulePolicy

Ensures module executions are disabled or sent by an allowlisted module.

function assertSafeModulePolicy() external view;

assertSafeDelegateCallPolicy

Blocks direct, module, and inner delegatecalls except configured top-level MultiSend execution.

function assertSafeDelegateCallPolicy() external view;

assertSafeTargetSelectorPolicy

Ensures every non-batch action uses a known target and allowed selector.

function assertSafeTargetSelectorPolicy() external view;

assertSafeBatchPolicy

Strictly parses configured MultiSend batches and rejects malformed or nested batches.

function assertSafeBatchPolicy() external view;

assertSafeApprovalPolicy

Enforces spender/operator and amount limits for approval-like calls.

function assertSafeApprovalPolicy() external view;

SafeTxShapeHelpers

Git Source

Inherits: Assertion

Title: SafeTxShapeHelpers

Author: Phylax Systems

Shared decoding and policy helpers for Safe transaction-shape assertions.

Constants

OPERATION_CALL

uint8 internal constant OPERATION_CALL = 0

OPERATION_DELEGATECALL

uint8 internal constant OPERATION_DELEGATECALL = 1

APPROVAL_KIND_ERC20_APPROVE

uint8 public constant APPROVAL_KIND_ERC20_APPROVE = 1

APPROVAL_KIND_ERC20_INCREASE_ALLOWANCE

uint8 public constant APPROVAL_KIND_ERC20_INCREASE_ALLOWANCE = 2

APPROVAL_KIND_ERC721_APPROVE

uint8 public constant APPROVAL_KIND_ERC721_APPROVE = 3

APPROVAL_KIND_ERC721_SET_APPROVAL_FOR_ALL

uint8 public constant APPROVAL_KIND_ERC721_SET_APPROVAL_FOR_ALL = 4

APPROVAL_KIND_ERC1155_SET_APPROVAL_FOR_ALL

uint8 public constant APPROVAL_KIND_ERC1155_SET_APPROVAL_FOR_ALL = 5

EXEC_TRANSACTION_SELECTOR

bytes4 public constant EXEC_TRANSACTION_SELECTOR =
    bytes4(keccak256("execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)"))

EXEC_TRANSACTION_FROM_MODULE_SELECTOR

bytes4 public constant EXEC_TRANSACTION_FROM_MODULE_SELECTOR =
    bytes4(keccak256("execTransactionFromModule(address,uint256,bytes,uint8)"))

EXEC_TRANSACTION_FROM_MODULE_RETURN_DATA_SELECTOR

bytes4 public constant EXEC_TRANSACTION_FROM_MODULE_RETURN_DATA_SELECTOR =
    bytes4(keccak256("execTransactionFromModuleReturnData(address,uint256,bytes,uint8)"))

MULTISEND_SELECTOR

bytes4 public constant MULTISEND_SELECTOR = bytes4(keccak256("multiSend(bytes)"))

APPROVE_SELECTOR

bytes4 public constant APPROVE_SELECTOR = bytes4(keccak256("approve(address,uint256)"))

INCREASE_ALLOWANCE_SELECTOR

bytes4 public constant INCREASE_ALLOWANCE_SELECTOR = bytes4(keccak256("increaseAllowance(address,uint256)"))

SET_APPROVAL_FOR_ALL_SELECTOR

bytes4 public constant SET_APPROVAL_FOR_ALL_SELECTOR = bytes4(keccak256("setApprovalForAll(address,bool)"))

MULTISEND_HEADER_LENGTH

uint256 internal constant MULTISEND_HEADER_LENGTH = 85

ALLOWANCE_READ_GAS

uint64 internal constant ALLOWANCE_READ_GAS = 500_000

moduleExecutionEnabled

bool public immutable moduleExecutionEnabled

State Variables

targetPolicies

TargetPolicy[] public targetPolicies

selectorPolicies

SelectorPolicy[] public selectorPolicies

batchExecutorPolicies

BatchExecutorPolicy[] public batchExecutorPolicies

approvalPolicies

ApprovalPolicy[] public approvalPolicies

allowedModules

address[] public allowedModules

_targetPolicyIndexPlusOne

mapping(address target => uint256 indexPlusOne) internal _targetPolicyIndexPlusOne

_selectorPolicyIndexPlusOne

mapping(address target => mapping(bytes4 selector => uint256 indexPlusOne)) internal _selectorPolicyIndexPlusOne

_batchPolicyIndexPlusOne

mapping(address executor => mapping(bytes4 selector => uint256 indexPlusOne)) internal _batchPolicyIndexPlusOne

_approvalPolicyByKey

mapping(address token => mapping(address spender => mapping(uint8 kind => ApprovalPolicy))) internal
    _approvalPolicyByKey

_approvalPolicyExists

mapping(address token => mapping(address spender => mapping(uint8 kind => bool))) internal _approvalPolicyExists

_tokenApprovalKindRegistered

mapping(address token => mapping(uint8 kind => bool)) internal _tokenApprovalKindRegistered

_allowedModule

mapping(address module => bool allowed) internal _allowedModule

Functions

constructor

constructor(
    TargetPolicy[] memory targetPolicies_,
    SelectorPolicy[] memory selectorPolicies_,
    BatchExecutorPolicy[] memory batchExecutorPolicies_,
    ApprovalPolicy[] memory approvalPolicies_,
    bool moduleExecutionEnabled_,
    address[] memory allowedModules_
) ;

targetPolicyCount

function targetPolicyCount() external view returns (uint256);

selectorPolicyCount

function selectorPolicyCount() external view returns (uint256);

batchExecutorPolicyCount

function batchExecutorPolicyCount() external view returns (uint256);

approvalPolicyCount

function approvalPolicyCount() external view returns (uint256);

allowedModuleCount

function allowedModuleCount() external view returns (uint256);

_triggeredAction

function _triggeredAction() internal view returns (Action memory action);

_validateInnerDelegateCallPolicy

function _validateInnerDelegateCallPolicy(Action memory action) internal pure;

_validateInnerTargetSelectorPolicy

function _validateInnerTargetSelectorPolicy(Action memory action) internal view;

_validateInnerApprovalPolicy

function _validateInnerApprovalPolicy(Action memory action) internal view;

_validateMultiSendDelegateCallPolicy

function _validateMultiSendDelegateCallPolicy(Action memory action, BatchExecutorPolicy storage batchPolicy)
    internal
    view;

_validateMultiSendTargetSelectorPolicy

function _validateMultiSendTargetSelectorPolicy(Action memory action, BatchExecutorPolicy storage batchPolicy)
    internal
    view;

_validateMultiSendBatchPolicy

function _validateMultiSendBatchPolicy(Action memory action, BatchExecutorPolicy storage batchPolicy)
    internal
    view;

_validateMultiSendApprovalPolicy

function _validateMultiSendApprovalPolicy(Action memory action, BatchExecutorPolicy storage batchPolicy)
    internal
    view;

_multiSendTransactions

function _multiSendTransactions(Action memory action, BatchExecutorPolicy storage batchPolicy)
    internal
    view
    returns (uint256 transactionsOffset, uint256 transactionsLength);

_readMultiSendAction

function _readMultiSendAction(
    Action memory parent,
    uint256 transactionsOffset,
    uint256 transactionsLength,
    uint256 offset
) internal pure returns (Action memory innerAction, uint256 nextOffset);

_validateTargetAndSelector

function _validateTargetAndSelector(Action memory action) internal view returns (bytes4 selector);

_validateApproval

function _validateApproval(Action memory action, bytes4 selector) internal view;

_validateNumericApproval

function _validateNumericApproval(address token, address spender, uint8 kind, uint256 amount) internal view;

_validateIncreaseAllowanceFinalState

function _validateIncreaseAllowanceFinalState(address safe, address token, address spender) internal view;

_validateOperatorApproval

function _validateOperatorApproval(address token, address operator, uint8 kind) internal view;

_operatorApprovalAllowed

function _operatorApprovalAllowed(address token, address operator, uint8 kind) internal view returns (bool);

_validateModuleCaller

function _validateModuleCaller(address module) internal view;

_resolveTriggeredSafeCall

function _resolveTriggeredSafeCall() internal view returns (TriggeredSafeCall memory triggered);

_decodeOwnerTx

function _decodeOwnerTx(bytes memory input) internal pure returns (OwnerTx memory ownerTx);

_decodeModuleTx

function _decodeModuleTx(bytes memory input) internal pure returns (ModuleTx memory moduleTx);

_decodeOwnerAction

function _decodeOwnerAction(address safe, bytes memory input) internal pure returns (Action memory action);

_decodeModuleAction

function _decodeModuleAction(address safe, address module, bytes memory input)
    internal
    pure
    returns (Action memory action);

_decodeSingleBytesArgument

function _decodeSingleBytesArgument(
    bytes memory input,
    uint256 inputOffset,
    uint256 inputLength,
    bytes4 expectedSelector
) internal pure returns (uint256 argumentOffset, uint256 argumentLength);

_batchPolicyForAction

function _batchPolicyForAction(address target, bytes memory data, uint256 dataOffset, uint256 dataLength)
    internal
    view
    returns (bool found, uint256 index);

_isConfiguredBatchCall

function _isConfiguredBatchCall(address target, bytes memory data, uint256 dataOffset, uint256 dataLength)
    internal
    view
    returns (bool);

_targetPolicyIndex

function _targetPolicyIndex(address target) internal view returns (bool found, uint256 index);

_selectorPolicyIndex

function _selectorPolicyIndex(address target, bytes4 selector) internal view returns (bool found, uint256 index);

_batchPolicyIndex

function _batchPolicyIndex(address executor, bytes4 selector) internal view returns (bool found, uint256 index);

_tokenHasApprovalKind

function _tokenHasApprovalKind(address token, uint8 kind) internal view returns (bool);

_storeTargetPolicies

function _storeTargetPolicies(TargetPolicy[] memory policies) private;

_storeSelectorPolicies

function _storeSelectorPolicies(SelectorPolicy[] memory policies) private;

_storeBatchExecutorPolicies

function _storeBatchExecutorPolicies(BatchExecutorPolicy[] memory policies) private;

_storeApprovalPolicies

function _storeApprovalPolicies(ApprovalPolicy[] memory policies) private;

_storeAllowedModules

function _storeAllowedModules(address[] memory modules) private;

_targetPolicyExistsInMemory

function _targetPolicyExistsInMemory(address target) private view returns (bool);

_isSupportedApprovalKind

function _isSupportedApprovalKind(uint8 kind) private pure returns (bool);

_registerReshiramSpec

function _registerReshiramSpec() internal;

_stripSelector

function _stripSelector(bytes memory input) internal pure returns (bytes memory args);

_selector

function _selector(bytes memory input) internal pure returns (bytes4 selector);

_selectorAt

function _selectorAt(bytes memory input, uint256 offset) internal pure returns (bytes4 selector);

_readAbiAddress

function _readAbiAddress(bytes memory data, uint256 offset) internal pure returns (address value);

_readAbiUint8

function _readAbiUint8(bytes memory data, uint256 offset) internal pure returns (uint8 value);

_readAbiBool

function _readAbiBool(bytes memory data, uint256 offset) internal pure returns (bool value);

_readDynamicBytes

function _readDynamicBytes(bytes memory data, uint256 headStart, uint256 relativeOffset)
    internal
    pure
    returns (bytes memory value);

_dynamicBytesBounds

function _dynamicBytesBounds(bytes memory data, uint256 headStart, uint256 relativeOffset)
    internal
    pure
    returns (uint256 valueOffset, uint256 valueLength);

_readPackedAddress

function _readPackedAddress(bytes memory data, uint256 offset) internal pure returns (address value);

_readUint256

function _readUint256(bytes memory data, uint256 offset) internal pure returns (uint256 value);

_slice

function _slice(bytes memory data, uint256 offset, uint256 length) internal pure returns (bytes memory out);

Errors

SafeTxShapeDuplicateTarget

error SafeTxShapeDuplicateTarget(address target);

SafeTxShapeDuplicateSelector

error SafeTxShapeDuplicateSelector(address target, bytes4 selector);

SafeTxShapeDuplicateBatchExecutor

error SafeTxShapeDuplicateBatchExecutor(address executor, bytes4 selector);

SafeTxShapeDuplicateApprovalPolicy

error SafeTxShapeDuplicateApprovalPolicy(address token, address spender, uint8 kind);

SafeTxShapeDuplicateModule

error SafeTxShapeDuplicateModule(address module);

SafeTxShapeInvalidPolicy

error SafeTxShapeInvalidPolicy();

SafeTxShapeTriggeredCallNotFound

error SafeTxShapeTriggeredCallNotFound(bytes4 selector, uint256 callStart);

SafeTxShapeUnsupportedEntrypoint

error SafeTxShapeUnsupportedEntrypoint(bytes4 selector);

SafeTxShapeModuleExecutionDisabled

error SafeTxShapeModuleExecutionDisabled(address module);

SafeTxShapeModuleNotAllowed

error SafeTxShapeModuleNotAllowed(address module);

SafeTxShapeUnknownOperation

error SafeTxShapeUnknownOperation(uint8 operation);

SafeTxShapeDelegateCallBlocked

error SafeTxShapeDelegateCallBlocked(address target);

SafeTxShapeInnerDelegateCallBlocked

error SafeTxShapeInnerDelegateCallBlocked(address target);

SafeTxShapeUnknownTarget

error SafeTxShapeUnknownTarget(address target);

SafeTxShapeSelectorNotAllowed

error SafeTxShapeSelectorNotAllowed(address target, bytes4 selector);

SafeTxShapeCalldataTooShort

error SafeTxShapeCalldataTooShort(address target, uint256 length);

SafeTxShapeEmptyCalldataBlocked

error SafeTxShapeEmptyCalldataBlocked(address target);

SafeTxShapeFallbackCalldataBlocked

error SafeTxShapeFallbackCalldataBlocked(address target, uint256 length);

SafeTxShapeNativeValueBlocked

error SafeTxShapeNativeValueBlocked(address target, bytes4 selector, uint256 value);

SafeTxShapeBatchDelegateCallNotAllowed

error SafeTxShapeBatchDelegateCallNotAllowed(address executor);

SafeTxShapeBatchPayloadMalformed

error SafeTxShapeBatchPayloadMalformed();

SafeTxShapeBatchTooManyActions

error SafeTxShapeBatchTooManyActions(uint256 maxActions);

SafeTxShapeNestedBatchBlocked

error SafeTxShapeNestedBatchBlocked(address executor);

SafeTxShapeApprovalMalformed

error SafeTxShapeApprovalMalformed(address token, bytes4 selector);

SafeTxShapeApprovalTokenUnconfigured

error SafeTxShapeApprovalTokenUnconfigured(address token, bytes4 selector);

SafeTxShapeApprovalSpenderNotAllowed

error SafeTxShapeApprovalSpenderNotAllowed(address token, address spender, uint8 kind);

SafeTxShapeApprovalUnlimitedBlocked

error SafeTxShapeApprovalUnlimitedBlocked(address token, address spender, uint8 kind);

SafeTxShapeApprovalAmountAboveCap

error SafeTxShapeApprovalAmountAboveCap(
    address token, address spender, uint8 kind, uint256 amount, uint256 maxAmount
);

SafeTxShapeAllowanceReadFailed

error SafeTxShapeAllowanceReadFailed(address token, address spender);

Structs

TargetPolicy

struct TargetPolicy {
    address target;
    bool allowAnySelector;
    bool allowEmptyCalldata;
    bool allowFallbackCalldata;
    bool allowNonzeroValue;
}

SelectorPolicy

struct SelectorPolicy {
    address target;
    bytes4 selector;
    bool allowNonzeroValue;
}

BatchExecutorPolicy

struct BatchExecutorPolicy {
    address executor;
    bytes4 selector;
    bool allowDelegateCall;
    uint256 maxActions;
    bool allowNested;
}

ApprovalPolicy

struct ApprovalPolicy {
    address token;
    address spender;
    uint8 kind;
    uint256 maxAmount;
    bool allowUnlimited;
}

TriggeredSafeCall

struct TriggeredSafeCall {
    bytes4 selector;
    address caller;
    bytes input;
    uint256 callStart;
    uint256 callEnd;
}

OwnerTx

struct OwnerTx {
    address to;
    uint256 value;
    bytes data;
    uint8 operation;
}

ModuleTx

struct ModuleTx {
    address to;
    uint256 value;
    bytes data;
    uint8 operation;
}

Action

struct Action {
    address safe;
    address module;
    address target;
    uint256 value;
    bytes data;
    uint256 dataOffset;
    uint256 dataLength;
    uint8 operation;
    bool fromModule;
    bool fromBatch;
}

Contents

Contents

CowSettlementAssertion

Git Source

Inherits: CowSettlementHelpers

Title: CowSettlementAssertion

Author: Phylax Systems

Example assertion bundle protecting a CoW Protocol (Gnosis Protocol v2) settlement contract against the two on-chain harms a malicious (or compromised) solver can cause that the settlement contract does NOT guard against itself.

The GPv2 settlement contract gates settle/swap behind an allowlist of bonded solvers, enforces user signatures, limit prices, fill amounts and expiry, and forbids interactions with the vault relayer. What it intentionally leaves open — solvers may run arbitrary interactions and may dip into the contract’s own accumulated buffers — is exactly what these two invariants cover. Neither uses a price oracle; both observe real token movements.

  1. Surplus protection (assertSolverDoesNotExtractValue, per settle/swap call): the settlement contract must not pay any of its tokens to the solver that authored the call. Solver rewards are paid out-of-band in COW; a settlement that transfers tokens to its own caller is siphoning batch surplus / buffer value that belongs to users or the DAO.
  2. Inventory protection (assertBufferConserved, per watched-token balance change): the settlement contract’s watched-token outflow must be explained by authorized DAO sweeps or by actual GPv2 Trade volume emitted by the settlement. This catches buffer drains that are not settlement volume, including the standing-approval class behind the February 2023 incident, while allowing normal settlements to use accumulated inventory. Limitations (documented intentionally, not bugs): without a reference price the bundle cannot judge whether surplus was “fairly” maximized, only that it did not flow to the solver; the surplus check assumes the solver address is not itself an order receiver in the same batch; and Trade events do not expose receivers, so the buffer check reconciles settlement volume rather than receiver-level authorization.

Functions

constructor

constructor(
    address settlement_,
    address sweepRecipient_,
    address[] memory bufferTokens_,
    uint256 bufferToleranceBps_
) CowSettlementHelpers(settlement_, sweepRecipient_, bufferTokens_, bufferToleranceBps_);

triggers

Registers the surplus check against the solver-only entry points, and the buffer conservation check at transaction end and on watched-token balance changes.

The surplus check is call-scoped (it needs the per-call solver and the call’s Transfer logs). The buffer check is a transaction-envelope property, so it runs at tx end and on ERC20 balance changes for every watched token. The ERC20-change trigger lets the check fire for a drain transaction that never calls the settlement contract directly.

function triggers() external view override;

assertSolverDoesNotExtractValue

A settlement must not pay batch surplus to the solver that authored it.

Solvers are compensated off-chain in COW, never by the settlement contract transferring traded tokens back to its own caller. Any token (across the whole call frame, including nested solver interactions) moved from the settlement contract to the solver is treated as siphoned surplus / buffer value and trips the assertion. A failure means the solver directed value that should have reached users (as price improvement) or stayed in the DAO buffer to itself instead. This is the on-chain footprint of surplus theft that the settlement’s signature / limit-price checks cannot catch, because those only guarantee the user’s signed floor, not where any excess goes.

function assertSolverDoesNotExtractValue() external view;

assertBufferConserved

The settlement contract’s watched-token outflow must be explained by GPv2 settlement volume or an authorized DAO sweep.

Reconciles both gross ERC20 outflow and net PreTx/PostTx balance decrease for every watched token. Normal settlements can legitimately use accumulated inventory, so GPv2 Trade event volume is allowed. Authorized sweeps to the configured recipient are also allowed. Any remaining outflow — including an external contract exploiting a standing approval on the settlement’s balance — trips the assertion.

function assertBufferConserved() external view;

CowSettlementHelpers

Git Source

Inherits: Assertion

Title: CowSettlementHelpers

Author: Phylax Systems

Fork-aware, log-aware helpers for the CoW Protocol (GPv2) settlement assertions.

The assertion adopter is the GPv2Settlement contract. These helpers wire up the bundle’s immutable configuration and provide the two observations the invariants need:

  • the solver that authored a settlement call (the call’s caller), and
  • the value the settlement contract moves out to a given recipient (via Transfer logs and ERC20 balance deltas). All reads go through snapshot forks / call-scoped logs; the bundle keeps no assertion-owned state of its own.

Constants

ERC20_TRANSFER_TOPIC

keccak256(“Transfer(address,address,uint256)”) — standard ERC20 Transfer topic0.

bytes32 internal constant ERC20_TRANSFER_TOPIC = keccak256("Transfer(address,address,uint256)")

GPV2_TRADE_TOPIC

keccak256(“Trade(address,address,address,uint256,uint256,uint256,bytes)”) from GPv2Settlement.

bytes32 internal constant GPV2_TRADE_TOPIC =
    keccak256("Trade(address,address,address,uint256,uint256,uint256,bytes)")

BPS

Basis-points denominator.

uint256 internal constant BPS = 10_000

SETTLEMENT

The GPv2 settlement contract this bundle protects (the assertion adopter).

address internal immutable SETTLEMENT

SWEEP_RECIPIENT

The single authorized destination for buffer outflows (the CoW DAO reward/sweep Safe). Net reductions of the settlement’s buffer are only allowed when they land here.

address internal immutable SWEEP_RECIPIENT

BUFFER_TOLERANCE_BPS

Dust tolerance, in basis points of the pre-transaction buffer balance, allowed when reconciling a buffer reduction against authorized sweeps. Absorbs rounding / fee dust.

uint256 internal immutable BUFFER_TOLERANCE_BPS

State Variables

bufferTokens

The buffer tokens whose settlement-held balances are protected (e.g. fee tokens that accumulate between DAO sweeps, such as the DAI buffer drained in the 2023 incident).

address[] internal bufferTokens

Functions

constructor

Configuration is passed in explicitly; the constructor never reads adopter state, since the assertion-deploy runtime is isolated from the calling state.

constructor(
    address settlement_,
    address sweepRecipient_,
    address[] memory bufferTokens_,
    uint256 bufferToleranceBps_
) ;

_requireSettlementIsAdopter

Reverts unless the configured settlement is the adopter for the current transaction.

function _requireSettlementIsAdopter() internal view;

_solver

Returns the solver that authored the settlement call.

GPv2’s onlySolver authenticates msg.sender, not tx.origin, so the protected counterparty is the immediate caller of the triggered settlement entry point.

function _solver(bytes4 selector, uint256 callId) internal view returns (address);

_valueFromSettlementTo

Sums the value of every ERC20 Transfer emitted inside callId that moves tokens from the settlement contract to recipient, across all tokens.

Scans standard 3-topic ERC20 Transfer events in the call frame (including nested calls, so value routed out through a solver interaction is still observed).

function _valueFromSettlementTo(uint256 callId, address recipient) internal view returns (uint256 total);

_reportedTradeVolume

Returns token volume that GPv2 itself reported as executed for the watched token.

Sums both sell and buy legs from GPv2 Trade events so normal settlement inventory movements do not trip the buffer assertion. The event does not identify the receiver, so this is a volume allowance rather than recipient-level authorization.

function _reportedTradeVolume(address token) internal view returns (uint256 volume);

_transferredValueFrom

Returns total standard ERC20 outflow from from for one token over the transaction.

function _transferredValueFrom(address token, address from) internal view returns (uint256 value);

_saturatingAdd

Saturating sum for small allowance sets used in reconciliation.

function _saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256);

IGPv2SettlementLike

Git Source

Title: IGPv2SettlementLike

Author: Phylax Systems

Minimal CoW Protocol (Gnosis Protocol v2) settlement surface needed by the example assertion bundle.

The structs and function signatures mirror cowprotocol/contracts (GPv2Settlement.settle, GPv2Settlement.swap, GPv2Trade.Data, GPv2Interaction.Data) byte-for-byte, so the selectors derived here match the production settlement contract on mainnet. The bundle only needs the selectors for settle/swap triggers — it does not decode the full trade tuple — but the exact ABI is kept so the calldata shape and selectors match real on-chain solver settlements. The canonical selector strings are pinned in the bundle’s test.

Functions

settle

Batch settlement entry point. Solver-only on the real contract.

function settle(
    address[] calldata tokens,
    uint256[] calldata clearingPrices,
    TradeData[] calldata trades,
    InteractionData[][3] calldata interactions
) external;

swap

Single-order Balancer fast-path. Solver-only on the real contract.

function swap(BatchSwapStep[] calldata swaps, address[] calldata tokens, TradeData calldata trade) external;

vaultRelayer

The vault relayer that holds user approvals. Never a legitimate value recipient.

function vaultRelayer() external view returns (address);

Structs

TradeData

Mirror of GPv2Trade.Data. Token addresses are encoded as indices into the settlement’s tokens array; amounts and flags are the signed order terms.

struct TradeData {
    uint256 sellTokenIndex;
    uint256 buyTokenIndex;
    address receiver;
    uint256 sellAmount;
    uint256 buyAmount;
    uint32 validTo;
    bytes32 appData;
    uint256 feeAmount;
    uint256 flags;
    uint256 executedAmount;
    bytes signature;
}

InteractionData

Mirror of GPv2Interaction.Data: an arbitrary call the solver asks the settlement contract to make. The fully arbitrary target/value/callData here is exactly the power that lets a solver move the settlement contract’s own buffers and grant approvals.

struct InteractionData {
    address target;
    uint256 value;
    bytes callData;
}

BatchSwapStep

Mirror of Balancer’s IVault.BatchSwapStep, used only by the swap fast-path.

struct BatchSwapStep {
    bytes32 poolId;
    uint256 assetInIndex;
    uint256 assetOutIndex;
    uint256 amount;
    bytes userData;
}

IERC20BalanceLike

Git Source

Title: IERC20BalanceLike

Minimal ERC20 balance surface for snapshot reads.

Functions

balanceOf

function balanceOf(address account) external view returns (uint256);

UniswapV4PoolManagerAssertion

Git Source

Inherits: UniswapV4PoolManagerHelpers

Title: UniswapV4PoolManagerAssertion

Author: Phylax Systems

Example assertion bundle for a single Uniswap v4 pool sitting inside the singleton PoolManager.

Protects the pool’s core AMM invariants and the manager-level custody invariants:

  • swaps move price only in the requested direction and never past the caller’s price limit;
  • modifyLiquidity calls update active liquidity exactly when the position range contains the current tick;
  • protocol-fee accruals stay backed by the manager’s currency balances;
  • protocol-fee collection does not mutate any of the watched pool’s swap-critical state. Because the PoolManager is shared across every v4 pool, each call-scoped trigger must check that the call’s PoolKey matches the configured pool before evaluating the per-pool invariants. Calls to other pools no-op silently. The example intentionally combines per-pool state checks with manager-level custody checks:
  • per-pool checks protect the configured pool’s price, tick, liquidity, and fee-growth state;
  • manager-level checks protect the singleton accounting that backs protocol-fee liabilities for the currencies used by the configured pool;
  • PoolKey filtering prevents activity in unrelated pools from producing false positives while still letting one assertion deployment monitor a specific pool inside the singleton.

Functions

constructor

constructor(address manager_, IUniswapV4PoolManagerLike.PoolKey memory poolKey_)
    UniswapV4PoolManagerHelpers(manager_, poolKey_);

triggers

Registers Uniswap v4 PoolManager selectors against their protection assertions.

The PoolManager is the assertion adopter. Call-scoped triggers compare the exact pre-call and post-call snapshots for the matched manager operation. The assertion functions that receive a PoolKey return early when the call targets a different pool.

function triggers() external view override;

_registerProtocolFeeCustodyTriggers

Registers the tx-end protocol-fee custody check.

Fee custody is global per currency in v4, and swap/donate calls may accrue fees before the surrounding unlock flow settles token custody. Checking at tx end avoids tripping on ordinary mid-flow accounting while still requiring final protocol-fee liabilities to be backed by PoolManager custody for ERC-20 currencies.

function _registerProtocolFeeCustodyTriggers() internal view;

assertSwapPriceMovement

A successful swap must respect direction and caller-supplied price limits.

For zeroForOne swaps sqrtPriceX96 can only decrease; for oneForZero swaps it can only increase. The post-call price must also stay strictly inside V4’s tick-range bounds. A failure means swap execution moved price the wrong way or crossed the explicit limit that bounds user execution. This protects the user-facing swap guarantee rather than duplicating v4’s calldata validation.

function assertSwapPriceMovement() external view;

assertActiveLiquidityAccounting

modifyLiquidity must update active liquidity exactly for in-range positions.

V4’s per-pool liquidity is only the currently active liquidity. A successful modifyLiquidity whose range excludes the pre-call tick must leave it unchanged; an in-range modifyLiquidity must shift it by liquidityDelta. A failure means active liquidity no longer reflects the position range that the swap engine will use. Slot0 (price + tick) must also be unchanged because modifyLiquidity is not allowed to move the pool. This catches accounting corruption that may not be visible until a later swap prices against the wrong active liquidity.

function assertActiveLiquidityAccounting() external view;

assertProtocolFeesCoveredByCustody

Manager currency balances must keep covering accrued protocol fees.

Swaps and donates can accrue protocol fees before the surrounding unlock flow settles token custody, while collectProtocolFees withdraws them. The PoolManager singleton holds tokens for every pool, but protocolFeesAccrued is summed per currency across all pools, so the invariant manager.balanceOf(currency) >= protocolFeesAccrued(currency) must hold globally at transaction end for ERC-20 currencies used by the configured pool.

function assertProtocolFeesCoveredByCustody() external view;

assertCollectProtocolPreservesPoolState

Protocol-fee collection must not mutate swap-critical pool state.

collectProtocolFees may only reduce protocolFeesAccrued[currency] and transfer the matching token custody. A failure means owner fee collection changed the configured pool’s price, tick, fee schedule, active liquidity, or fee growth, or the protocol-fee accounting and manager balance change disagreed for the targeted currency. Calls collecting a currency that is not part of the watched pool only assert the per-pool no-mutation invariant. This protects against singleton side effects where a fee withdrawal for one currency unexpectedly contaminates the watched pool.

function assertCollectProtocolPreservesPoolState() external view;

_requireCollectProtocolCustodyMatches

When amount == 0, V4 collects the full accrued amount for the currency. We treat the actual delta as the amount taken and require the manager’s balance to drop by the same amount.

function _requireCollectProtocolCustodyMatches(
    uint256 requestedAmount,
    uint256 preAccrued,
    uint256 postAccrued,
    uint256 preBalance,
    uint256 postBalance,
    string memory currencyTag,
    bool isNativeCurrency
) internal pure;

_msg

function _msg(string memory base, string memory tag) internal pure returns (string memory);

UniswapV4PoolManagerHelpers

Git Source

Inherits: Assertion

Title: UniswapV4PoolManagerHelpers

Author: Phylax Systems

Fork-aware Uniswap v4 PoolManager state helpers used by the example assertions.

V4 stores every pool’s state inside the singleton PoolManager at slot 6 (mapping(PoolId => Pool.State) _pools). For each watched pool we compute the per-pool base storage slot once at construction and then read the packed Slot0, liquidity, and fee growth via the manager’s extsload(bytes32) precompile. Protocol-fee accruals are tracked globally per currency on the manager, not per pool.

Constants

MIN_SQRT_PRICE

uint160 internal constant MIN_SQRT_PRICE = 4_295_128_739

MAX_SQRT_PRICE

uint160 internal constant MAX_SQRT_PRICE = 1_461_446_703_485_210_103_287_273_052_203_988_822_378_723_970_342

POOLS_SLOT

Storage slot of mapping(PoolId => Pool.State) _pools on the PoolManager. Mirrors StateLibrary.POOLS_SLOT from v4-core.

uint256 internal constant POOLS_SLOT = 6

FEE_GROWTH_GLOBAL0_OFFSET

Per-pool offsets inside Pool.State. Mirror StateLibrary constants in v4-core.

uint256 internal constant FEE_GROWTH_GLOBAL0_OFFSET = 1

FEE_GROWTH_GLOBAL1_OFFSET

uint256 internal constant FEE_GROWTH_GLOBAL1_OFFSET = 2

LIQUIDITY_OFFSET

uint256 internal constant LIQUIDITY_OFFSET = 3

MANAGER

address internal immutable MANAGER

CURRENCY0

address internal immutable CURRENCY0

CURRENCY1

address internal immutable CURRENCY1

POOL_ID

bytes32 internal immutable POOL_ID

POOL_STATE_BASE_SLOT

bytes32 internal immutable POOL_STATE_BASE_SLOT

Functions

constructor

Accepts the manager and the full PoolKey explicitly so the constructor never reads from the adopter. The Credible Layer assertion-deploy runtime is isolated from the calling state, so a manager.extsload(...) call in the constructor would revert with EXTCODESIZE = 0.

constructor(address manager_, IUniswapV4PoolManagerLike.PoolKey memory poolKey_) ;

_snapshotAt

function _snapshotAt(PhEvm.ForkId memory fork) internal view returns (PoolSnapshot memory snapshot);

_slot0At

function _slot0At(PhEvm.ForkId memory fork) internal view returns (Slot0Snapshot memory slot0);

_liquidityAt

function _liquidityAt(PhEvm.ForkId memory fork) internal view returns (uint128 liquidity);

_feeGrowthGlobalsAt

function _feeGrowthGlobalsAt(PhEvm.ForkId memory fork) internal view returns (uint256 g0, uint256 g1);

_protocolFeesAccruedAt

function _protocolFeesAccruedAt(address currency, PhEvm.ForkId memory fork) internal view returns (uint256);

_currencyBalanceAt

function _currencyBalanceAt(address currency, address account, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256);

_isNativeCurrency

function _isNativeCurrency(address currency) internal pure returns (bool);

_extsloadAt

function _extsloadAt(bytes32 slot, PhEvm.ForkId memory fork) internal view returns (bytes32);

_offsetSlot

function _offsetSlot(uint256 offset) internal view returns (bytes32);

_swapArgs

function _swapArgs(bytes memory input)
    internal
    pure
    returns (
        IUniswapV4PoolManagerLike.PoolKey memory key,
        IUniswapV4PoolManagerLike.SwapParams memory params,
        bytes memory hookData
    );

_modifyLiquidityArgs

function _modifyLiquidityArgs(bytes memory input)
    internal
    pure
    returns (
        IUniswapV4PoolManagerLike.PoolKey memory key,
        IUniswapV4PoolManagerLike.ModifyLiquidityParams memory params,
        bytes memory hookData
    );

_collectProtocolFeesArgs

function _collectProtocolFeesArgs(bytes memory input)
    internal
    pure
    returns (address recipient, address currency, uint256 amount);

_inRange

function _inRange(int24 currentTick, int24 tickLower, int24 tickUpper) internal pure returns (bool);

_matchesConfiguredPool

function _matchesConfiguredPool(IUniswapV4PoolManagerLike.PoolKey memory key) internal view returns (bool);

_requireConfiguredManagerIsAdopter

function _requireConfiguredManagerIsAdopter() internal view;

_args

function _args(bytes memory input) internal pure returns (bytes memory args);

Structs

Slot0Snapshot

struct Slot0Snapshot {
    uint160 sqrtPriceX96;
    int24 tick;
    uint24 protocolFee;
    uint24 lpFee;
}

PoolSnapshot

struct PoolSnapshot {
    Slot0Snapshot slot0;
    uint128 liquidity;
    uint256 feeGrowthGlobal0X128;
    uint256 feeGrowthGlobal1X128;
    uint256 protocolFeesAccrued0;
    uint256 protocolFeesAccrued1;
    uint256 managerBalance0;
    uint256 managerBalance1;
}

IUniswapV4PoolManagerLike

Git Source

Title: IUniswapV4PoolManagerLike

Author: Phylax Systems

Minimal Uniswap v4 PoolManager surface needed by the example assertion bundle.

Currency and IHooks are typed address here. The canonical Uniswap v4 ABI encodes both as address, so the function selectors derived from these signatures match the production PoolManager exactly.

Functions

initialize

function initialize(PoolKey calldata key, uint160 sqrtPriceX96) external returns (int24 tick);

modifyLiquidity

function modifyLiquidity(PoolKey calldata key, ModifyLiquidityParams calldata params, bytes calldata hookData)
    external
    returns (int256 callerDelta, int256 feesAccrued);

swap

function swap(PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
    external
    returns (int256 swapDelta);
function donate(PoolKey calldata key, uint256 amount0, uint256 amount1, bytes calldata hookData)
    external
    returns (int256 delta);

take

function take(address currency, address to, uint256 amount) external;

settle

function settle() external payable returns (uint256);

sync

function sync(address currency) external;

mint

function mint(address to, uint256 id, uint256 amount) external;

burn

function burn(address from, uint256 id, uint256 amount) external;

updateDynamicLPFee

function updateDynamicLPFee(PoolKey calldata key, uint24 newDynamicLPFee) external;

setProtocolFee

function setProtocolFee(PoolKey calldata key, uint24 newProtocolFee) external;

setProtocolFeeController

function setProtocolFeeController(address controller) external;

collectProtocolFees

function collectProtocolFees(address recipient, address currency, uint256 amount) external returns (uint256);

unlock

function unlock(bytes calldata data) external returns (bytes memory);

protocolFeesAccrued

function protocolFeesAccrued(address currency) external view returns (uint256);

extsload

function extsload(bytes32 slot) external view returns (bytes32);

extsload

function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory);

Structs

PoolKey

struct PoolKey {
    address currency0;
    address currency1;
    uint24 fee;
    int24 tickSpacing;
    address hooks;
}

SwapParams

struct SwapParams {
    bool zeroForOne;
    int256 amountSpecified;
    uint160 sqrtPriceLimitX96;
}

ModifyLiquidityParams

struct ModifyLiquidityParams {
    int24 tickLower;
    int24 tickUpper;
    int256 liquidityDelta;
    bytes32 salt;
}

Contents

Contents

MetaMorphoVaultAssertion

Git Source

Inherits: ERC4626SharePriceAssertion, ERC4626PreviewAssertion, ERC4626CumulativeOutflowAssertion

Title: MetaMorphoVaultAssertion

Author: Phylax Systems

Example ERC-4626 assertion bundle for MetaMorpho vaults.

MetaMorpho vaults typically deploy deposited assets into Morpho Blue markets, so vault-held ERC-20 balance does not need to equal totalAssets(). This bundle intentionally omits ERC4626AssetFlowAssertion and keeps the ERC-4626 checks that fit computed-asset vaults: share price, preview consistency, and cumulative underlying outflow. The three inherited assertions cover different failure modes:

  • per-call share price protects existing depositors from dilution during deposit, mint, withdraw, or redeem calls;
  • preview consistency checks that the pre-call ERC-4626 quote matches the shares/assets actually returned by the triggered call;
  • cumulative outflow acts as a rolling-window breaker for vault asset exits that are each individually valid but collectively risky. A failure points to an externally visible vault-accounting problem: holders were diluted, users received a result inconsistent with ERC-4626 previews, or the configured outflow budget was breached.

Functions

constructor

constructor(
    address vault_,
    address asset_,
    uint256 sharePriceToleranceBps_,
    uint256 outflowThresholdBps_,
    uint256 outflowWindowDuration_
)
    ERC4626BaseAssertion(vault_, asset_)
    ERC4626SharePriceAssertion(sharePriceToleranceBps_)
    ERC4626CumulativeOutflowAssertion(outflowThresholdBps_, outflowWindowDuration_);

Parameters

NameTypeDescription
vault_addressMetaMorpho vault instance whose selectors this bundle will monitor.
asset_addressUnderlying ERC-20 asset of the vault.
sharePriceToleranceBps_uint256Max share-price drift tolerated by ERC4626SharePriceAssertion, in basis points.
outflowThresholdBps_uint256Cumulative net-outflow limit as bps of TVL enforced by ERC4626CumulativeOutflowAssertion over the rolling window.
outflowWindowDuration_uint256Rolling window, in seconds, used by the outflow assertion.

triggers

Registers the ERC-4626 selectors against the MetaMorpho-safe assertion set.

The selector set comes from the inherited ERC-4626 protections. We do not register the asset-flow assertion because a healthy MetaMorpho vault can hold less on-hand USDC than totalAssets() while its funds are allocated into Morpho markets.

function triggers() external view override;

ERC4626AssetFlowAssertion

Git Source

Inherits: ERC4626BaseAssertion

Title: ERC4626AssetFlowAssertion

Author: Phylax Systems

Asserts that ERC-20 token movement and the vault’s internal asset accounting agree, and that fundamental share-token invariants hold. Invariants covered:

  • Token movement matches accounting: the change in totalAssets across the transaction equals the net ERC-20 flow into/out of the vault. This catches transfer-fee tokens, rebasing tokens, or accounting bugs where totalAssets drifts from reality.
  • Zero address never holds shares: balanceOf(address(0)) == 0 after every share-minting operation.

Uses V2 registerTxEndTrigger for tx-wide checks and registerFnCallTrigger + ph.context() for call-scoped checks.

Functions

_registerAssetFlowTriggers

Register the default trigger set for asset-flow invariants.

function _registerAssetFlowTriggers() internal view;

assertAssetFlowMatchesAccounting

Verifies the change in totalAssets across the tx matches the net ERC-20 flow.

function assertAssetFlowMatchesAccounting() external;

_netAssetFlow

Compute net ERC-20 flow into (+) or out of (-) the vault across the tx.

Override for vaults that deploy assets through adapters or external protocols. The override should include flows to/from all relevant addresses (vault + adapters).

function _netAssetFlow() internal view virtual returns (int256 netFlow);

assertZeroAddressHasNoShares

Verifies the zero address never holds vault shares.

Uses ph.context() to check at PostCall of the triggering call.

function assertZeroAddressHasNoShares() external;

ERC4626BaseAssertion

Git Source

Inherits: Assertion

Title: ERC4626BaseAssertion

Author: Phylax Systems

Base contract for ERC-4626 vault assertions (V2 syntax).

Provides vault-specific state accessors on top of the shared Assertion helpers. Inherit from this (and one or more invariant contracts), then implement triggers(). Example – combine share-price, preview, and outflow invariants:

contract MyVaultAssertion is ERC4626SharePriceAssertion, ERC4626PreviewAssertion, ERC4626CumulativeOutflowAssertion {
constructor(address _vault, address _asset)
ERC4626BaseAssertion(_vault, _asset)
ERC4626SharePriceAssertion(50) // 50 bps tolerance
ERC4626CumulativeOutflowAssertion(1_000, 24 hours) // 10% in 24h
{}
function triggers() external view override {
_registerSharePriceTriggers();
_registerPreviewTriggers();
_registerCumulativeOutflowTriggers();
}
}

Constants

vault

The ERC-4626 vault being monitored (assertion adopter).

address internal immutable vault

asset

The underlying ERC-20 asset of the vault.

address internal immutable asset

Functions

constructor

Accepts the asset address explicitly so the constructor never reads from the adopter. The Credible Layer’s assertion-deploy runtime is isolated from the calling test state, so a vault.asset() call inside the constructor would revert with EXTCODESIZE = 0.

constructor(address _vault, address _asset) ;

Parameters

NameTypeDescription
_vaultaddressThe ERC-4626 vault being monitored (assertion adopter).
_assetaddressThe vault’s underlying ERC-20 asset.

_totalAssetsAt

function _totalAssetsAt(PhEvm.ForkId memory fork) internal view returns (uint256);

_totalSupplyAt

function _totalSupplyAt(PhEvm.ForkId memory fork) internal view returns (uint256);

_shareBalanceAt

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

_assetBalanceAt

Uses the same balanceOf(address) selector — valid for any ERC-20.

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

ERC4626CumulativeOutflowAssertion

Git Source

Inherits: ERC4626BaseAssertion

Title: ERC4626CumulativeOutflowAssertion

Author: Phylax Systems

Circuit breaker that triggers when cumulative ERC-20 outflow from the vault exceeds a percentage threshold within a rolling time window. Invariant covered:

  • Cumulative outflow cap: the net outflow of the vault’s underlying asset must not exceed outflowThresholdBps of the TVL snapshot within a rolling outflowWindowDuration.

Uses watchCumulativeOutflow trigger registration — the executor handles all persistent state tracking, TVL snapshots, and threshold enforcement internally. The assertion function fires only when the threshold is breached. Override assertCumulativeOutflow for smart breaker logic (e.g. deposit/repay-only mode). The default implementation unconditionally reverts (hard breaker).

Constants

outflowThresholdBps

Maximum cumulative outflow as basis points of the TVL snapshot. 1000 = 10%.

uint256 public immutable outflowThresholdBps

outflowWindowDuration

Rolling window length in seconds.

uint256 public immutable outflowWindowDuration

Functions

constructor

constructor(uint256 _thresholdBps, uint256 _windowDuration) ;

_registerCumulativeOutflowTriggers

Register the cumulative outflow circuit breaker trigger.

Call this inside your triggers().

function _registerCumulativeOutflowTriggers() internal view;

assertCumulativeOutflow

Called when cumulative outflow exceeds the threshold.

Default is a hard breaker (unconditional revert). Override for smart breaker logic — e.g. allow deposits but block withdrawals using ph.outflowContext() and _matchingCalls().

function assertCumulativeOutflow() external virtual;

ERC4626PreviewAssertion

Git Source

Inherits: ERC4626BaseAssertion

Title: ERC4626PreviewAssertion

Author: Phylax Systems

Asserts that ERC-4626 preview functions are consistent with the actual results of the corresponding state-changing operations, and that rounding favors the vault. Invariants covered:

  • Preview consistency: for the same pre-state, previewDeposit(a) == shares minted by deposit(a) previewMint(s) == assets charged by mint(s) previewWithdraw(a) == shares burned by withdraw(a) previewRedeem(s) == assets returned by redeem(s)
  • Rounding direction (implicit in the inequality checks): previewDeposit rounds DOWN (returns fewer shares -> favors vault) previewMint rounds UP (returns more assets -> favors vault) previewWithdraw rounds UP (returns more shares -> favors vault) previewRedeem rounds DOWN (returns fewer assets -> favors vault)

Uses V2 registerFnCallTrigger + ph.context() for call-scoped triggers, ph.callinputAt() to read call arguments, and ph.callOutputAt() to read the actual return value — replacing the totalSupply/totalAssets delta inference from V1.

Functions

_registerPreviewTriggers

Register the default trigger set for preview-consistency invariants.

Each ERC-4626 operation gets its own assertion function via registerFnCallTrigger.

function _registerPreviewTriggers() internal view;

_maxPreviewDeviation

Maximum acceptable deviation between a preview result and the actual result.

Defaults to 1 (single-unit rounding). Override for vaults with wider rounding (e.g. multi-step rounding, fee chunking, or decimal normalization).

function _maxPreviewDeviation() internal view virtual returns (uint256);

assertDepositPreview

For the triggering deposit(assets, receiver) call, verifies: previewDeposit(assets) <= actualSharesMinted (ERC-4626 spec) actualSharesMinted - previewDeposit(assets) <= maxDeviation

function assertDepositPreview() external;

assertMintPreview

For the triggering mint(shares, receiver) call, verifies: previewMint(shares) >= actualAssetsCharged (ERC-4626 spec) previewMint(shares) - actualAssetsCharged <= maxDeviation

function assertMintPreview() external;

assertWithdrawPreview

For the triggering withdraw(assets, receiver, owner) call, verifies: previewWithdraw(assets) >= actualSharesBurned (ERC-4626 spec) previewWithdraw(assets) - actualSharesBurned <= maxDeviation

function assertWithdrawPreview() external;

assertRedeemPreview

For the triggering redeem(shares, receiver, owner) call, verifies: previewRedeem(shares) <= actualAssetsReturned (ERC-4626 spec) actualAssetsReturned - previewRedeem(shares) <= maxDeviation

function assertRedeemPreview() external;

_stripSelector

Strip the 4-byte selector from raw call input bytes.

function _stripSelector(bytes memory input) internal pure returns (bytes memory args);

ERC4626SharePriceAssertion

Git Source

Inherits: ERC4626BaseAssertion

Title: ERC4626SharePriceAssertion

Author: Phylax Systems

Asserts that the vault’s share price (totalAssets / totalSupply) does not decrease beyond a configurable tolerance, both transaction-wide and per individual user operation. Invariants covered:

  • Non-dilutive entry/exit: absent explicit fee accrual or loss recognition, deposit/mint/withdraw/redeem must not reduce assets-per-share for remaining holders.
  • Rounding favors incumbents: the share price must not move against the vault (i.e., existing holders) during ordinary user operations.

Uses the V2 assetsMatchSharePrice / assetsMatchSharePriceAt precompiles for the primary check, and ratioGe for an explicit cross-multiplication comparison as a second, readable signal. The tolerance is expressed in basis points (1 bps = 0.01%). A tolerance of 0 enforces strict non-decrease; values like 25-50 allow for rounding noise.

Constants

sharePriceToleranceBps

Maximum acceptable share-price decrease in basis points.

uint256 public immutable sharePriceToleranceBps

Functions

constructor

constructor(uint256 _toleranceBps) ;

_registerSharePriceTriggers

Register the default trigger set for share-price invariants.

Uses registerTxEndTrigger for the tx-wide envelope and registerFnCallTrigger for per-call checks. Call this inside your triggers().

function _registerSharePriceTriggers() internal view;

assertSharePriceEnvelope

Verifies the share price did not decrease beyond tolerance across the entire transaction.

Uses assetsMatchSharePrice for a comprehensive all-forks check, then ratioGe for an explicit pre/post comparison as a second signal.

function assertSharePriceEnvelope() external;

assertPerCallSharePrice

Verifies each individual deposit/mint/withdraw/redeem call does not decrease the share price beyond tolerance.

Uses ph.context() to get the triggering call boundaries and assetsMatchSharePriceAt for a targeted pre/post-call comparison.

function assertPerCallSharePrice() external;

IERC4626

Git Source

Title: IERC4626

Minimal ERC-4626 tokenized vault interface for assertion contracts.

Includes the ERC-20 view surface (totalSupply, balanceOf) since ERC-4626 extends ERC-20.

Functions

totalSupply

function totalSupply() external view returns (uint256);

balanceOf

function balanceOf(address account) external view returns (uint256);

asset

function asset() external view returns (address);

totalAssets

function totalAssets() external view returns (uint256);

convertToShares

function convertToShares(uint256 assets) external view returns (uint256);

convertToAssets

function convertToAssets(uint256 shares) external view returns (uint256);

previewDeposit

function previewDeposit(uint256 assets) external view returns (uint256);

previewMint

function previewMint(uint256 shares) external view returns (uint256);

previewWithdraw

function previewWithdraw(uint256 assets) external view returns (uint256);

previewRedeem

function previewRedeem(uint256 shares) external view returns (uint256);

maxDeposit

function maxDeposit(address receiver) external view returns (uint256);

maxMint

function maxMint(address receiver) external view returns (uint256);

maxWithdraw

function maxWithdraw(address owner) external view returns (uint256);

maxRedeem

function maxRedeem(address owner) external view returns (uint256);

deposit

function deposit(uint256 assets, address receiver) external returns (uint256 shares);

mint

function mint(uint256 shares, address receiver) external returns (uint256 assets);

withdraw

function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

redeem

function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

Contents

BacktestingTypes

Git Source

Title: BacktestingTypes

Author: Phylax Systems

Type definitions for the backtesting framework

Contains structs for configuration, transaction data, and results used by CredibleTestWithBacktesting

Structs

TransactionData

Transaction data from blockchain

struct TransactionData {
    bytes32 hash;
    address from;
    address to;
    uint256 value;
    bytes data;
    uint256 blockNumber;
    uint256 transactionIndex;
    uint256 gasPrice;
    uint256 gasLimit;
    uint256 maxFeePerGas;
    uint256 maxPriorityFeePerGas;
}

ValidationDetails

Detailed validation result with error information

struct ValidationDetails {
    ValidationResult result;
    string errorMessage;
    bool isProtocolViolation;
}

BacktestingConfig

Configuration for backtesting runs (block range mode)

Internal call detection is automatic - the system tries trace_filter first, then falls back to debug_traceBlockByNumber, debug_traceTransaction, and finally direct-calls-only if no trace methods are supported.

struct BacktestingConfig {
    address targetContract;
    uint256 endBlock;
    uint256 blockRange;
    bytes assertionCreationCode;
    bytes4 assertionSelector;
    string rpcUrl;
    bool detailedBlocks; // Enable detailed block summaries in output
    bool forkByTxHash; // Fork by transaction hash for correct pre-tx state; block forks are unsafe.
}

BacktestingResults

Enhanced backtesting results with detailed categorization

struct BacktestingResults {
    uint256 totalTransactions;
    uint256 processedTransactions; // Transactions that were actually processed
    uint256 successfulValidations;
    uint256 skippedTransactions; // Transactions where assertion wasn't triggered (selector mismatch)
    uint256 assertionFailures; // Real protocol violations
    uint256 replayFailures; // Transactions that reverted during replay before assertion
    uint256 unknownErrors; // Unexpected failures
}

Enums

ValidationResult

Validation result categories for detailed error analysis

enum ValidationResult {
    Success, // Transaction passed all assertions
    Skipped, // Transaction didn't trigger the assertion (function selector mismatch)
    ReplayFailure, // Transaction reverted during replay before assertion could execute
    AssertionFailed, // Assertion logic failed (actual protocol violation)
    UnknownError // Unexpected error during validation
}

BacktestingUtils

Git Source

Title: BacktestingUtils

Author: Phylax Systems

Utility functions for the backtesting framework

Provides parsing, string manipulation, and error decoding utilities used internally by CredibleTestWithBacktesting

Functions

extractDataLine

Extract transaction data from fetcher output

function extractDataLine(string memory output) internal pure returns (string memory);

_isWhitespace

Check if a character is whitespace

function _isWhitespace(bytes1 char) private pure returns (bool);

splitString

Simple pipe-delimited string splitter

function splitString(string memory str, string memory) internal pure returns (string[] memory);

stringToUint

Parse hex or decimal string to uint256

function stringToUint(string memory str) internal pure returns (uint256);

stringToAddress

Parse hex address string to address

function stringToAddress(string memory str) internal pure returns (address);

stringToBytes32

Parse hex string to bytes32

function stringToBytes32(string memory str) internal pure returns (bytes32);

hexStringToBytes

Parse hex string to bytes

function hexStringToBytes(string memory str) internal pure returns (bytes memory);

bytes32ToHex

Convert bytes32 to hex string

function bytes32ToHex(bytes32 data) internal pure returns (string memory);

extractFunctionSelector

Extract function selector from calldata

function extractFunctionSelector(bytes memory data) internal pure returns (string memory);

parseMultipleTransactions

Parse multiple transactions from a single data line

function parseMultipleTransactions(string memory txDataString)
    internal
    pure
    returns (BacktestingTypes.TransactionData[] memory transactions);

_hexCharToUint8

Convert hex character to uint8

function _hexCharToUint8(bytes1 char) private pure returns (uint8);

substring

Helper to get substring for debugging

function substring(string memory str, uint256 start, uint256 len) private pure returns (string memory);

decodeRevertReason

Decode revert reason from error data

function decodeRevertReason(bytes memory data) internal pure returns (string memory);

Parameters

NameTypeDescription
databytesThe error data from a failed call

Returns

NameTypeDescription
<none>stringThe decoded revert reason string

_panicCodeToString

Convert panic code to human-readable string

function _panicCodeToString(uint256 code) private pure returns (string memory);

bytesToHex

Convert bytes to hex string

function bytesToHex(bytes memory data) internal pure returns (bytes memory);

Parameters

NameTypeDescription
databytesThe bytes to convert

Returns

NameTypeDescription
<none>bytesThe hex string representation

getErrorTypeString

Get human-readable error type string from validation result

function getErrorTypeString(BacktestingTypes.ValidationResult result) internal pure returns (string memory);

Parameters

NameTypeDescription
resultBacktestingTypes.ValidationResultThe validation result enum

Returns

NameTypeDescription
<none>stringThe human-readable string representation

startsWith

Check if a string starts with a prefix

function startsWith(string memory str, string memory prefix) internal pure returns (bool);

Parameters

NameTypeDescription
strstringThe string to check
prefixstringThe prefix to look for

Returns

NameTypeDescription
<none>boolTrue if str starts with prefix

getDefaultScriptSearchPaths

Get the standard search paths for transaction_fetcher.sh

function getDefaultScriptSearchPaths() internal pure returns (string[] memory);

Returns

NameTypeDescription
<none>string[]Array of paths to check, in order of preference

IERC20BalanceReaderLike

Git Source

Minimal token balance surface used by fork-read helpers.

Functions

balanceOf

function balanceOf(address account) external view returns (uint256);

ForkUtils

Git Source

Inherits: Credible

Title: ForkUtils

Author: Phylax Systems

Shared fork-aware read and ERC20-delta helpers for assertions and protection suites.

Constants

FORK_VIEW_GAS

Gas forwarded to snapshot-time protocol view calls made through ph.staticcallAt.

uint64 internal constant FORK_VIEW_GAS = 500_000

Functions

_viewFailureMessage

Revert string used when a fork-time static call fails.

Override this when a more specific failure message is useful for a derived contract.

function _viewFailureMessage() internal pure virtual returns (string memory);

_viewAt

Executes a static call against target at a specific snapshot fork.

function _viewAt(address target, bytes memory data, PhEvm.ForkId memory fork)
    internal
    view
    returns (bytes memory resultData);

Parameters

NameTypeDescription
targetaddressThe protocol contract to query.
databytesABI-encoded calldata for the target view.
forkPhEvm.ForkIdThe snapshot fork the read should execute against.

Returns

NameTypeDescription
resultDatabytesRaw return bytes from the static call.

_readUintAt

Convenience wrapper that decodes a snapshot-time static call as uint256.

function _readUintAt(address target, bytes memory data, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256 value);

_readUint8At

Convenience wrapper that decodes a snapshot-time static call as uint8.

function _readUint8At(address target, bytes memory data, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint8 value);

_readAddressAt

Convenience wrapper that decodes a snapshot-time static call as address.

function _readAddressAt(address target, bytes memory data, PhEvm.ForkId memory fork)
    internal
    view
    returns (address value);

_readBoolAt

Convenience wrapper that decodes a snapshot-time static call as bool.

function _readBoolAt(address target, bytes memory data, PhEvm.ForkId memory fork)
    internal
    view
    returns (bool value);

_readBalanceAt

Convenience wrapper that reads an ERC20-style balanceOf(account) at a snapshot fork.

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

_reducedErc20BalanceDeltasAt

Returns reduced ERC20 transfer deltas for a token within the selected fork scope.

function _reducedErc20BalanceDeltasAt(address token, PhEvm.ForkId memory fork)
    internal
    view
    returns (PhEvm.Erc20TransferData[] memory deltas);

_transferredValueAt

Returns the total amount of token transferred from from to to in the fork scope.

function _transferredValueAt(address token, address from, address to, PhEvm.ForkId memory fork)
    internal
    view
    returns (uint256 value);

_consumedBetween

Computes the non-negative decrease between two snapshot values.

function _consumedBetween(uint256 beforeValue, uint256 afterValue) internal pure returns (uint256 consumed);

IERC20LogUtils

Git Source

Title: IERC20LogUtils

Author: Phylax Systems

Helpers for querying and decoding standard ERC20 logs returned by PhEvm.

Constants

TRANSFER_EVENT_SIGNATURE

Standard ERC20 Transfer event signature.

bytes32 internal constant TRANSFER_EVENT_SIGNATURE = keccak256("Transfer(address,address,uint256)")

APPROVAL_EVENT_SIGNATURE

Standard ERC20 Approval event signature.

bytes32 internal constant APPROVAL_EVENT_SIGNATURE = keccak256("Approval(address,address,uint256)")

Functions

transferQuery

Builds a PhEvm query for ERC20 Transfer logs emitted by token.

Pass address(0) to match Transfer logs from any emitter.

function transferQuery(address token) internal pure returns (PhEvm.LogQuery memory query);

approvalQuery

Builds a PhEvm query for ERC20 Approval logs emitted by token.

Pass address(0) to match Approval logs from any emitter.

function approvalQuery(address token) internal pure returns (PhEvm.LogQuery memory query);

isTransfer

Returns true when log has the canonical ERC20 Transfer event shape.

function isTransfer(PhEvm.Log memory log) internal pure returns (bool);

isApproval

Returns true when log has the canonical ERC20 Approval event shape.

function isApproval(PhEvm.Log memory log) internal pure returns (bool);

decodeTransfer

Decodes a canonical ERC20 Transfer log.

function decodeTransfer(PhEvm.Log memory log) internal pure returns (PhEvm.Erc20TransferData memory transfer);

decodeApproval

Decodes a canonical ERC20 Approval log.

function decodeApproval(PhEvm.Log memory log) internal pure returns (ApprovalData memory approval);

decodeTransfers

Decodes all logs as ERC20 Transfer events.

Reverts if any log is not a canonical Transfer event.

function decodeTransfers(PhEvm.Log[] memory logs)
    internal
    pure
    returns (PhEvm.Erc20TransferData[] memory transfers);

decodeApprovals

Decodes all logs as ERC20 Approval events.

Reverts if any log is not a canonical Approval event.

function decodeApprovals(PhEvm.Log[] memory logs) internal pure returns (ApprovalData[] memory approvals);

_topicAddress

function _topicAddress(bytes32 topic) private pure returns (address);

Structs

ApprovalData

Decoded ERC20 Approval event data.

struct ApprovalData {
    /// @notice The token contract that emitted the Approval event.
    address token_addr;
    /// @notice The token owner indexed in topic1.
    address owner;
    /// @notice The approved spender indexed in topic2.
    address spender;
    /// @notice The approved amount decoded from log data.
    uint256 value;
}

LogUtils

Git Source

Title: LogUtils

Author: Phylax Systems

Pure utility library for matching, filtering, and decoding EVM logs returned by getLogs() or getLogsQuery(). All functions operate on in-memory PhEvm.Log structs and do not call any precompiles.

Implemented with internal functions so they inline at the call site and avoid external call overhead.

Functions

sig

Returns topic[0] of the log, or bytes32(0) if no topics.

function sig(PhEvm.Log memory log) internal pure returns (bytes32);

isSig

True if log.topics[0] == eventSig.

function isSig(PhEvm.Log memory log, bytes32 eventSig) internal pure returns (bool);

isFrom

True if log.emitter == emitter.

function isFrom(PhEvm.Log memory log, address emitter) internal pure returns (bool);

isEvent

True if log matches both emitter and eventSig.

function isEvent(PhEvm.Log memory log, address emitter, bytes32 eventSig) internal pure returns (bool);

indexedTopic

Returns topics[indexedIdx + 1] as raw bytes32. Reverts if out of bounds.

The +1 skips topic[0] (the event signature).

function indexedTopic(PhEvm.Log memory log, uint256 indexedIdx) internal pure returns (bytes32);

indexedAddress

Decodes indexed param as address.

function indexedAddress(PhEvm.Log memory log, uint256 indexedIdx) internal pure returns (address);

indexedUint

Decodes indexed param as uint256.

function indexedUint(PhEvm.Log memory log, uint256 indexedIdx) internal pure returns (uint256);

indexedBool

Decodes indexed param as bool.

function indexedBool(PhEvm.Log memory log, uint256 indexedIdx) internal pure returns (bool);

topic

Encodes address as topic-compatible bytes32 (left-pads 20 bytes to 32).

function topic(address value) internal pure returns (bytes32);

topic

Encodes uint256 as topic-compatible bytes32.

function topic(uint256 value) internal pure returns (bytes32);

first

Returns the first log matching (emitter, eventSig).

function first(PhEvm.Log[] memory logs, address emitter, bytes32 eventSig)
    internal
    pure
    returns (bool found, PhEvm.Log memory log);

Returns

NameTypeDescription
foundboolTrue if a matching log was found.
logPhEvm.LogThe first matching log, or an empty log if none found.

count

Counts logs matching (emitter, eventSig).

function count(PhEvm.Log[] memory logs, address emitter, bytes32 eventSig) internal pure returns (uint256 n);

Assertion

Git Source

Inherits: ForkUtils, StateChanges

Title: Assertion

Author: Phylax Systems

Base contract for creating Credible Layer assertions

Inherit from this contract to create custom assertions. Assertions can inspect transaction state via the inherited ph precompile and register triggers to specify when the assertion should be executed. Example:

contract MyAssertion is Assertion {
function triggers() external view override {
registerCallTrigger(this.checkInvariant.selector, ITarget.deposit.selector);
}
function checkInvariant() external {
ph.forkPostTx();
Check invariants...
}
}

Constants

triggerRecorder

The trigger recorder precompile for registering assertion triggers

Address is derived from a deterministic hash for consistency

TriggerRecorder constant triggerRecorder = TriggerRecorder(address(uint160(uint256(keccak256("TriggerRecorder")))))

specRecorder

The spec recorder precompile for registering the assertion spec

Address is derived from keccak256(“SpecRecorder”)

SpecRecorder constant specRecorder = SpecRecorder(address(uint160(uint256(keccak256("SpecRecorder")))))

Functions

triggers

Used to record fn selectors and their triggers.

function triggers() external view virtual;

registerCallTrigger

Registers a call trigger for the AA without specifying an AA function selector. This will trigger the assertion function on any call to the AA.

function registerCallTrigger(bytes4 fnSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerCallTrigger

Registers a call trigger for calls to the AA with a specific AA function selector.

function registerCallTrigger(bytes4 fnSelector, bytes4 triggerSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.
triggerSelectorbytes4The function selector upon which the assertion will be triggered.

registerStorageChangeTrigger

Registers storage change trigger for any slot

function registerStorageChangeTrigger(bytes4 fnSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerStorageChangeTrigger

Registers storage change trigger for a specific slot

function registerStorageChangeTrigger(bytes4 fnSelector, bytes32 slot) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.
slotbytes32The storage slot to trigger on.

registerBalanceChangeTrigger

Registers balance change trigger for the AA

function registerBalanceChangeTrigger(bytes4 fnSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerFnCallTrigger

Registers an onFnCall trigger. The assertion fires once per matching call, with TriggerContext available via ph.context().

function registerFnCallTrigger(bytes4 fnSelector, bytes4 triggerSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.
triggerSelectorbytes4The 4-byte selector on the adopter to watch for.

registerTxEndTrigger

Registers a trigger that fires once after the entire transaction completes.

function registerTxEndTrigger(bytes4 fnSelector) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.

registerErc20ChangeTrigger

Registers a trigger that fires when a token’s balances change.

function registerErc20ChangeTrigger(bytes4 fnSelector, address token) internal view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.
tokenaddressThe ERC20 token address to watch.

watchCumulativeOutflow

Registers a circuit breaker trigger that fires when cumulative ERC20 outflow from the assertion adopter exceeds a percentage threshold within a rolling time window.

function watchCumulativeOutflow(address token, uint256 thresholdBps, uint256 windowDuration, bytes4 fnSelector)
    internal
    view;

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address to monitor.
thresholdBpsuint256Maximum cumulative outflow as basis points of the TVL snapshot taken at window start. 1000 = 10%.
windowDurationuint256Rolling window length in seconds.
fnSelectorbytes4The assertion function to invoke when the threshold is breached.

watchCumulativeInflow

Registers a circuit breaker trigger that fires when cumulative ERC20 inflow into the assertion adopter exceeds a percentage threshold within a rolling time window.

function watchCumulativeInflow(address token, uint256 thresholdBps, uint256 windowDuration, bytes4 fnSelector)
    internal
    view;

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address to monitor.
thresholdBpsuint256Maximum cumulative inflow as basis points of the TVL snapshot taken at window start. 1000 = 10%.
windowDurationuint256Rolling window length in seconds.
fnSelectorbytes4The assertion function to invoke when the threshold is breached.

watchAnomaly

Registers an anomaly-detection trigger. Fires whenever the executor’s configured AnomalySubsystem produces a score for target in a transaction that touches it.

The model owns the firing decision: the trigger fires whenever the subsystem returns a score at all. The assertion reads the score back via ph.anomalyContext(target) and decides whether to revert, run extra checks, or ignore.

function watchAnomaly(address target, bytes4 fnSelector) internal view;

Parameters

NameTypeDescription
targetaddressThe address whose anomaly score this assertion observes.
fnSelectorbytes4The assertion function to invoke when target is scored.

_successOnlyFilter

Returns a CallFilter that only matches successful calls at any depth.

function _successOnlyFilter() internal pure returns (PhEvm.CallFilter memory filter);

_matchingCalls

Returns successful calls matching target and selector, up to limit.

function _matchingCalls(address target, bytes4 selector, uint256 limit)
    internal
    view
    returns (PhEvm.TriggerCall[] memory);

_preTx

function _preTx() internal pure returns (PhEvm.ForkId memory);

_postTx

function _postTx() internal pure returns (PhEvm.ForkId memory);

_preCall

function _preCall(uint256 callId) internal pure returns (PhEvm.ForkId memory);

_postCall

function _postCall(uint256 callId) internal pure returns (PhEvm.ForkId memory);

registerAssertionSpec

Registers the desired assertion spec. Must be called within the constructor. The assertion spec defines what subset of precompiles are available. Can only be called once. For an assertion to be valid, it needs a defined spec.

function registerAssertionSpec(AssertionSpec spec) internal;

Parameters

NameTypeDescription
specAssertionSpecThe desired AssertionSpec.

console

Git Source

Title: console

Author: Phylax Systems

Logging library for Credible Layer assertions

Provides console logging functionality within assertion execution context. Logs are captured by the Credible Layer runtime for debugging purposes.

Constants

CONSOLE_ADDRESS

The console precompile address

Derived from a deterministic hash to ensure consistency with the runtime

address constant CONSOLE_ADDRESS = address(uint160(uint256(keccak256("Kim Jong Un Sucks"))))

Functions

log

Log a string message

Messages are captured by the Credible Layer runtime

function log(string memory message) internal view;

Parameters

NameTypeDescription
messagestringThe message to log

Credible

Git Source

Title: Credible

Author: Phylax Systems

Base contract providing access to the PhEvm precompile interface

All assertion contracts should inherit from this contract (via Assertion) to access the PhEvm precompile for reading transaction state, logs, and call inputs.

Constants

ph

The PhEvm precompile instance for accessing transaction state

The address is derived from a deterministic hash to ensure consistency

PhEvm constant ph = PhEvm(address(uint160(uint256(keccak256("Kim Jong Un Sucks")))))

VmEx

Git Source

Inherits: Vm

Title: VmEx

Extended Vm interface with assertion testing capabilities

Extends the standard Forge Vm interface with Credible Layer specific cheatcodes

Functions

assertion

Register an assertion for testing

function assertion(address adopter, bytes calldata createData, bytes4 fnSelector) external;

Parameters

NameTypeDescription
adopteraddressThe address of the contract that adopts the assertion
createDatabytesThe creation bytecode of the assertion contract
fnSelectorbytes4The function selector of the assertion function to test

CredibleTest

Git Source

Title: CredibleTest

Author: Phylax Systems

Base contract for testing Credible Layer assertions with Forge

Inherit from this contract (or CredibleTestWithBacktesting) to test assertions locally

Constants

cl

The extended Vm cheatcode interface for assertion testing

Provides access to assertion-specific cheatcodes

VmEx public constant cl = VmEx(address(uint160(uint256(keccak256("hevm cheat code")))))

CredibleTestWithBacktesting

Git Source

Inherits: CredibleTest, Test

Title: CredibleTestWithBacktesting

Author: Phylax Systems

Extended CredibleTest with historical transaction backtesting capabilities

Inherit from this contract to test assertions against historical blockchain transactions. Supports two modes:

  • Block range mode: Test all transactions in a block range via executeBacktest(config)
  • Single transaction mode: Test a specific transaction via executeBacktestForTransaction(txHash, ...) Example:
contract MyBacktest is CredibleTestWithBacktesting {
function testHistorical() public {
executeBacktest(BacktestingTypes.BacktestingConfig({
targetContract: 0x...,
endBlock: 1000000,
blockRange: 100,
assertionCreationCode: type(MyAssertion).creationCode,
assertionSelector: MyAssertion.check.selector,
rpcUrl: "https://eth.llamarpc.com",
detailedBlocks: false,
forkByTxHash: true
}));
}
}

State Variables

_cachedScriptPath

Cached script path to avoid repeated filesystem lookups

string private _cachedScriptPath

Functions

executeBacktestForTransaction

Execute backtesting for a single transaction by hash (overload for single tx mode)

function executeBacktestForTransaction(
    bytes32 txHash,
    address targetContract,
    bytes memory assertionCreationCode,
    bytes4 assertionSelector,
    string memory rpcUrl
) public returns (BacktestingTypes.BacktestingResults memory results);

Parameters

NameTypeDescription
txHashbytes32The transaction hash to backtest
targetContractaddressThe target contract address
assertionCreationCodebytesThe assertion contract creation code
assertionSelectorbytes4The assertion function selector
rpcUrlstringThe RPC URL to use

Returns

NameTypeDescription
resultsBacktestingTypes.BacktestingResultsThe backtesting results

executeBacktest

Execute backtesting with config struct (block range mode)

function executeBacktest(BacktestingTypes.BacktestingConfig memory config)
    public
    returns (BacktestingTypes.BacktestingResults memory results);

_getScriptSearchPaths

Get the standard search paths for transaction_fetcher.sh

Override this in your test contract to add custom search paths

function _getScriptSearchPaths() internal view virtual returns (string[] memory);

Returns

NameTypeDescription
<none>string[]Array of paths to check, in order of preference

_findScriptPath

Find the transaction_fetcher.sh script path

Checks environment variable first, then searches common locations, and finally uses find to auto-detect the script location. Override _getScriptSearchPaths() to customize search locations

function _findScriptPath() internal virtual returns (string memory);

Returns

NameTypeDescription
<none>stringThe path to transaction_fetcher.sh

_autoDetectScriptPath

Auto-detect the script path using find command

Searches the project directory for transaction_fetcher.sh

function _autoDetectScriptPath() internal virtual returns (string memory);

Returns

NameTypeDescription
<none>stringThe detected path, or empty string if not found

_fetchTransactions

Fetch transactions using FFI

Automatically detects internal calls using trace APIs with fallback: trace_filter -> debug_traceBlockByNumber -> debug_traceTransaction -> direct calls only

function _fetchTransactions(address targetContract, uint256 startBlock, uint256 endBlock, string memory rpcUrl)
    private
    returns (BacktestingTypes.TransactionData[] memory transactions);

_executeBacktestForSingleTransaction

Execute backtesting for a single transaction specified by hash

function _executeBacktestForSingleTransaction(
    bytes32 txHash,
    address targetContract,
    bytes memory assertionCreationCode,
    bytes4 assertionSelector,
    string memory rpcUrl
) private returns (BacktestingTypes.BacktestingResults memory results);

_fetchTransactionByHash

Fetch a single transaction by hash using FFI

function _fetchTransactionByHash(bytes32 txHash, string memory rpcUrl)
    private
    returns (BacktestingTypes.TransactionData memory txData);

_parseTransactionFromTsv

Parse transaction data from tab-separated output

function _parseTransactionFromTsv(string memory tsvLine)
    private
    pure
    returns (BacktestingTypes.TransactionData memory txData);

_validateTransaction

Validate a single transaction with detailed error categorization

function _validateTransaction(
    address targetContract,
    bytes memory assertionCreationCode,
    bytes4 assertionSelector,
    string memory rpcUrl,
    BacktestingTypes.TransactionData memory txData,
    bool forkByTxHash
) private returns (BacktestingTypes.ValidationDetails memory validation);

_replayTransactionForTrace

Replay a failed transaction to show the full execution trace

Forks to state before the tx and makes a raw call so Foundry prints the full trace

function _replayTransactionForTrace(
    address, // targetContract - unused, kept for interface compatibility
    bytes memory, // assertionCreationCode - unused
    bytes4, // assertionSelector - unused
    string memory rpcUrl,
    BacktestingTypes.TransactionData memory txData
) private;

_categorizeAndLogError

Categorize and log error details

function _categorizeAndLogError(BacktestingTypes.ValidationDetails memory validation) private pure;

_printDetailedResults

Print detailed results with error categorization

function _printDetailedResults(
    uint256 startBlock,
    uint256 endBlock,
    BacktestingTypes.BacktestingResults memory results
) private pure;

PhEvm

Git Source

Title: PhEvm

Author: Phylax Systems

Precompile interface for accessing transaction state within assertions

This interface provides access to the Credible Layer’s execution environment, allowing assertions to inspect transaction state, logs, call inputs, and storage changes. The precompile is available at a deterministic address during assertion execution.

Functions

forkPreTx

Fork to the state before the assertion-triggering transaction

DEPRECATED: Use staticcallAt / loadStateAt with ForkId instead.

function forkPreTx() external;

forkPostTx

Fork to the state after the assertion-triggering transaction

DEPRECATED: Use staticcallAt / loadStateAt with ForkId instead.

function forkPostTx() external;

forkPreCall

Fork to the state before a specific call execution

DEPRECATED: Use staticcallAt / loadStateAt with ForkId instead.

function forkPreCall(uint256 id) external;

forkPostCall

Fork to the state after a specific call execution

DEPRECATED: Use staticcallAt / loadStateAt with ForkId instead.

function forkPostCall(uint256 id) external;

load

Load a storage slot value from any address

DEPRECATED: Use loadStateAt with ForkId instead.

function load(address target, bytes32 slot) external view returns (bytes32 data);

loadStateAt

Read a storage slot from the current assertion adopter at a snapshot.

function loadStateAt(bytes32 slot, ForkId calldata fork) external view returns (bytes32 value);

Parameters

NameTypeDescription
slotbytes32The storage slot to read.
forkForkIdThe snapshot fork to read from.

Returns

NameTypeDescription
valuebytes32The raw 32-byte value at the slot.

loadStateAt

Read a storage slot from any account at a snapshot.

function loadStateAt(address target, bytes32 slot, ForkId calldata fork) external view returns (bytes32 value);

Parameters

NameTypeDescription
targetaddressThe address to read storage from.
slotbytes32The storage slot to read.
forkForkIdThe snapshot fork to read from.

Returns

NameTypeDescription
valuebytes32The raw 32-byte value at the slot.

staticcallAt

Execute a static call against a snapshot fork.

function staticcallAt(address target, bytes calldata data, uint64 gas_limit, ForkId calldata fork)
    external
    view
    returns (StaticCallResult memory result);

Parameters

NameTypeDescription
targetaddressThe contract to call.
databytesThe ABI-encoded function call.
gas_limituint64The gas budget forwarded to the nested static call.
forkForkIdThe snapshot fork to execute against.

Returns

NameTypeDescription
resultStaticCallResultSuccess flag and return or revert bytes from the nested call.

getLogsQuery

Get logs matching a query from a snapshot fork

function getLogsQuery(LogQuery calldata query, ForkId calldata fork) external view returns (Log[] memory logs);

Parameters

NameTypeDescription
queryLogQueryThe emitter and signature filters to apply
forkForkIdThe snapshot fork to read logs from

Returns

NameTypeDescription
logsLog[]Array of logs matching the query inside the selected snapshot window

getErc20Transfers

Returns all ERC20 transfers for a single token in the specified fork.

function getErc20Transfers(address token, ForkId calldata fork)
    external
    view
    returns (Erc20TransferData[] memory transfers);

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address.
forkForkIdThe fork to query.

Returns

NameTypeDescription
transfersErc20TransferData[]Array of decoded transfer records.

getErc20TransfersForTokens

Returns all ERC20 transfers for multiple tokens in the specified fork.

function getErc20TransfersForTokens(address[] calldata tokens, ForkId calldata fork)
    external
    view
    returns (Erc20TransferData[] memory transfers);

Parameters

NameTypeDescription
tokensaddress[]Array of ERC20 token addresses.
forkForkIdThe fork to query.

Returns

NameTypeDescription
transfersErc20TransferData[]Combined array of decoded transfer records across all tokens.

changedErc20BalanceDeltas

Returns all transfers involving the given token for the specified fork.

Semantic alias of getErc20Transfers for balance-delta workflows.

function changedErc20BalanceDeltas(address token, ForkId calldata fork)
    external
    view
    returns (Erc20TransferData[] memory deltas);

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address.
forkForkIdThe fork to query.

reduceErc20BalanceDeltas

Reduces transfers into net balance deltas per unique (from, to) pair.

function reduceErc20BalanceDeltas(address token, ForkId calldata fork)
    external
    view
    returns (Erc20TransferData[] memory deltas);

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address.
forkForkIdThe fork to query.

Returns

NameTypeDescription
deltasErc20TransferData[]Aggregated transfer records in first-seen pair order.

getLogs

Get all logs emitted during the transaction

Returns logs in emission order

function getLogs() external returns (Log[] memory logs);

Returns

NameTypeDescription
logsLog[]Array of Log structs containing all emitted events

getAllCallInputs

Get all call inputs for a target and selector (all call types)

Includes CALL, STATICCALL, DELEGATECALL, and CALLCODE. Each returned CallInputs.input is the ABI-encoded arguments WITHOUT the 4-byte selector (the selector is the query key here). Prepend it with bytes.concat(selector, calls[i].input) to reconstruct full calldata before decoding; do not slice input[4:]. See the CallInputs.input field docs.

function getAllCallInputs(address target, bytes4 selector) external view returns (CallInputs[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target contract address
selectorbytes4The function selector to filter by

Returns

NameTypeDescription
callsCallInputs[]Array of CallInputs matching the criteria

getCallInputs

Get call inputs for regular CALL opcode only

function getCallInputs(address target, bytes4 selector) external view returns (CallInputs[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target contract address
selectorbytes4The function selector to filter by

Returns

NameTypeDescription
callsCallInputs[]Array of CallInputs from CALL opcodes

getStaticCallInputs

Get call inputs for STATICCALL opcode only

function getStaticCallInputs(address target, bytes4 selector) external view returns (CallInputs[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target contract address
selectorbytes4The function selector to filter by

Returns

NameTypeDescription
callsCallInputs[]Array of CallInputs from STATICCALL opcodes

getDelegateCallInputs

Get call inputs for DELEGATECALL opcode only

function getDelegateCallInputs(address target, bytes4 selector) external view returns (CallInputs[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target/proxy contract address
selectorbytes4The function selector to filter by

Returns

NameTypeDescription
callsCallInputs[]Array of CallInputs from DELEGATECALL opcodes

getCallCodeInputs

Get call inputs for CALLCODE opcode only

function getCallCodeInputs(address target, bytes4 selector) external view returns (CallInputs[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target contract address
selectorbytes4The function selector to filter by

Returns

NameTypeDescription
callsCallInputs[]Array of CallInputs from CALLCODE opcodes

callOutputAt

Returns the raw return or revert bytes for a traced call.

function callOutputAt(uint256 callId) external view returns (bytes memory output);

Parameters

NameTypeDescription
callIduint256The call identifier from CallInputs.id.

Returns

NameTypeDescription
outputbytesThe raw ABI-encoded return bytes or revert bytes.

callinputAt

Returns the calldata of a specific call.

function callinputAt(uint256 callId) external view returns (bytes memory input);

Parameters

NameTypeDescription
callIduint256The call ID to read input from.

Returns

NameTypeDescription
inputbytesThe raw calldata bytes (selector + ABI-encoded arguments).

getStateChanges

Get all state changes for a specific storage slot

Returns the sequence of values the slot held during transaction execution

function getStateChanges(address contractAddress, bytes32 slot)
    external
    view
    returns (bytes32[] memory stateChanges);

Parameters

NameTypeDescription
contractAddressaddressThe contract whose storage to inspect
slotbytes32The storage slot to get changes for

Returns

NameTypeDescription
stateChangesbytes32[]Array of values the slot held (in order of changes)

forbidChangeForSlot

Checks that a single storage slot on the assertion adopter was not modified.

function forbidChangeForSlot(bytes32 slot) external view returns (bool ok);

Parameters

NameTypeDescription
slotbytes32The slot to protect.

Returns

NameTypeDescription
okboolTrue when the slot was not written during the transaction.

forbidChangeForSlots

Checks that none of the given storage slots on the assertion adopter were modified.

function forbidChangeForSlots(bytes32[] calldata slots) external view returns (bool ok);

Parameters

NameTypeDescription
slotsbytes32[]The slots to protect.

Returns

NameTypeDescription
okboolTrue when none of the slots were written during the transaction.

getAssertionAdopter

Get the assertion adopter address for the current transaction

The adopter is the contract that registered the assertion

function getAssertionAdopter() external view returns (address);

Returns

NameTypeDescription
<none>addressThe address of the assertion adopter contract

getTxObject

Get the original transaction object that triggered the assertion

Returns the transaction envelope data for the assertion-triggering tx

function getTxObject() external view returns (TxObject memory txObject);

Returns

NameTypeDescription
txObjectTxObjectThe transaction data struct

context

Returns the context for the current onFnCall trigger invocation.

Only valid inside an assertion function triggered by registerFnCallTrigger. Reverts if called outside of an onFnCall-triggered assertion.

function context() external view returns (TriggerContext memory);

matchingCalls

Returns calls matching the given target, selector, and filter criteria.

Each returned TriggerCall.input is the ABI-encoded arguments WITHOUT the 4-byte selector — prepend selector before decoding (see the TriggerCall.input field docs).

function matchingCalls(address target, bytes4 selector, CallFilter calldata filter, uint256 limit)
    external
    view
    returns (TriggerCall[] memory calls);

Parameters

NameTypeDescription
targetaddressThe target contract address.
selectorbytes4The function selector to filter by.
filterCallFilterFiltering criteria (call type, depth, success).
limituint256Maximum number of results to return.

Returns

NameTypeDescription
callsTriggerCall[]Array of matching call records.

getLogsForCall

Returns logs emitted during a specific call frame.

function getLogsForCall(LogQuery calldata query, uint256 callId) external view returns (Log[] memory logs);

Parameters

NameTypeDescription
queryLogQueryThe emitter and signature filters to apply.
callIduint256The call ID to scope the log query to.

Returns

NameTypeDescription
logsLog[]Array of logs emitted during the call.

store

Write a bytes32 value to persistent assertion storage.

function store(bytes32 key, bytes32 value) external;

Parameters

NameTypeDescription
keybytes32The storage key.
valuebytes32The value to store.

load

Read a bytes32 value from persistent assertion storage.

function load(bytes32 key) external view returns (bytes32 value);

Parameters

NameTypeDescription
keybytes32The storage key.

Returns

NameTypeDescription
valuebytes32The stored value.

exists

Check if a key exists in persistent assertion storage.

function exists(bytes32 key) external view returns (bool doesExist);

Parameters

NameTypeDescription
keybytes32The storage key.

Returns

NameTypeDescription
doesExistboolTrue if the key has been written to.

values_left

Returns remaining storage slots available to this assertion.

function values_left() external view returns (uint256 remaining);

changedMappingKeys

Returns canonical Solidity key encodings h(key) for keys whose mapping entry at baseSlot was written during the tx.

Best-effort heuristic: traces KECCAK256 -> SSTORE provenance in the execution trace. Custom inline assembly or precomputed hashed slots can bypass the visible keccak chain and produce false negatives.

function changedMappingKeys(address target, bytes32 baseSlot) external view returns (bytes[] memory keys);

Parameters

NameTypeDescription
targetaddressThe contract whose storage was modified.
baseSlotbytes32The Solidity mapping’s base storage slot.

Returns

NameTypeDescription
keysbytes[]Array of encoded keys (each is the h(key) preimage).

mappingValueDiff

Returns the pre/post values for a specific mapping entry.

Computes slot = keccak256(key ++ baseSlot) + fieldOffset, then reads pre from the PreTx fork and post from the PostTx fork.

function mappingValueDiff(address target, bytes32 baseSlot, bytes calldata key, uint256 fieldOffset)
    external
    view
    returns (bytes32 pre, bytes32 post, bool changed);

Parameters

NameTypeDescription
targetaddressThe contract address.
baseSlotbytes32The mapping’s base slot.
keybytesThe canonical encoding h(key) of the mapping key.
fieldOffsetuint256Struct field offset (0 for the first slot of the value).

Returns

NameTypeDescription
prebytes32The PreTx value.
postbytes32The PostTx value.
changedboolTrue if pre != post.

assetsMatchSharePrice

Checks ERC4626 share price consistency across all fork points.

function assetsMatchSharePrice(address vault, uint256 toleranceBps) external view returns (bool);

Parameters

NameTypeDescription
vaultaddressThe ERC4626 vault address.
toleranceBpsuint256Maximum allowed deviation in basis points.

Returns

NameTypeDescription
<none>boolTrue if share price stays within tolerance at all forks.

assetsMatchSharePriceAt

Checks ERC4626 share price consistency between two specific forks.

function assetsMatchSharePriceAt(address vault, uint256 toleranceBps, ForkId calldata fork0, ForkId calldata fork1)
    external
    view
    returns (bool);

Parameters

NameTypeDescription
vaultaddressThe ERC4626 vault address.
toleranceBpsuint256Maximum allowed deviation in basis points.
fork0ForkIdThe baseline fork.
fork1ForkIdThe comparison fork.

Returns

NameTypeDescription
<none>boolTrue if share price stays within tolerance.

conserveBalance

Checks that an account’s ERC20 balance is unchanged between two forks.

function conserveBalance(ForkId calldata fork0, ForkId calldata fork1, address token, address account)
    external
    view
    returns (bool);

Parameters

NameTypeDescription
fork0ForkIdThe baseline fork.
fork1ForkIdThe comparison fork.
tokenaddressThe ERC20 token address.
accountaddressThe account whose balance should remain unchanged.

Returns

NameTypeDescription
<none>boolTrue if balanceOf(account) is identical at both forks.

outflowContext

Returns context about the outflow that triggered this assertion.

Only valid inside an assertion function triggered by watchCumulativeOutflow. Returns a zeroed struct if called from a non-outflow trigger context.

function outflowContext() external view returns (OutflowContext memory ctx);

Returns

NameTypeDescription
ctxOutflowContextThe outflow context for the current trigger invocation.

inflowContext

Returns context about the inflow that triggered this assertion.

Only valid inside an assertion function triggered by watchCumulativeInflow. Returns a zeroed struct if called from a non-inflow trigger context.

function inflowContext() external view returns (InflowContext memory ctx);

Returns

NameTypeDescription
ctxInflowContextThe inflow context for the current trigger invocation.

outflowRate

Returns the outflow rate-of-change context for the current invocation.

EXPERIMENTAL: requires registerAssertionSpec(AssertionSpec.Experimental). Only valid inside an assertion function triggered by watchCumulativeOutflow. Returns a zeroed struct otherwise.

function outflowRate() external view returns (FlowRateContext memory ctx);

Returns

NameTypeDescription
ctxFlowRateContextThe outflow rate-of-change context for the current invocation.

inflowRate

Returns the inflow rate-of-change context for the current invocation.

EXPERIMENTAL: requires registerAssertionSpec(AssertionSpec.Experimental). Only valid inside an assertion function triggered by watchCumulativeInflow. Returns a zeroed struct otherwise.

function inflowRate() external view returns (FlowRateContext memory ctx);

Returns

NameTypeDescription
ctxFlowRateContextThe inflow rate-of-change context for the current invocation.

anomalyContext

Returns the anomaly detector’s view of target for the current transaction.

Returns a zero-filled context when target was not able to be scored, such that it fails open.

function anomalyContext(address target) external view returns (AnomalyContext memory ctx);

Parameters

NameTypeDescription
targetaddressThe address whose anomaly context to read.

Returns

NameTypeDescription
ctxAnomalyContextThe anomaly context for target in the current tx.

oracleSanity

Checks oracle price consistency across all fork points.

function oracleSanity(address target, bytes calldata data, uint256 bpsDeviation) external returns (bool);

Parameters

NameTypeDescription
targetaddressThe oracle contract address.
databytesThe ABI-encoded oracle query.
bpsDeviationuint256Maximum allowed deviation in basis points.

Returns

NameTypeDescription
<none>boolTrue if oracle price stays within tolerance.

oracleSanityAt

Checks oracle price consistency between two specific forks.

function oracleSanityAt(
    address target,
    bytes calldata data,
    uint256 bpsDeviation,
    ForkId calldata initialFork,
    ForkId calldata currentFork
) external returns (bool);

Parameters

NameTypeDescription
targetaddressThe oracle contract address.
databytesThe ABI-encoded oracle query.
bpsDeviationuint256Maximum allowed deviation in basis points.
initialForkForkIdThe baseline fork.
currentForkForkIdThe comparison fork.

Returns

NameTypeDescription
<none>boolTrue if oracle price stays within tolerance.

mulDivDown

Computes (x * y) / denominator, rounded down. Uses 512-bit intermediates.

function mulDivDown(uint256 x, uint256 y, uint256 denominator) external pure returns (uint256 result);

mulDivUp

Computes (x * y) / denominator, rounded up. Uses 512-bit intermediates.

function mulDivUp(uint256 x, uint256 y, uint256 denominator) external pure returns (uint256 result);

normalizeDecimals

Scales an amount from one decimal base to another.

function normalizeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals)
    external
    pure
    returns (uint256 result);

ratioGe

Compares two ratios with tolerance: num1/den1 >= num2/den2 * (1 - toleranceBps/10000).

Uses cross-multiplication with wide intermediates to avoid division and overflow.

function ratioGe(uint256 num1, uint256 den1, uint256 num2, uint256 den2, uint256 toleranceBps)
    external
    pure
    returns (bool);

Structs

Log

Represents an Ethereum log emitted during transaction execution

Used by getLogs() to return transaction logs for inspection

struct Log {
    /// @notice The topics of the log, including the event signature if any
    bytes32[] topics;
    /// @notice The raw ABI-encoded data of the log
    bytes data;
    /// @notice The address of the contract that emitted the log
    address emitter;
}

LogQuery

Query used to filter transaction logs by emitter and/or signature

struct LogQuery {
    /// @notice address(0) matches any emitter
    address emitter;
    /// @notice bytes32(0) matches any topic0 signature
    bytes32 signature;
}

CallInputs

Represents the inputs to a call made during transaction execution

Used by getCallInputs() and related functions to inspect call details

struct CallInputs {
    /// @notice The ABI-encoded call arguments, WITHOUT the 4-byte function selector.
    /// @dev The `get*CallInputs` queries (getAllCallInputs/getCallInputs/getStaticCallInputs/
    ///      getDelegateCallInputs/getCallCodeInputs) key on `selector`, so it is stripped from
    ///      this field and only the argument tail remains. To rebuild full calldata, prepend the
    ///      selector: `bytes.concat(selector, input)`. Do NOT slice `input[4:]` — the selector is
    ///      already gone, and slicing would drop the first argument word. Contrast `callinputAt`,
    ///      which returns the raw selector-prefixed calldata.
    bytes input;
    /// @notice The gas limit of the call
    uint64 gas_limit;
    /// @notice The address of the bytecode being executed (code address)
    address bytecode_address;
    /// @notice The target address whose storage may be modified
    address target_address;
    /// @notice The address that initiated this call
    address caller;
    /// @notice The ETH value sent with the call
    uint256 value;
    /// @notice Unique identifier for this call, used with forkPreCall/forkPostCall
    uint256 id;
}

TxObject

Contains data about the original assertion-triggering transaction

Provides access to transaction envelope data for inspection in assertions

struct TxObject {
    /// @notice The address that initiated the transaction (tx.origin equivalent)
    address from;
    /// @notice The transaction recipient, or address(0) for contract creation
    address to;
    /// @notice The ETH value sent with the transaction
    uint256 value;
    /// @notice The chain ID, or 0 if not present
    uint64 chain_id;
    /// @notice The gas limit for the transaction
    uint64 gas_limit;
    /// @notice The gas price or max_fee_per_gas for EIP-1559 transactions
    uint128 gas_price;
    /// @notice The transaction calldata
    bytes input;
}

StaticCallResult

Result of a nested static call executed against a snapshot.

struct StaticCallResult {
    /// @notice Whether the nested call completed successfully
    bool ok;
    /// @notice Raw return data or revert data from the nested call
    bytes data;
}

Erc20TransferData

Decoded ERC20 Transfer event data from a snapshot fork.

struct Erc20TransferData {
    /// @notice The token contract that emitted the Transfer event
    address token_addr;
    /// @notice The sender indexed in topic1
    address from;
    /// @notice The receiver indexed in topic2
    address to;
    /// @notice The transferred amount decoded from log data
    uint256 value;
}

ForkId

Identifies a read-only transaction snapshot.

forkType: 0 = PreTx, 1 = PostTx, 2 = PreCall, 3 = PostCall callIndex is used only for call-scoped snapshots.

struct ForkId {
    uint8 forkType;
    uint256 callIndex;
}

TriggerContext

Context for an onFnCall-triggered assertion invocation.

Only valid inside an assertion function triggered by registerFnCallTrigger.

struct TriggerContext {
    /// @notice The function selector that was called on the adopter
    bytes4 selector;
    /// @notice Call index for constructing PreCall ForkId
    uint256 callStart;
    /// @notice Call index for constructing PostCall ForkId
    uint256 callEnd;
}

CallFilter

Filter criteria for matchingCalls queries.

struct CallFilter {
    /// @notice Call type: 0 = any, 1 = CALL, 2 = STATICCALL, 3 = DELEGATECALL, 4 = CALLCODE
    uint8 callType;
    /// @notice Minimum call depth to include
    uint32 minDepth;
    /// @notice Maximum call depth to include
    uint32 maxDepth;
    /// @notice If true, only return top-level calls (depth == 1)
    bool topLevelOnly;
    /// @notice If true, only return calls that succeeded
    bool successOnly;
}

TriggerCall

Detailed record of a call in the transaction trace.

struct TriggerCall {
    uint256 callId;
    uint256 parentCallId;
    address caller;
    address target;
    address codeAddress;
    bytes4 selector;
    uint32 depth;
    uint8 callType;
    bool success;
    uint256 value;
    /// @notice The ABI-encoded call arguments, WITHOUT the 4-byte function selector.
    /// @dev Same shape as `CallInputs.input`: `matchingCalls` keys on `selector`, so it is
    ///      stripped here. Prepend `selector` (`bytes.concat(selector, input)`) before decoding;
    ///      do not slice `input[4:]`.
    bytes input;
}

OutflowContext

Context about the outflow that triggered an assertion via watchCumulativeOutflow.

Only valid inside an assertion function triggered by watchCumulativeOutflow. Returns a zeroed struct if called from a non-outflow trigger context.

struct OutflowContext {
    /// @notice The ERC20 token that breached the threshold
    address token;
    /// @notice Net outflow within the window (token units)
    uint256 cumulativeOutflow;
    /// @notice Total absolute outflow within the window (token units, ignoring deposits)
    uint256 absoluteOutflow;
    /// @notice Current outflow as basis points of TVL snapshot
    uint256 currentBps;
    /// @notice Adopter's token balance at window start
    uint256 tvlSnapshot;
    /// @notice Timestamp when the current window began
    uint256 windowStart;
    /// @notice Timestamp when the current window expires
    uint256 windowEnd;
}

InflowContext

Context about the inflow that triggered an assertion via watchCumulativeInflow.

Only valid inside an assertion function triggered by watchCumulativeInflow. Returns a zeroed struct if called from a non-inflow trigger context.

struct InflowContext {
    /// @notice The ERC20 token that breached the threshold
    address token;
    /// @notice Net inflow within the window (token units)
    uint256 cumulativeInflow;
    /// @notice Total absolute inflow within the window (token units, ignoring withdrawals)
    uint256 absoluteInflow;
    /// @notice Current inflow as basis points of TVL snapshot
    uint256 currentBps;
    /// @notice Adopter's token balance at window start
    uint256 tvlSnapshot;
    /// @notice Timestamp when the current window began
    uint256 windowStart;
    /// @notice Timestamp when the current window expires
    uint256 windowEnd;
}

FlowRateContext

Rate-of-change statistics for a cumulative-flow-triggered assertion.

All rate fields are basis points of tvlSnapshot per second, derived from the same rolling-window buckets as the cumulative context (no extra storage). A single 10s bucket draining the whole snapshot reads ~1000 bps/s. Use this to suppress false positives of the cumulative breaker: a large-but-slow withdrawal reads a benign peak rate. Never gate the cumulative alert on it — treat it as OR-style escalation only.

struct FlowRateContext {
    /// @notice The ERC20 token that triggered the assertion. address(0) if no flow trigger fired.
    address token;
    /// @notice Peak per-bucket net flow rate over the window (bps of TVL / second)
    uint256 peakRateBps;
    /// @notice Most recent in-window bucket's net flow rate (bps of TVL / second)
    uint256 lastRateBps;
    /// @notice Mean net flow rate across the active window span (bps of TVL / second)
    uint256 meanRateBps;
    /// @notice Adopter's token balance at window start
    uint256 tvlSnapshot;
    /// @notice Timestamp when the current window began
    uint256 windowStart;
    /// @notice Timestamp when the current window expires
    uint256 windowEnd;
}

AnomalyContext

Context returned by anomalyContext(target) describing the anomaly detector’s view of target for the current tx.

scoreBps is in basis points (0..=10_000), where 0 is “very likely not anomalous” and 10_000 is “very likely anomalous”.

struct AnomalyContext {
    uint16 scoreBps;
}

AssertionSpec

Git Source

The assertion spec defines what subset of precompiles are available. All new specs derive and expose all precompiles from the old definitions, unless specified otherwise.

enum AssertionSpec {
/// @notice Standard set of PhEvm precompiles available at launch.
Legacy,
/// @notice Contains tx object precompiles.
Reshiram,
/// @notice Unrestricted access to all available precompiles. May be untested and dangerous.
Experimental
}

SpecRecorder

Git Source

Title: SpecRecorder

Author: Phylax Systems

Precompile interface for registering the desired assertion spec

Used within the constructor of assertion contracts to specify which subset of PhEvm precompiles should be available during assertion execution. You can only call registerAssertionSpec once per assertion.

Functions

registerAssertionSpec

Called within the constructor to set the desired assertion spec. The assertion spec defines what subset of precompiles are available. You can only call this function once. For an assertion to be valid, it needs to have a defined spec.

function registerAssertionSpec(AssertionSpec spec) external view;

Parameters

NameTypeDescription
specAssertionSpecThe desired AssertionSpec.

StateChanges

Git Source

Inherits: Credible

Title: StateChanges

Helper contract for converting state changes from bytes32 arrays to typed arrays

Inherits from Credible to access the PhEvm interface

Functions

getStateChangesUint

Converts state changes for a slot to uint256 array

function getStateChangesUint(address contractAddress, bytes32 slot) internal view returns (uint256[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe address of the contract to get state changes from
slotbytes32The storage slot to get state changes for

Returns

NameTypeDescription
<none>uint256[]Array of state changes as uint256 values

getStateChangesAddress

Converts state changes for a slot to address array

function getStateChangesAddress(address contractAddress, bytes32 slot) internal view returns (address[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe address of the contract to get state changes from
slotbytes32The storage slot to get state changes for

Returns

NameTypeDescription
<none>address[]Array of state changes as address values

getStateChangesBool

Converts state changes for a slot to boolean array

function getStateChangesBool(address contractAddress, bytes32 slot) internal view returns (bool[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe address of the contract to get state changes from
slotbytes32The storage slot to get state changes for

Returns

NameTypeDescription
<none>bool[]Array of state changes as boolean values

getStateChangesBytes32

Gets raw state changes as bytes32 array

function getStateChangesBytes32(address contractAddress, bytes32 slot) internal view returns (bytes32[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe address of the contract to get state changes from
slotbytes32The storage slot to get state changes for

Returns

NameTypeDescription
<none>bytes32[]Array of state changes as bytes32 values

getSlotMapping

Calculates the storage slot for a mapping with a given key and offset

function getSlotMapping(bytes32 slot, uint256 key, uint256 offset) private pure returns (bytes32);

Parameters

NameTypeDescription
slotbytes32The base storage slot of the mapping
keyuint256The key in the mapping
offsetuint256Additional offset to add to the calculated slot

Returns

NameTypeDescription
<none>bytes32The storage slot for the mapping entry

getStateChangesUint

Gets uint256 state changes for a mapping entry

function getStateChangesUint(address contractAddress, bytes32 slot, uint256 key)
    internal
    view
    returns (uint256[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key

Returns

NameTypeDescription
<none>uint256[]Array of state changes as uint256 values

getStateChangesAddress

Gets address state changes for a mapping entry

function getStateChangesAddress(address contractAddress, bytes32 slot, uint256 key)
    internal
    view
    returns (address[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key

Returns

NameTypeDescription
<none>address[]Array of state changes as address values

getStateChangesBool

Gets boolean state changes for a mapping entry

function getStateChangesBool(address contractAddress, bytes32 slot, uint256 key)
    internal
    view
    returns (bool[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key

Returns

NameTypeDescription
<none>bool[]Array of state changes as boolean values

getStateChangesBytes32

Gets bytes32 state changes for a mapping entry

function getStateChangesBytes32(address contractAddress, bytes32 slot, uint256 key)
    internal
    view
    returns (bytes32[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key

Returns

NameTypeDescription
<none>bytes32[]Array of state changes as bytes32 values

getStateChangesUint

Gets uint256 state changes for a mapping entry with offset

function getStateChangesUint(address contractAddress, bytes32 slot, uint256 key, uint256 slotOffset)
    internal
    view
    returns (uint256[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key
slotOffsetuint256Additional offset to add to the slot

Returns

NameTypeDescription
<none>uint256[]Array of state changes as uint256 values

getStateChangesAddress

Gets address state changes for a mapping entry with offset

function getStateChangesAddress(address contractAddress, bytes32 slot, uint256 key, uint256 slotOffset)
    internal
    view
    returns (address[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key
slotOffsetuint256Additional offset to add to the slot

Returns

NameTypeDescription
<none>address[]Array of state changes as address values

getStateChangesBool

Gets boolean state changes for a mapping entry with offset

function getStateChangesBool(address contractAddress, bytes32 slot, uint256 key, uint256 slotOffset)
    internal
    view
    returns (bool[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key
slotOffsetuint256Additional offset to add to the slot

Returns

NameTypeDescription
<none>bool[]Array of state changes as boolean values

getStateChangesBytes32

Gets bytes32 state changes for a mapping entry with offset

function getStateChangesBytes32(address contractAddress, bytes32 slot, uint256 key, uint256 slotOffset)
    internal
    view
    returns (bytes32[] memory);

Parameters

NameTypeDescription
contractAddressaddressThe contract address
slotbytes32The mapping’s slot
keyuint256The mapping key
slotOffsetuint256Additional offset to add to the slot

Returns

NameTypeDescription
<none>bytes32[]Array of state changes as bytes32 values

TriggerRecorder

Git Source

Title: TriggerRecorder

Author: Phylax Systems

Precompile interface for registering assertion triggers

Used within the triggers() function of assertion contracts to specify when assertions should be executed. Supports call triggers, storage change triggers, and balance change triggers.

Functions

registerStorageChangeTrigger

Registers storage change trigger for all slots

function registerStorageChangeTrigger(bytes4 fnSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerStorageChangeTrigger

Registers storage change trigger for a slot

function registerStorageChangeTrigger(bytes4 fnSelector, bytes32 slot) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.
slotbytes32The storage slot to trigger on.

registerBalanceChangeTrigger

Registers balance change trigger for the AA

function registerBalanceChangeTrigger(bytes4 fnSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerCallTrigger

Registers a call trigger for calls to the AA.

function registerCallTrigger(bytes4 fnSelector, bytes4 triggerSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.
triggerSelectorbytes4The function selector of the trigger function.

registerCallTrigger

Records a call trigger for the specified assertion function. A call trigger signifies that the assertion function should be called if the assertion adopter is called.

function registerCallTrigger(bytes4 fnSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The function selector of the assertion function.

registerFnCallTrigger

Registers a trigger that fires when the adopter receives a call matching triggerSelector. The assertion fires once per matching call, with TriggerContext available via ph.context().

function registerFnCallTrigger(bytes4 fnSelector, bytes4 triggerSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.
triggerSelectorbytes4The 4-byte selector on the adopter to watch for.

registerTxEndTrigger

Registers a trigger that fires once after the entire transaction completes.

function registerTxEndTrigger(bytes4 fnSelector) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.

registerErc20ChangeTrigger

Registers a trigger that fires when a token’s balances change.

function registerErc20ChangeTrigger(bytes4 fnSelector, address token) external view;

Parameters

NameTypeDescription
fnSelectorbytes4The assertion function to invoke.
tokenaddressThe ERC20 token address to watch.

watchCumulativeOutflow

Registers a circuit breaker trigger that fires when cumulative ERC20 outflow from the assertion adopter exceeds a percentage threshold within a rolling time window.

The executor handles all persistent state tracking, TVL snapshots, and threshold enforcement internally.

function watchCumulativeOutflow(address token, uint256 thresholdBps, uint256 windowDuration, bytes4 fnSelector)
    external
    view;

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address to monitor.
thresholdBpsuint256Maximum cumulative outflow as basis points of the TVL snapshot taken at window start. 1000 = 10%.
windowDurationuint256Rolling window length in seconds. Balance deltas are stored in 10-second buckets; old buckets that fall outside the window are dropped as the window slides forward.
fnSelectorbytes4The assertion function to invoke when the threshold is breached.

watchCumulativeInflow

Registers a circuit breaker trigger that fires when cumulative ERC20 inflow into the assertion adopter exceeds a percentage threshold within a rolling time window.

The executor handles all persistent state tracking, TVL snapshots, and threshold enforcement internally.

function watchCumulativeInflow(address token, uint256 thresholdBps, uint256 windowDuration, bytes4 fnSelector)
    external
    view;

Parameters

NameTypeDescription
tokenaddressThe ERC20 token address to monitor.
thresholdBpsuint256Maximum cumulative inflow as basis points of the TVL snapshot taken at window start. 1000 = 10%.
windowDurationuint256Rolling window length in seconds. Balance deltas are stored in 10-second buckets; old buckets that fall outside the window are dropped as the window slides forward.
fnSelectorbytes4The assertion function to invoke when the threshold is breached.

watchAnomaly

Registers an anomaly-detection trigger. Fires whenever the executor’s configured AnomalySubsystem produces a score for target in a transaction that touches it.

The model owns the firing decision: the trigger fires whenever anomaly detection returns a score at all. The assertion reads the score back via ph.anomalyContext(target) and decides whether to revert, run extra checks, or ignore.

function watchAnomaly(address target, bytes4 fnSelector) external view;

Parameters

NameTypeDescription
targetaddressThe address whose anomaly score this assertion observes.
fnSelectorbytes4The assertion function to invoke when target is scored.