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
Using Foundry (Recommended)
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
| Contract | Description |
|---|---|
Assertion.sol | Base contract for creating assertions with trigger registration |
Credible.sol | Provides access to the PhEvm precompile for transaction state inspection |
PhEvm.sol | Interface for the PhEvm precompile (state forking, logs, call inputs) |
StateChanges.sol | Type-safe utilities for tracking contract state changes |
TriggerRecorder.sol | Interface for registering assertion triggers |
CredibleTest.sol | Base contract for testing assertions with Forge |
CredibleTestWithBacktesting.sol | Extended 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:
| Function | Description |
|---|---|
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:
- trace_filter - Fastest, requires Erigon or archive node with trace API
- debug_traceBlockByNumber - Slower but widely supported
- debug_traceTransaction - Slowest, per-transaction tracing
- 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:
| Result | Description |
|---|---|
| Success | Transaction passed assertion validation |
| Skipped | Transaction didn’t trigger the assertion (selector mismatch) |
| Assertion Failed | Real protocol violation detected |
| Replay Failure | Transaction reverted before assertion could run |
| Unknown Error | Unexpected 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
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
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
| Name | Type | Description |
|---|---|---|
balances | ConservedBalance[] | 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
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
toleranceBpsacross 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
| Name | Type | Description |
|---|---|---|
vaults | ProtectedVault[] | 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
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
| Name | Type | Description |
|---|---|---|
slots | bytes32[] | 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
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):
- If the credible builder set looks offline (the most recent credible block is more
than
failOpenBlockThresholdblocks behind the current block), FAIL OPEN and allow the call. This prevents a stalled builder set from permanently locking the contract. - Otherwise the builder set is live, so the current block MUST be credible; if it is not, the call is blocked with NonCredibleBlock.
- 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
| Name | Type | Description |
|---|---|---|
credibleRegistry_ | ICredibleRegistry | The Credible Registry address (configurable per deployment). |
failOpenBlockThreshold_ | uint256 | Blocks 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
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
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
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The highest block number marked credible so far. |
Contents
Contents
- IERC20MetadataLike
- AaveV3Types
- IAaveV3PoolLike
- IAaveV3AddressesProviderLike
- IAaveV3OracleLike
- AaveV3LikeProtectionSuite
- IERC20MetadataLike
- AaveV3LikeTypes
- IAaveV3LikePool
- IAaveV3LikeAddressesProvider
- IAaveV3LikeOracle
- AaveV3LikeProtectionSuite
- AaveV3LikeOperationSafetyAssertionBase
- AaveV3HorizonProtectionSuite
- AaveV3HorizonOperationSafetyAssertion
- AaveV3ProtectionSuite
- AaveV3OperationSafetyAssertion
- AaveV3PostOperationSolvencyAssertion
- SparkLendV1ProtectionSuite
- SparkLendV1OperationSafetyAssertion
- SparkLendV1PostOperationSolvencyAssertion
IERC20MetadataLike
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
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
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
Functions
getPriceOracle
function getPriceOracle() external view returns (address);
IAaveV3OracleLike
Functions
getAssetPrice
function getAssetPrice(address asset) external view returns (uint256);
AaveV3LikeProtectionSuite
Inherits: LendingProtectionSuiteBase
Title: AaveV3LikeProtectionSuite
Author: Phylax Systems
Shared ILendingProtectionSuite implementation for Aave v3-compatible lending forks.
This adapter matches the interface and accounting model used by forks such as the local Aave v3 Horizon deployment and SparkLend v1. The suite protects the lending surface that can directly worsen an account:
- new borrows and collateral withdrawals must leave the account above Aave’s health-factor liquidation threshold;
- disabling collateral, transferring aTokens, or changing e-mode must not turn a healthy account into a liquidatable one;
- withdrawals and liquidations are bounded by the user’s pre-call claim, debt, and collateral
so a locally successful call cannot consume more value than the pre-state allowed.
These checks intentionally use the pool’s own
getUserAccountDataoutput as the risk source of truth. A failure means the transaction passed protocol execution but left external lending accounting in a state users would experience as bad debt, over-withdrawal, or over-liquidation.
Constants
HEALTH_FACTOR_METRIC
bytes32 internal constant HEALTH_FACTOR_METRIC = 0x4845414c54485f464143544f5200000000000000000000000000000000000000
WITHDRAW_CLAIM_CHECK
bytes32 internal constant WITHDRAW_CLAIM_CHECK = "WITHDRAW_CLAIM"
LIQUIDATION_DEBT_CHECK
bytes32 internal constant LIQUIDATION_DEBT_CHECK = "LIQUIDATION_DEBT"
LIQUIDATION_COLLATERAL_CHECK
bytes32 internal constant LIQUIDATION_COLLATERAL_CHECK = "LIQUIDATION_COLLATERAL"
HEALTH_FACTOR_THRESHOLD
uint256 internal constant HEALTH_FACTOR_THRESHOLD = 1e18
HEALTH_FACTOR_THRESHOLD_INT
int256 internal constant HEALTH_FACTOR_THRESHOLD_INT = 1e18
POOL
address internal immutable POOL
ADDRESSES_PROVIDER
address internal immutable ADDRESSES_PROVIDER
Functions
constructor
Creates an Aave v3-like suite bound to a specific pool.
constructor(address pool_, address addressesProvider_) ;
Parameters
| Name | Type | Description |
|---|---|---|
pool_ | address | Pool address whose accounting and selectors this suite targets. |
addressesProvider_ | address | The pool’s ADDRESSES_PROVIDER. Passed in explicitly because assertions are deployed against an empty state where calling the pool would fail. |
getMonitoredSelectors
Returns the Aave v3-like pool selectors relevant to the shared lending invariants.
The list is intentionally limited to operations that can change debt, effective collateral, liquidation settlement, or the user’s risk category. Supply, repay, and other risk-improving paths are left out to keep the example focused and low noise.
function getMonitoredSelectors() external pure override returns (bytes4[] memory selectors);
decodeOperation
Decodes an Aave v3-like pool call into the shared lending operation model.
The generic lending base only needs a normalized operation: whose risk changed, which asset moved, and whether the call increased debt or reduced effective collateral. Keeping the Aave ABI details here lets the base assertion express protocol-agnostic safety rules.
function decodeOperation(TriggeredCall calldata triggered)
external
pure
override
returns (OperationContext memory operation);
_baseOperation
function _baseOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeBorrowOperation
function _decodeBorrowOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeWithdrawOperation
function _decodeWithdrawOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeLiquidationOperation
function _decodeLiquidationOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeCollateralToggleOperation
function _decodeCollateralToggleOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeFinalizeTransferOperation
function _decodeFinalizeTransferOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
_decodeSetUserEModeOperation
function _decodeSetUserEModeOperation(TriggeredCall calldata triggered)
internal
pure
returns (OperationContext memory operation);
shouldCheckPostOperationSolvency
Filters decoded Aave v3-like operations down to the ones that must preserve solvency.
A post-operation health-factor check is only meaningful for calls that can worsen the account. This avoids tripping on harmless operations and makes each failure actionable: a previously healthy account ended the triggering call below the liquidation threshold.
function shouldCheckPostOperationSolvency(OperationContext calldata operation)
external
pure
override
returns (bool shouldCheck);
getConsumptionChecks
Returns the bounded-consumption checks implied by the decoded Aave v3-like operation.
Solvency alone does not catch value extraction bugs. These checks also assert that a withdraw cannot return more assets than the caller’s pre-call aToken claim, and that a liquidation cannot repay/seize more debt or collateral than existed immediately before it.
function getConsumptionChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view override returns (ConsumptionCheck[] memory checks);
getAccountSnapshot
Reads the post-call snapshot needed by the health-factor solvency invariant.
The lending base calls this at precise pre-call and post-call forks. Using the same pool aggregate view that integrators rely on makes the failure easy to interpret: the protocol’s own account data says the user is no longer solvent.
function getAccountSnapshot(address account, PhEvm.ForkId calldata fork)
external
view
virtual
override
returns (AccountSnapshot memory snapshot);
getAccountState
Reads aggregate account metrics from Pool.getUserAccountData(...).
function getAccountState(address account, PhEvm.ForkId calldata fork)
external
view
override
returns (AccountState memory state);
getAccountBalances
Enumerates reserve balances for the account.
function getAccountBalances(address account, PhEvm.ForkId calldata fork)
external
view
override
returns (AccountBalance[] memory balances);
evaluateSolvency
Evaluates solvency from aggregate account state.
function evaluateSolvency(
AccountState calldata state,
AccountBalance[] calldata balances,
PhEvm.ForkId calldata fork
) external pure override returns (SolvencyState memory solvency);
_getAccountState
Internal helper that reads and normalizes aggregate account data.
function _getAccountState(address account, PhEvm.ForkId memory fork)
internal
view
returns (AccountState memory state);
_getAccountBalances
Internal helper that expands the account into reserve-level balances and values.
function _getAccountBalances(address account, PhEvm.ForkId memory fork)
internal
view
returns (AccountBalance[] memory balances);
_getWithdrawClaimCheck
Builds the withdraw bounded-consumption check from call output and pre-state.
withdraw returns the actual asset amount sent. Comparing that return value with the
caller’s pre-call aToken balance catches accounting bugs where a successful withdrawal
consumes more collateral claim than the account had before the call.
function _getWithdrawClaimCheck(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork
) internal view returns (ConsumptionCheck memory check);
_getLiquidationDebtCheck
Builds the liquidation debt-consumption check from actual debt-asset transfers.
The liquidation input can request type(uint256).max, so the assertion observes the
actual debt-asset transfer instead of trusting calldata. A failure means the liquidator
repaid more debt than the borrower owed in the pre-call snapshot.
function _getLiquidationDebtCheck(
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) internal view returns (ConsumptionCheck memory check);
_getLiquidationCollateralCheck
Builds the liquidation collateral-consumption check from actual collateral transfers.
The seized side differs depending on receiveAToken: liquidators can receive aTokens or
underlying collateral. The check observes the token that actually moved and bounds it by
the borrower’s pre-call collateral claim, protecting users from over-seizure.
function _getLiquidationCollateralCheck(
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) internal view returns (ConsumptionCheck memory check);
_transferredValueDuringCall
Reads token movement for only the triggered call window.
ERC20 transfer precompiles return cumulative logs up to the requested fork, so subtract the pre-call amount from the post-call amount to avoid counting earlier same-tx transfers.
function _transferredValueDuringCall(
address token,
address from,
address to,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) internal view returns (uint256 value);
_evaluateHealthFactor
Converts aggregate metrics into the common solvency representation.
function _evaluateHealthFactor(AccountState memory state) internal pure returns (SolvencyState memory solvency);
_buildAccountBalance
Builds a single reserve-level balance entry for the account.
function _buildAccountBalance(
address asset,
address account,
uint256 userConfigData,
address oracle,
PhEvm.ForkId memory fork
) internal view returns (bool include, AccountBalance memory balance);
_getReserveData
Reads reserve metadata for a single asset at the requested snapshot fork.
function _getReserveData(address asset, PhEvm.ForkId memory fork)
internal
view
returns (AaveV3LikeTypes.ReserveData memory reserveData);
_getUserReserveDebt
Reads the user’s total debt for one reserve from the debt-token balances.
function _getUserReserveDebt(address asset, address account, PhEvm.ForkId memory fork)
internal
view
returns (uint256 debtBalance);
_readOptionalBalance
Reads a token balance when the token address may be unset.
function _readOptionalBalance(address token, address account, PhEvm.ForkId memory fork)
internal
view
returns (uint256 balance);
_valueInBase
Converts an asset-denominated balance into the pool base currency.
function _valueInBase(address oracle, address asset, uint256 balance, PhEvm.ForkId memory fork)
internal
view
returns (uint256);
_isUsingAsCollateral
Returns whether the reserve is enabled as collateral in the user config bitset.
function _isUsingAsCollateral(uint256 userConfigData, uint256 reserveId) internal pure returns (bool);
_toInt256
Safely casts a uint256 metric to int256 for SolvencyState.
function _toInt256(uint256 value) internal pure returns (int256);
Structs
AaveAccountMetrics
Extra aggregate Aave v3-like metrics kept in AccountState.metadata.
struct AaveAccountMetrics {
uint256 availableBorrowsBase;
uint256 currentLiquidationThreshold;
uint256 ltv;
uint256 healthFactor;
}
IERC20MetadataLike
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
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
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
Functions
getPriceOracle
function getPriceOracle() external view returns (address);
IAaveV3LikeOracle
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
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
| Name | Type | Description |
|---|---|---|
pool_ | address | Pool address whose accounting and selectors this suite targets. |
addressesProvider_ | address | The pool’s ADDRESSES_PROVIDER. Passed in explicitly because assertions are deployed against an empty state where calling the pool would fail. |
getMonitoredSelectors
Returns the Aave v3-like pool selectors relevant to the shared lending invariants.
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
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
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
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
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
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
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
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
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
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
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:
- Read monitored selectors from
getMonitoredSelectors(). - Resolve the caller-aware
TriggeredCall. - Decode the triggered call with
decodeOperation(...). - Read operation-specific consumption checks with
getConsumptionChecks(...)and requireconsumed <= availableBeforefor each returned check. - Filter to solvency-relevant operations with
shouldCheckPostOperationSolvency(...). - Read the account snapshot with
getAccountSnapshot(...). - 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
| Name | Type | Description |
|---|---|---|
selectors | bytes4[] | 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
| Name | Type | Description |
|---|---|---|
triggered | TriggeredCall | The exact adopter frame that caused the assertion to run. |
Returns
| Name | Type | Description |
|---|---|---|
operation | OperationContext | Protocol-normalized context used by downstream filtering and checks. |
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
| Name | Type | Description |
|---|---|---|
operation | OperationContext | The decoded operation context returned by decodeOperation(...). |
Returns
| Name | Type | Description |
|---|---|---|
shouldCheck | bool | True when the assertion should read state and enforce 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
| Name | Type | Description |
|---|---|---|
triggered | TriggeredCall | The exact adopter frame that caused the assertion to run. |
operation | OperationContext | The decoded operation context returned by decodeOperation(...). |
beforeFork | PhEvm.ForkId | The pre-call snapshot fork. |
afterFork | PhEvm.ForkId | The post-call snapshot fork. |
Returns
| Name | Type | Description |
|---|---|---|
checks | ConsumptionCheck[] | 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
| Name | Type | Description |
|---|---|---|
account | address | The account whose post-operation solvency is being checked. |
fork | PhEvm.ForkId | The post-call snapshot fork that should be queried. |
Returns
| Name | Type | Description |
|---|---|---|
snapshot | AccountSnapshot | Aggregate state, optional per-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
| Name | Type | Description |
|---|---|---|
account | address | The account whose risk state should be inspected. |
fork | PhEvm.ForkId | The snapshot fork to read from. |
Returns
| Name | Type | Description |
|---|---|---|
state | AccountState | Aggregate 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
| Name | Type | Description |
|---|---|---|
account | address | The account whose positions should be inspected. |
fork | PhEvm.ForkId | The snapshot fork to read from. |
Returns
| Name | Type | Description |
|---|---|---|
balances | AccountBalance[] | 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
| Name | Type | Description |
|---|---|---|
state | AccountState | Aggregate account state returned by getAccountState(...). |
balances | AccountBalance[] | Per-asset balances returned by getAccountBalances(...). |
fork | PhEvm.ForkId | The snapshot fork used for the solvency evaluation. |
Returns
| Name | Type | Description |
|---|---|---|
solvency | SolvencyState | Protocol-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
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
| Name | Type | Description |
|---|---|---|
triggered | TriggeredCall | The exact adopter frame that caused the assertion to run. |
operation | OperationContext | The decoded operation context. |
beforeFork | PhEvm.ForkId | The pre-call snapshot fork. |
afterFork | PhEvm.ForkId | The post-call snapshot fork. |
Returns
| Name | Type | Description |
|---|---|---|
checks | ConsumptionCheck[] | 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
| Name | Type | Description |
|---|---|---|
account | address | The account whose post-operation state should be read. |
fork | PhEvm.ForkId | The snapshot fork to query. |
Returns
| Name | Type | Description |
|---|---|---|
snapshot | AccountSnapshot | Aggregate 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
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
| Name | Type | Description |
|---|---|---|
<none> | ILendingProtectionSuite | suite 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
| Name | Type | Description |
|---|---|---|
suite | ILendingProtectionSuite | The protocol-specific lending suite. |
triggered | ILendingProtectionSuite.TriggeredCall | The exact adopter frame that caused the assertion to run. |
operation | ILendingProtectionSuite.OperationContext | The decoded lending operation. |
beforeFork | PhEvm.ForkId | The pre-call snapshot fork. |
afterFork | PhEvm.ForkId | The 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
| Name | Type | Description |
|---|---|---|
suite | ILendingProtectionSuite | The protocol-specific lending suite. |
triggered | ILendingProtectionSuite.TriggeredCall | The exact adopter frame that caused the assertion to run. |
operation | ILendingProtectionSuite.OperationContext | The decoded lending operation. |
beforeFork | PhEvm.ForkId | The pre-call snapshot fork used for the baseline solvency read. |
afterFork | PhEvm.ForkId | The 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
| Name | Type | Description |
|---|---|---|
triggered | ILendingProtectionSuite.TriggeredCall | Caller-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
Title: IPerpetualProtectionSuite
Author: Phylax Systems
Step-oriented interface for protocol-specific perpetual protection adapters.
Implementations expose the protocol-specific plumbing needed to assert a shared family of perpetual-protocol invariants:
- any non-liquidation user mutation must not create self-bad-debt and must leave the affected account healthy under the protocol’s own mark-to-market risk rule
- taker execution must stay at or worse than the protocol’s externally anchored mark
- open exposure must remain backed by explicit liquidity or liability accounting
- funding settlement must be derived from cumulative-state deltas rather than ad hoc values
- liquidation is the only path that may realize deficit, and it must be gated by pre-state unhealthiness while routing any realized loss into an explicit absorber
- risk-critical transitions must stay anchored to an external oracle or equivalent mark Expected assertion flow:
- Read monitored selectors from
getMonitoredSelectors(). - Resolve the caller-aware
TriggeredCall. - Decode the triggered call with
decodeOperation(...). - Read and enforce any suite-provided execution, liquidity, funding, liquidation, and oracle-anchor checks for the successful call.
- Filter to non-liquidation risk-preserving operations with
shouldCheckPostMutationRisk(...). - Read the post-mutation snapshot with
getPostMutationSnapshot(...). - Require
snapshot.risk.equity >= 0,snapshot.risk.hasBadDebt == false, andsnapshot.risk.isHealthy == true.
Functions
getMonitoredSelectors
Returns the adopter selectors that can participate in the shared perpetual invariants.
function getMonitoredSelectors() external view returns (bytes4[] memory selectors);
Returns
| Name | Type | Description |
|---|---|---|
selectors | bytes4[] | Selectors that should trigger the generic perpetual operation-safety assertion. |
enabledCheckKinds
Returns which optional check families this suite can populate.
The assertion skips disabled families, avoiding cross-contract calls that would only
decode empty arrays. Older suites that do not implement this function are treated as
having every family enabled by PerpetualBaseAssertion.
function enabledCheckKinds() external view returns (EnabledCheckKinds memory enabled);
decodeOperation
Decodes the triggered adopter call into a protocol-normalized operation context.
function decodeOperation(TriggeredCall calldata triggered) external view returns (OperationContext memory operation);
Parameters
| Name | Type | Description |
|---|---|---|
triggered | TriggeredCall | The exact adopter frame that caused the assertion to run. |
Returns
| Name | Type | Description |
|---|---|---|
operation | OperationContext | Protocol-normalized context used by downstream filtering and checks. |
shouldCheckPostMutationRisk
Returns whether the decoded action must preserve post-mutation health.
This should normally be true for non-liquidation user actions that can reduce effective account safety, such as increasing leverage, withdrawing collateral, realizing losses, or removing LP capital against an LP leverage bound.
function shouldCheckPostMutationRisk(OperationContext calldata operation) external view returns (bool shouldCheck);
Parameters
| Name | Type | Description |
|---|---|---|
operation | OperationContext | The decoded operation context returned by decodeOperation(...). |
Returns
| Name | Type | Description |
|---|---|---|
shouldCheck | bool | True when the assertion should read state and enforce the post-state risk rule. |
getExecutionPriceChecks
Returns suite-provided execution-price bounds for the decoded operation.
function getExecutionPriceChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (ExecutionPriceCheck[] memory checks);
getLiquidityCoverageChecks
Returns suite-provided liquidity or liability coverage bounds for the decoded operation.
function getLiquidityCoverageChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (LiquidityCoverageCheck[] memory checks);
getFundingDeltaChecks
Returns suite-provided cumulative-funding settlement bounds for the decoded operation.
function getFundingDeltaChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (FundingDeltaCheck[] memory checks);
getLiquidationChecks
Returns suite-provided liquidation-path bounds for the decoded operation.
function getLiquidationChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (LiquidationCheck[] memory checks);
getOracleAnchorChecks
Returns suite-provided oracle-anchor bounds for the decoded operation.
function getOracleAnchorChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (OracleAnchorCheck[] memory checks);
getAccountingConservationChecks
Returns suite-provided accounting-conservation bounds for the decoded operation.
These checks catch exploit families where a non-liquidation settlement or liquidity-removal path creates unjustified economic gain through LP accounting drift, stale share math, or double-counted PnL.
function getAccountingConservationChecks(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata beforeFork,
PhEvm.ForkId calldata afterFork
) external view returns (AccountingConservationCheck[] memory checks);
getPostMutationSnapshot
Reads the operation-aware snapshot used by the post-mutation risk assertion.
Implementations may return the same result as getAccountSnapshot(...) when their
post-state rule depends only on the account and fork. Protocols with action-specific
postconditions may override this to select the appropriate metric for the decoded
operation.
function getPostMutationSnapshot(
TriggeredCall calldata triggered,
OperationContext calldata operation,
PhEvm.ForkId calldata fork
) external view returns (AccountSnapshot memory snapshot);
getAccountSnapshot
Reads the account snapshot used by the post-mutation risk assertion.
function getAccountSnapshot(address account, PhEvm.ForkId calldata fork)
external
view
returns (AccountSnapshot memory snapshot);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The account whose post-operation risk state is being checked. |
fork | PhEvm.ForkId | The post-call snapshot fork that should be queried. |
Returns
| Name | Type | Description |
|---|---|---|
snapshot | AccountSnapshot | Aggregate state, optional per-market positions, and the final risk result. |
getAccountState
Reads protocol-normalized aggregate account state at a given snapshot fork.
function getAccountState(address account, PhEvm.ForkId calldata fork)
external
view
returns (AccountState memory state);
getAccountPositions
Reads protocol-normalized per-market positions for an account at a snapshot fork.
function getAccountPositions(address account, PhEvm.ForkId calldata fork)
external
view
returns (PositionState[] memory positions);
evaluateRisk
Evaluates the protocol’s post-state risk rule from the decoded account snapshot.
function evaluateRisk(AccountState calldata state, PositionState[] calldata positions, PhEvm.ForkId calldata fork)
external
view
returns (RiskState memory risk);
Structs
TriggeredCall
Resolved information about the exact adopter call that triggered the assertion.
struct TriggeredCall {
/// @notice Function selector invoked on the adopter.
bytes4 selector;
/// @notice Immediate caller of the adopter frame.
address caller;
/// @notice Adopter target address that was called.
address target;
/// @notice Raw calldata for the adopter frame.
bytes input;
/// @notice Call identifier used to construct a PreCall snapshot.
uint256 callStart;
/// @notice Call identifier used to construct a PostCall snapshot.
uint256 callEnd;
}
OperationContext
Protocol-decoded context for a monitored perpetual call.
struct OperationContext {
/// @notice The adopter selector that produced this operation context.
bytes4 selector;
/// @notice The high-level action kind.
OperationKind kind;
/// @notice The immediate caller of the adopter frame.
address caller;
/// @notice The primary account whose risk state should be checked after the operation.
address account;
/// @notice The market, product, or pair being mutated, if any.
address market;
/// @notice Primary collateral or settlement asset involved in the action, if any.
address collateralAsset;
/// @notice Secondary account involved in the action, if any.
address counterparty;
/// @notice Position direction when the operation is market-directional.
bool isLong;
/// @notice Absolute exposure delta requested or realized by the action.
uint256 sizeDelta;
/// @notice Signed collateral delta in protocol-defined units, when known.
int256 collateralDelta;
/// @notice User-specified price bound or execution hint, if any.
uint256 limitPrice;
/// @notice True when the action mutates open exposure.
bool mutatesExposure;
/// @notice True when the action can reduce the account's post-state safety margin.
bool reducesAccountSafety;
/// @notice True when the action is a liquidation or other exceptional bad-debt path.
bool isLiquidation;
/// @notice Extension point for protocol-specific metadata.
bytes metadata;
}
AccountState
Protocol-normalized aggregate mark-to-market state for an account.
struct AccountState {
/// @notice The account whose state was read.
address account;
/// @notice Total collateral or margin value in protocol-defined accounting units.
uint256 collateralValue;
/// @notice Total open notional or equivalent exposure measure.
uint256 openNotional;
/// @notice Aggregate unrealized PnL at the protocol's mark price.
int256 unrealizedPnl;
/// @notice Aggregate unsettled or accrued funding at the protocol's mark state.
int256 accruedFunding;
/// @notice Whether the account currently has any open exposure.
bool hasOpenExposure;
/// @notice Extension point for protocol-specific aggregate data.
bytes metadata;
}
PositionState
Protocol-normalized per-market position data for an account.
struct PositionState {
/// @notice The market, product, or pair represented by this entry.
address market;
/// @notice Collateral or settlement asset associated with the position.
address collateralAsset;
/// @notice Direction of the position when applicable.
bool isLong;
/// @notice Position size in protocol-defined units.
uint256 size;
/// @notice Position notional in protocol-defined units.
uint256 openNotional;
/// @notice Position-level collateral or margin allocation.
uint256 collateralValue;
/// @notice Position PnL at the protocol's mark price.
int256 pnl;
/// @notice Position-level accrued funding at the snapshot.
int256 accruedFunding;
/// @notice Position mark price used by the protocol's risk engine.
uint256 markPrice;
/// @notice Position maintenance requirement in protocol-defined units.
uint256 maintenanceRequirement;
/// @notice Extension point for protocol-specific per-position metadata.
bytes metadata;
}
RiskState
Protocol-defined post-operation risk output for an account at a snapshot fork.
struct RiskState {
/// @notice Whether the account is healthy under the protocol's own post-state rules.
bool isHealthy;
/// @notice Whether the account has entered a self-bad-debt state.
bool hasBadDebt;
/// @notice Whether the protocol would consider the account liquidatable at this snapshot.
bool isLiquidatable;
/// @notice Identifier for the protocol's primary risk metric, e.g. "MARGIN_RATIO".
bytes32 metricName;
/// @notice Mark-to-market account equity after collateral, PnL, and funding.
int256 equity;
/// @notice Protocol-normalized post-state safety metric or equivalent.
int256 metricValue;
/// @notice Threshold compared against `metricValue`.
int256 thresholdValue;
/// @notice Comparison rule used to interpret `metricValue` vs `thresholdValue`.
ComparisonKind comparison;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
AccountSnapshot
Full post-operation snapshot for a monitored account.
struct AccountSnapshot {
/// @notice Aggregate state for the monitored account.
AccountState state;
/// @notice Per-market positions. Implementations may return an empty array on the hot path.
PositionState[] positions;
/// @notice Protocol-defined post-operation risk decision.
RiskState risk;
}
EnabledCheckKinds
Check families populated by this suite for the generic perpetual assertion.
struct EnabledCheckKinds {
bool executionPrice;
bool liquidityCoverage;
bool fundingDelta;
bool liquidation;
bool oracleAnchor;
bool accountingConservation;
}
ExecutionPriceCheck
One concrete taker-price bound that must hold for a successful operation.
struct ExecutionPriceCheck {
/// @notice Identifier for the bound being asserted, e.g. "TAKER_WORSE_THAN_MARK".
bytes32 checkName;
/// @notice The account whose trade is being bounded.
address account;
/// @notice The market whose execution is being inspected.
address market;
/// @notice Actual execution price in protocol-defined price units.
uint256 executionPrice;
/// @notice Inclusive lower bound on the allowed execution price.
uint256 minExecutionPrice;
/// @notice Inclusive upper bound on the allowed execution price.
uint256 maxExecutionPrice;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
LiquidityCoverageCheck
One concrete liquidity or liability coverage bound that must hold after a mutation.
struct LiquidityCoverageCheck {
/// @notice Identifier for the bound being asserted, e.g. "RESERVE_COVERAGE".
bytes32 checkName;
/// @notice The market whose liquidity bucket is being checked.
address market;
/// @notice The pool, vault, insurance fund, or liability bucket used as the source of coverage.
address accountingBucket;
/// @notice Required amount implied by the post-state exposure or liability.
uint256 requiredAmount;
/// @notice Available amount in the backing bucket.
uint256 availableAmount;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
FundingDeltaCheck
One concrete funding-settlement check derived from cumulative funding state.
struct FundingDeltaCheck {
/// @notice Identifier for the bound being asserted, e.g. "FUNDING_SETTLEMENT".
bytes32 checkName;
/// @notice The account whose funding is being inspected.
address account;
/// @notice The market whose funding state is being inspected.
address market;
/// @notice Actual funding charged or credited by the successful operation.
int256 actualFunding;
/// @notice Inclusive lower bound implied by cumulative funding deltas.
int256 minExpectedFunding;
/// @notice Inclusive upper bound implied by cumulative funding deltas.
int256 maxExpectedFunding;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
LiquidationCheck
One concrete liquidation-path check for exceptional bad-debt handling.
struct LiquidationCheck {
/// @notice Identifier for the bound being asserted, e.g. "ONLY_UNHEALTHY_LIQUIDATABLE".
bytes32 checkName;
/// @notice The account being liquidated.
address account;
/// @notice The market whose liquidation path is being inspected.
address market;
/// @notice Whether the account was unsafe before the liquidation executed.
bool wasLiquidatableBefore;
/// @notice Positive realized deficit that the liquidation created, if any.
int256 lossCreated;
/// @notice Amount explicitly absorbed by the loss-bearing account or bucket.
uint256 absorbedLoss;
/// @notice Loss-bearing account or bucket, if any.
address absorber;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
AccountingConservationCheck
One concrete accounting-conservation bound for a non-liquidation settlement path.
Catches exploit families where an operation creates unjustified economic gain through stale LP share math, double-counted PnL, or accounting drift — scenarios that pass a solvency-only check because the account never goes negative.
struct AccountingConservationCheck {
/// @notice Identifier for the bound being asserted, e.g. "EQUITY_CONSERVATION".
bytes32 checkName;
/// @notice The account whose accounting is being inspected.
address account;
/// @notice The market whose accounting path is being inspected.
address market;
/// @notice Actual economic delta observed across the operation (e.g. post-equity minus pre-equity).
int256 actualDelta;
/// @notice Inclusive lower bound on the allowed economic delta.
int256 minAllowedDelta;
/// @notice Inclusive upper bound on the allowed economic delta.
int256 maxAllowedDelta;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
OracleAnchorCheck
One concrete oracle-anchor bound for a risk-critical transition.
struct OracleAnchorCheck {
/// @notice Identifier for the bound being asserted, e.g. "RISK_MARK_ANCHORED".
bytes32 checkName;
/// @notice The market whose oracle anchoring is being inspected.
address market;
/// @notice Actual price used by the protocol for the checked path.
uint256 usedPrice;
/// @notice Inclusive lower bound implied by the external oracle or mark source.
uint256 minOraclePrice;
/// @notice Inclusive upper bound implied by the external oracle or mark source.
uint256 maxOraclePrice;
/// @notice Extension point for protocol-specific evidence or decoded fields.
bytes metadata;
}
Enums
OperationKind
The perpetual action being inspected for shared post-operation safety checks.
enum OperationKind {
Unknown,
IncreasePosition,
DecreasePosition,
DepositCollateral,
WithdrawCollateral,
AddLiquidity,
RemoveLiquidity,
SettleFunding,
RealizePnL,
Liquidation
}
ComparisonKind
Comparison rule for the protocol-defined post-mutation risk metric.
enum ComparisonKind {
Unknown,
Gte,
Gt
}
PerpetualProtectionSuiteBase
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
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
- IERC165
- ITransactionGuard
- CredibleSafeGuard
- ICredibleRegistry
- SafeConfigLockAssertion
- ISafeConfigLockTarget
- SafeConfigLockHelpers
- SafeTxShapeAssertion
- SafeTxShapeHelpers
Enum
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
ERC-165 interface (EIP-165 / OpenZeppelin IERC165).
Functions
supportsInterface
function supportsInterface(bytes4 interfaceId) external view returns (bool);
ITransactionGuard
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
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:
- If the current block is credible, the transaction is always allowed.
- Otherwise, if the credible builder set looks offline (the most recent credible block
is more than
failOpenBlockThresholdblocks behind the current block), FAIL OPEN and allow the transaction. This prevents a stalled builder set from permanently locking the Safe. - 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
setGuardagain.
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
| Name | Type | Description |
|---|---|---|
credibleRegistry_ | ICredibleRegistry | The Credible Registry address (configurable per deployment). |
failOpenBlockThreshold_ | uint256 | Blocks 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
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
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
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The highest block number marked credible so far. |
SafeConfigLockAssertion
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
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
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
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
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
- CowSettlementHelpers
- IGPv2SettlementLike
- IERC20BalanceLike
- UniswapV4PoolManagerAssertion
- UniswapV4PoolManagerHelpers
- IUniswapV4PoolManagerLike
CowSettlementAssertion
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.
- Surplus protection (
assertSolverDoesNotExtractValue, persettle/swapcall): 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. - 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 GPv2Tradevolume 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; andTradeevents 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
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
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
Title: IERC20BalanceLike
Minimal ERC20 balance surface for snapshot reads.
Functions
balanceOf
function balanceOf(address account) external view returns (uint256);
UniswapV4PoolManagerAssertion
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
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
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);
donate
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
- examples
- ERC4626AssetFlowAssertion
- ERC4626BaseAssertion
- ERC4626CumulativeOutflowAssertion
- ERC4626PreviewAssertion
- ERC4626SharePriceAssertion
- IERC4626
Contents
MetaMorphoVaultAssertion
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
| Name | Type | Description |
|---|---|---|
vault_ | address | MetaMorpho vault instance whose selectors this bundle will monitor. |
asset_ | address | Underlying ERC-20 asset of the vault. |
sharePriceToleranceBps_ | uint256 | Max share-price drift tolerated by ERC4626SharePriceAssertion, in basis points. |
outflowThresholdBps_ | uint256 | Cumulative net-outflow limit as bps of TVL enforced by ERC4626CumulativeOutflowAssertion over the rolling window. |
outflowWindowDuration_ | uint256 | Rolling 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
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
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
| Name | Type | Description |
|---|---|---|
_vault | address | The ERC-4626 vault being monitored (assertion adopter). |
_asset | address | The 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
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
outflowThresholdBpsof the TVL snapshot within a rollingoutflowWindowDuration.
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
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
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
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
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
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
| Name | Type | Description |
|---|---|---|
data | bytes | The error data from a failed call |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The 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
| Name | Type | Description |
|---|---|---|
data | bytes | The bytes to convert |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | The hex string representation |
getErrorTypeString
Get human-readable error type string from validation result
function getErrorTypeString(BacktestingTypes.ValidationResult result) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
result | BacktestingTypes.ValidationResult | The validation result enum |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The 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
| Name | Type | Description |
|---|---|---|
str | string | The string to check |
prefix | string | The prefix to look for |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if str starts with prefix |
getDefaultScriptSearchPaths
Get the standard search paths for transaction_fetcher.sh
function getDefaultScriptSearchPaths() internal pure returns (string[] memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string[] | Array of paths to check, in order of preference |
IERC20BalanceReaderLike
Minimal token balance surface used by fork-read helpers.
Functions
balanceOf
function balanceOf(address account) external view returns (uint256);
ForkUtils
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
| Name | Type | Description |
|---|---|---|
target | address | The protocol contract to query. |
data | bytes | ABI-encoded calldata for the target view. |
fork | PhEvm.ForkId | The snapshot fork the read should execute against. |
Returns
| Name | Type | Description |
|---|---|---|
resultData | bytes | Raw 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
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
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
| Name | Type | Description |
|---|---|---|
found | bool | True if a matching log was found. |
log | PhEvm.Log | The 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
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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
triggerSelector | bytes4 | The function selector upon which the assertion will be triggered. |
registerStorageChangeTrigger
Registers storage change trigger for any slot
function registerStorageChangeTrigger(bytes4 fnSelector) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
registerStorageChangeTrigger
Registers storage change trigger for a specific slot
function registerStorageChangeTrigger(bytes4 fnSelector, bytes32 slot) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
slot | bytes32 | The storage slot to trigger on. |
registerBalanceChangeTrigger
Registers balance change trigger for the AA
function registerBalanceChangeTrigger(bytes4 fnSelector) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The assertion function to invoke. |
triggerSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The assertion function to invoke. |
token | address | The 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address to monitor. |
thresholdBps | uint256 | Maximum cumulative outflow as basis points of the TVL snapshot taken at window start. 1000 = 10%. |
windowDuration | uint256 | Rolling window length in seconds. |
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address to monitor. |
thresholdBps | uint256 | Maximum cumulative inflow as basis points of the TVL snapshot taken at window start. 1000 = 10%. |
windowDuration | uint256 | Rolling window length in seconds. |
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
target | address | The address whose anomaly score this assertion observes. |
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
spec | AssertionSpec | The desired AssertionSpec. |
console
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
| Name | Type | Description |
|---|---|---|
message | string | The message to log |
Credible
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
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
| Name | Type | Description |
|---|---|---|
adopter | address | The address of the contract that adopts the assertion |
createData | bytes | The creation bytecode of the assertion contract |
fnSelector | bytes4 | The function selector of the assertion function to test |
CredibleTest
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
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
| Name | Type | Description |
|---|---|---|
txHash | bytes32 | The transaction hash to backtest |
targetContract | address | The target contract address |
assertionCreationCode | bytes | The assertion contract creation code |
assertionSelector | bytes4 | The assertion function selector |
rpcUrl | string | The RPC URL to use |
Returns
| Name | Type | Description |
|---|---|---|
results | BacktestingTypes.BacktestingResults | The 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
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
<none> | string | The 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
| Name | Type | Description |
|---|---|---|
<none> | string | The 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
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
| Name | Type | Description |
|---|---|---|
slot | bytes32 | The storage slot to read. |
fork | ForkId | The snapshot fork to read from. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes32 | The 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
| Name | Type | Description |
|---|---|---|
target | address | The address to read storage from. |
slot | bytes32 | The storage slot to read. |
fork | ForkId | The snapshot fork to read from. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes32 | The 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
| Name | Type | Description |
|---|---|---|
target | address | The contract to call. |
data | bytes | The ABI-encoded function call. |
gas_limit | uint64 | The gas budget forwarded to the nested static call. |
fork | ForkId | The snapshot fork to execute against. |
Returns
| Name | Type | Description |
|---|---|---|
result | StaticCallResult | Success 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
| Name | Type | Description |
|---|---|---|
query | LogQuery | The emitter and signature filters to apply |
fork | ForkId | The snapshot fork to read logs from |
Returns
| Name | Type | Description |
|---|---|---|
logs | Log[] | 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address. |
fork | ForkId | The fork to query. |
Returns
| Name | Type | Description |
|---|---|---|
transfers | Erc20TransferData[] | 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
| Name | Type | Description |
|---|---|---|
tokens | address[] | Array of ERC20 token addresses. |
fork | ForkId | The fork to query. |
Returns
| Name | Type | Description |
|---|---|---|
transfers | Erc20TransferData[] | 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address. |
fork | ForkId | The 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address. |
fork | ForkId | The fork to query. |
Returns
| Name | Type | Description |
|---|---|---|
deltas | Erc20TransferData[] | 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
| Name | Type | Description |
|---|---|---|
logs | Log[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The target contract address |
selector | bytes4 | The function selector to filter by |
Returns
| Name | Type | Description |
|---|---|---|
calls | CallInputs[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The target contract address |
selector | bytes4 | The function selector to filter by |
Returns
| Name | Type | Description |
|---|---|---|
calls | CallInputs[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The target contract address |
selector | bytes4 | The function selector to filter by |
Returns
| Name | Type | Description |
|---|---|---|
calls | CallInputs[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The target/proxy contract address |
selector | bytes4 | The function selector to filter by |
Returns
| Name | Type | Description |
|---|---|---|
calls | CallInputs[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The target contract address |
selector | bytes4 | The function selector to filter by |
Returns
| Name | Type | Description |
|---|---|---|
calls | CallInputs[] | 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
| Name | Type | Description |
|---|---|---|
callId | uint256 | The call identifier from CallInputs.id. |
Returns
| Name | Type | Description |
|---|---|---|
output | bytes | The 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
| Name | Type | Description |
|---|---|---|
callId | uint256 | The call ID to read input from. |
Returns
| Name | Type | Description |
|---|---|---|
input | bytes | The 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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract whose storage to inspect |
slot | bytes32 | The storage slot to get changes for |
Returns
| Name | Type | Description |
|---|---|---|
stateChanges | bytes32[] | 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
| Name | Type | Description |
|---|---|---|
slot | bytes32 | The slot to protect. |
Returns
| Name | Type | Description |
|---|---|---|
ok | bool | True 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
| Name | Type | Description |
|---|---|---|
slots | bytes32[] | The slots to protect. |
Returns
| Name | Type | Description |
|---|---|---|
ok | bool | True 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
| Name | Type | Description |
|---|---|---|
<none> | address | The 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
| Name | Type | Description |
|---|---|---|
txObject | TxObject | The 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
| Name | Type | Description |
|---|---|---|
target | address | The target contract address. |
selector | bytes4 | The function selector to filter by. |
filter | CallFilter | Filtering criteria (call type, depth, success). |
limit | uint256 | Maximum number of results to return. |
Returns
| Name | Type | Description |
|---|---|---|
calls | TriggerCall[] | 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
| Name | Type | Description |
|---|---|---|
query | LogQuery | The emitter and signature filters to apply. |
callId | uint256 | The call ID to scope the log query to. |
Returns
| Name | Type | Description |
|---|---|---|
logs | Log[] | Array of logs emitted during the call. |
store
Write a bytes32 value to persistent assertion storage.
function store(bytes32 key, bytes32 value) external;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The storage key. |
value | bytes32 | The value to store. |
load
Read a bytes32 value from persistent assertion storage.
function load(bytes32 key) external view returns (bytes32 value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The storage key. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes32 | The stored value. |
exists
Check if a key exists in persistent assertion storage.
function exists(bytes32 key) external view returns (bool doesExist);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The storage key. |
Returns
| Name | Type | Description |
|---|---|---|
doesExist | bool | True 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
| Name | Type | Description |
|---|---|---|
target | address | The contract whose storage was modified. |
baseSlot | bytes32 | The Solidity mapping’s base storage slot. |
Returns
| Name | Type | Description |
|---|---|---|
keys | bytes[] | 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
| Name | Type | Description |
|---|---|---|
target | address | The contract address. |
baseSlot | bytes32 | The mapping’s base slot. |
key | bytes | The canonical encoding h(key) of the mapping key. |
fieldOffset | uint256 | Struct field offset (0 for the first slot of the value). |
Returns
| Name | Type | Description |
|---|---|---|
pre | bytes32 | The PreTx value. |
post | bytes32 | The PostTx value. |
changed | bool | True if pre != post. |
assetsMatchSharePrice
Checks ERC4626 share price consistency across all fork points.
function assetsMatchSharePrice(address vault, uint256 toleranceBps) external view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | The ERC4626 vault address. |
toleranceBps | uint256 | Maximum allowed deviation in basis points. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
vault | address | The ERC4626 vault address. |
toleranceBps | uint256 | Maximum allowed deviation in basis points. |
fork0 | ForkId | The baseline fork. |
fork1 | ForkId | The comparison fork. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
fork0 | ForkId | The baseline fork. |
fork1 | ForkId | The comparison fork. |
token | address | The ERC20 token address. |
account | address | The account whose balance should remain unchanged. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
ctx | OutflowContext | The 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
| Name | Type | Description |
|---|---|---|
ctx | InflowContext | The 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
| Name | Type | Description |
|---|---|---|
ctx | FlowRateContext | The 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
| Name | Type | Description |
|---|---|---|
ctx | FlowRateContext | The 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
| Name | Type | Description |
|---|---|---|
target | address | The address whose anomaly context to read. |
Returns
| Name | Type | Description |
|---|---|---|
ctx | AnomalyContext | The 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
| Name | Type | Description |
|---|---|---|
target | address | The oracle contract address. |
data | bytes | The ABI-encoded oracle query. |
bpsDeviation | uint256 | Maximum allowed deviation in basis points. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
| Name | Type | Description |
|---|---|---|
target | address | The oracle contract address. |
data | bytes | The ABI-encoded oracle query. |
bpsDeviation | uint256 | Maximum allowed deviation in basis points. |
initialFork | ForkId | The baseline fork. |
currentFork | ForkId | The comparison fork. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True 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
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
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
| Name | Type | Description |
|---|---|---|
spec | AssertionSpec | The desired AssertionSpec. |
StateChanges
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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The address of the contract to get state changes from |
slot | bytes32 | The storage slot to get state changes for |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The address of the contract to get state changes from |
slot | bytes32 | The storage slot to get state changes for |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The address of the contract to get state changes from |
slot | bytes32 | The storage slot to get state changes for |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The address of the contract to get state changes from |
slot | bytes32 | The storage slot to get state changes for |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
slot | bytes32 | The base storage slot of the mapping |
key | uint256 | The key in the mapping |
offset | uint256 | Additional offset to add to the calculated slot |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | The 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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
slotOffset | uint256 | Additional offset to add to the slot |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
slotOffset | uint256 | Additional offset to add to the slot |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
slotOffset | uint256 | Additional offset to add to the slot |
Returns
| Name | Type | Description |
|---|---|---|
<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
| Name | Type | Description |
|---|---|---|
contractAddress | address | The contract address |
slot | bytes32 | The mapping’s slot |
key | uint256 | The mapping key |
slotOffset | uint256 | Additional offset to add to the slot |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32[] | Array of state changes as bytes32 values |
TriggerRecorder
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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
registerStorageChangeTrigger
Registers storage change trigger for a slot
function registerStorageChangeTrigger(bytes4 fnSelector, bytes32 slot) external view;
Parameters
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
slot | bytes32 | The storage slot to trigger on. |
registerBalanceChangeTrigger
Registers balance change trigger for the AA
function registerBalanceChangeTrigger(bytes4 fnSelector) external view;
Parameters
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The function selector of the assertion function. |
triggerSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The assertion function to invoke. |
triggerSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
fnSelector | bytes4 | The assertion function to invoke. |
token | address | The 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address to monitor. |
thresholdBps | uint256 | Maximum cumulative outflow as basis points of the TVL snapshot taken at window start. 1000 = 10%. |
windowDuration | uint256 | Rolling 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. |
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
token | address | The ERC20 token address to monitor. |
thresholdBps | uint256 | Maximum cumulative inflow as basis points of the TVL snapshot taken at window start. 1000 = 10%. |
windowDuration | uint256 | Rolling 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. |
fnSelector | bytes4 | The 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
| Name | Type | Description |
|---|---|---|
target | address | The address whose anomaly score this assertion observes. |
fnSelector | bytes4 | The assertion function to invoke when target is scored. |