Use this tutorial to learn the basic shape of an assertion. If you already understand the workflow and need specific syntax, jump to Triggers and the Cheatcodes API Reference.
pcl installed.
What you’ll build: An assertion that blocks any transaction attempting to change a contract’s owner.
The Example Contract
We’ll protect a simple ownership contract:Step 1: Set Up Your Project
Clone the starter repository:The starter repository includes the complete ownership assertion example from this guide. You can follow along or explore the finished code directly.
Step 2: Write the Assertion
Createassertions/src/OwnableAssertion.a.sol:
How It Works
This assertion compares the owner before and after the transaction. If the owner changed, therequire fails, the assertion reverts, and the transaction is dropped from the block.
In general: if an assertion reverts, the transaction is blocked. This prevents attacks entirely rather than just detecting them.
Key Components
Imports and inheritance:Assertion base class provides cheatcodes via the ph namespace.
Triggers:
assertionOwnershipChange runs whenever transferOwnership is called.
Key points about triggers:
- Each assertion function must be registered via its selector
- You can define multiple assertion functions in one contract
- Each trigger maps to exactly one assertion function
- Use triggers to run assertions only when needed (saves gas)
ph.getAssertionAdopter(): Returns the protected contract’s addressPhEvm.ForkId: Identifies the pre-transaction and post-transaction snapshotsph.loadStateAt(): Reads the protected contract’s owner slot at the requested snapshot- The
requireblocks the transaction if ownership changed
Best Practices
- Single responsibility: Each assertion should verify one property
- Use triggers efficiently: Only run assertions when relevant functions are called
- Return early: Check simple conditions before complex logic
- Use explicit
ForkIds: For checks around a matched call, useph.context()and constructPhEvm.ForkId({forkType: 2, callIndex: ctx.callStart})orPhEvm.ForkId({forkType: 3, callIndex: ctx.callEnd}).
Step 3: Test the Assertion
Createassertions/test/OwnableAssertion.t.sol:
Key Testing Concepts
cl.assertion(): Registers the assertion to run on the next transactionvm.expectRevert(): Verifies the assertion blocks the transaction- Test both cases: invalid (ownership changes) and valid (no change)
Recap
You’ve built a complete assertion:- Set up: Clone the starter repo with correct structure
- Write: Create assertion with triggers and validation logic
- Test: Verify it blocks attacks and allows normal operations
Video Walkthrough
For a visual walkthrough of assertion development:Next Steps
Apply Assertions
Deploy your assertions with pcl apply
From Invariant to Assertion
Learn to write complex real-world assertions
Cheatcodes Reference
All available assertion cheatcodes
Assertions Book
More assertion examples

