implementing-assertions

implementing-assertions

Phylax Credible Layer assertions implementation. Implements phylax/credible layer assertion contracts using cheatcodes, triggers, and event/state inspection.

0
0포크
업데이트됨 1/21/2026
SKILL.md
readonlyread-only
name
implementing-assertions
description

"Phylax Credible Layer assertions implementation. Implements phylax/credible layer assertion contracts using cheatcodes, triggers, and event/state inspection."

Implementing Assertions

Turn a written invariant spec into a correct, gas-safe assertion contract.

Meta-Cognitive Protocol

Adopt the role of a Meta-Cognitive Reasoning Expert.

For every complex problem:
1.DECOMPOSE: Break into sub-problems
2.SOLVE: Address each with explicit confidence (0.0-1.0)
3.VERIFY: Check logic, facts, completeness, bias
4.SYNTHESIZE: Combine using weighted confidence
5.REFLECT: If confidence <0.8, identify weakness and retry
For simple questions, skip to direct answer.

Always output:
∙Clear answer
∙Confidence level
∙Key caveats

When to Use

  • Writing a new assertion contract from a defined invariant.
  • Refactoring or optimizing existing assertions.
  • Adding trigger logic, call input parsing, or event/state checks.

When NOT to Use

  • You only need invariant ideation or trigger selection. Use designing-assertions.
  • You only need testing patterns. Use testing-assertions.

Quick Start

Setup:

File and Naming Conventions

  • Assertion files: {ContractOrFeature}Assertion.a.sol (e.g., VaultOwnerAssertion.a.sol)
  • Test files: {ContractOrFeature}Assertion.t.sol (e.g., VaultOwnerAssertion.t.sol)
  • Assertion functions: must start with assertion followed by the property name (e.g., assertionOwnershipChange, assertionHealthFactor, assertionSupplyCap)
  • Directory structure: assertions/src/ for assertion contracts, assertions/test/ for tests
contract MyAssertion is Assertion {
    function triggers() external view override {
        registerCallTrigger(this.assertionMonotonic.selector, ITarget.doThing.selector);
    }

    function assertionMonotonic() external {
        ITarget target = ITarget(ph.getAssertionAdopter());
        ph.forkPreTx();
        uint256 pre = target.totalAssets();
        ph.forkPostTx();
        uint256 post = target.totalAssets();
        require(post >= pre, "Invariant violated");
    }
}

Implementation Checklist

  • Triggers: use the narrowest possible trigger; avoid global triggers (registerCallTrigger(fn) or registerStorageChangeTrigger(fn) without a selector/slot).
  • One Trigger, One Assertion: avoid multi-selector dispatchers; register each selector to its own assertion function and reuse shared helpers.
  • Interface Clarity: use selectors from the interface that declares the function; if the adopter inherits it (e.g., ERC20/4626), call that out so the trigger source is obvious.
  • Pre/Post: call forkPreTx() only when needed; default is post-state.
  • Call-Scoped Checks: use getCallInputs + forkPreCall/forkPostCall for per-call invariants.
  • Call Input Shape: CallInputs.input contains args only (selector is stripped). Decode args directly; if you need msg.data (e.g., timelock keys), rebuild with abi.encodePacked(selector, input).
  • Preconditions: use before* hook triggers or forkPreCall to assert pre-state requirements.
  • Baselines: for intra-tx stability, read forkPreTx() once and compare per-call post snapshots.
  • Event Parsing: filter by emitter and topics[0]; decode indexed vs data fields correctly.
  • Storage Slots: use ph.load (not vm.load) for EIP-1967 slots, packed fields, and mappings; derive slots via forge inspect <Contract> storage-layout.
  • State Changes: getStateChanges* includes the initial value at index 0; length 0 means no changes.
  • Constructors: cheatcodes are unavailable; prefer ph.getAssertionAdopter() inside assertion functions and pass only constants via constructor args if needed.
  • Nested Calls: avoid double counting; prefer getCallInputs to avoid proxy duplicates.
  • Internal Calls: internal Solidity calls are not traced; register on external entrypoints (or this. calls) when you need call inputs.
  • Batch Dedupe: deduplicate targets/accounts when a batch can repeat entries.
  • Tolerances: use minimal, documented tolerances for price/decimals rounding.
  • Optional Interfaces: use staticcall probing and skip when unsupported.
  • Token Quirks: validate using balance deltas; handle fee-on-transfer and rebasing tokens.
  • Packed Calldata: decode using protocol packing logic (assetId, amount, mode) and map ids via helpers.
  • Delta-Based Supply Checks: compare totalSupply delta to sum of per-call amounts instead of enumerating users.
  • Id Mapping Guards: if a packed id maps to address(0), skip or fail early to avoid false positives.
  • Sentinel Amounts: normalize max/sentinel values (e.g., full repay/withdraw) using pre-state.
  • Gas: assertion gas cap is 300k; happy path is often most expensive; early return, cache reads, and limit loops.
  • Size Limit: organize assertions by domain (e.g., access control, timelock, accounting) and split if you hit CreateContractSizeLimit.

Anti-Patterns

❌ Dispatcher Pattern (Avoid)

Do not route multiple triggers through one assertion function that dispatches to helpers:

// ❌ WRONG: Many triggers → one dispatcher → helpers
function triggers() external view override {
    registerCallTrigger(this.assertionOwnership.selector, IVault.setFee.selector);
    registerCallTrigger(this.assertionOwnership.selector, IVault.setGuardian.selector);
    registerCallTrigger(this.assertionOwnership.selector, IVault.submitCap.selector);
}

function assertionOwnership() external {
    // Dispatches internally based on what was called - hard to debug, wastes gas
}

✅ One Trigger, One Assertion (Preferred)

Register each trigger to its own assertion function. Share logic via internal helpers:

// ✅ CORRECT: Each trigger has its own assertion function
function triggers() external view override {
    registerCallTrigger(this.assertionSetFee.selector, IVault.setFee.selector);
    registerCallTrigger(this.assertionSetGuardian.selector, IVault.setGuardian.selector);
    registerCallTrigger(this.assertionSubmitCap.selector, IVault.submitCap.selector);
}

function assertionSetFee() external {
    _checkOnlyOwner(); // Reuse helper
}

function assertionSetGuardian() external {
    _checkOnlyOwner(); // Reuse helper
}

❌ Mixed Interfaces (Avoid)

Do not mix selectors from parent and child interfaces:

// ❌ CONFUSING: Mixing IERC4626 and IVault when IVault extends IERC4626
registerCallTrigger(this.assertionDeposit.selector, IERC4626.deposit.selector);
registerCallTrigger(this.assertionSubmitCap.selector, IVault.submitCap.selector);

✅ Consistent Interface (Preferred)

Use the adopter's interface consistently:

// ✅ CLEAR: Use IVault for everything since it extends IERC4626
registerCallTrigger(this.assertionDeposit.selector, IVault.deposit.selector);
registerCallTrigger(this.assertionSubmitCap.selector, IVault.submitCap.selector);

Rationalizations to Reject

  • "Use getAllCallInputs everywhere." It can double-count proxy calls.
  • "Many selectors can share one assertion dispatcher." It hurts gas and makes debugging harder.
  • "I can ignore nested calls." Batched flows are common and must be handled.
  • "Events are enough." If events can be skipped, back them with state checks.
  • "We can rely on storage layout guesses." Always derive slots from layout.

References

You Might Also Like

Related Skills

coding-agent

coding-agent

179Kdev-codegen

Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.

openclaw avataropenclaw
받기
add-uint-support

add-uint-support

97Kdev-codegen

Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.

pytorch avatarpytorch
받기
at-dispatch-v2

at-dispatch-v2

97Kdev-codegen

Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.

pytorch avatarpytorch
받기
skill-writer

skill-writer

97Kdev-codegen

Guide users through creating Agent Skills for Claude Code. Use when the user wants to create, write, author, or design a new Skill, or needs help with SKILL.md files, frontmatter, or skill structure.

pytorch avatarpytorch
받기

Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.

oven-sh avataroven-sh
받기

Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.

oven-sh avataroven-sh
받기