Skip to main content

Write and deploy assertions

The Credible Layer is security infrastructure built by Phylax Systems that lets you define security rules for your smart contracts and enforce them at the network level. When a transaction violates your rules, it's dropped before it can executeβ€”preventing exploits rather than detecting them after the damage is done.

You write rules in Solidity (called assertions), and no contract modifications are required. This means you can add protection to any contract, including immutable ones.

How the Credible Layer works​

The Credible Layer runs as a sidecar alongside the network's sequencer. When you deploy assertions, they're registered on-chain and the sidecar validates every transaction against them during block production.

The flow:

  1. User submits a transaction to the network
  2. The sequencer passes it to the Assertion Enforcer (sidecar) for validation
  3. The sidecar simulates the transaction and runs all relevant assertions
  4. If assertions pass β†’ transaction is included in the block
  5. If any assertion fails β†’ transaction is dropped and never executes.

This flow adds no new trust assumptions: if you trust the network to include your transactions, you can trust it to enforce your assertions.

For full architecture details, see Architecture Overview.

What assertions can do​

Assertions have capabilities that regular Solidity contracts don't, including:

  • Compare before/after states β€” Use ph.forkPreTx() and ph.forkPostTx() to check how a transaction changes state
  • Protect immutable contracts β€” Add security rules without modifying or redeploying contracts
  • Inspect nested calls β€” Use ph.forkPreCall() and ph.forkPostCall() to examine state around specific function calls within a transaction
  • Run complex validation β€” Execute assertions off-chain with higher gas limits than typical on-chain operations.

Use cases​

Assertions can protect against a wide range of issues:

  • Parameter protection β€” Prevent unauthorized changes to owner addresses, implementation contracts, or protocol settings
  • Price manipulation β€” Ensure prices don't move beyond specified thresholds in a single transaction
  • Lending safety β€” Guarantee positions maintain required collateralization ratios
  • Balance invariants β€” Verify that token balances remain consistent (e.g., sum of positions equals total supply)
  • Oracle validation β€” Detect suspicious oracle price deviations.

For real-world examples, see the Assertions Book and Previous Hacks that assertions would have prevented.

Quick start​

The following guide walks you through creating a project and deploying assertions with the Credible Layer.

Prerequisites​

Before you begin, make sure you have:

  • Foundry installed
  • The pcl CLI installed (see below)
  • A way to sign transactions in your browser (MetaMask or similar).

Install pcl​

brew tap phylaxsystems/pcl
brew install phylax

Verify installation:

pcl --version

For other installation methods, see the Installation Guide.

Clone the starter project​

To get started, clone the starter project:

git clone --recurse-submodules https://github.com/phylaxsystems/credible-layer-starter
cd credible-layer-starter

This includes example contracts and assertions you can deploy immediately.

Understand the ownable assertion contract​

The starter project includes an assertion that prevents unauthorized ownership changes. Here's what it looks like:

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

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

contract OwnableAssertion is Assertion {
function triggers() external view override {
registerCallTrigger(this.assertionOwnershipChange.selector, Ownable.transferOwnership.selector);
}

function assertionOwnershipChange() external {
Ownable ownable = Ownable(ph.getAssertionAdopter());

ph.forkPreTx();
address preOwner = ownable.owner();

ph.forkPostTx();
address postOwner = ownable.owner();

require(postOwner == preOwner, "Ownership has changed");
}
}

How it works:

  • triggers() registers when the assertion runsβ€”here, whenever transferOwnership is called
  • ph.forkPreTx() switches to the state before the transaction
  • ph.forkPostTx() switches to the state after the transaction
  • If the owner changed, the require fails and the transaction is dropped.

For a detailed walkthrough, see Write Your First Assertion.

To test this assertion run the following:

pcl test

You should see output like:

[PASS] test_assertionOwnershipChanged() (gas: 806650)
[PASS] test_assertionOwnershipNotChanged() (gas: 804708)
Suite result: ok. 2 passed; 0 failed; 0 skipped

For more on testing, see Testing Assertions.

Deploy your contract​

Deploy the Ownable contract to the network:

forge script script/DeployOwnable.s.sol \
--rpc-url <RPC_URL> \
--sender <DEPLOYER_ADDRESS> \
--private-key <PRIVATE_KEY> \
--broadcast

Note the Deployed to: address, you'll need it later.

Authenticate and create a project​

To authenticate with the Credible Layer, run:

pcl auth login

This opens a browser window. Sign in and create a new project, then link your deployed contract address to it.

Store and submit your assertion​

To deploy an assertion, you first need to store the assertion in the Assertion Data Availability layer:

pcl store OwnableAssertion

Then, submit it to your project:

pcl submit -a 'OwnableAssertion' -p <project_name>

Replace <project_name> with the name of the project you created.

Deploy the assertion​

Return to the browser window from pcl auth login and navigate to your project. You'll see the assertion ready for deployment. Review and deploy it to activate protection.

After a timelock period, your assertion becomes enforced and will actively protect your contract.

For the full deployment process, see Deploy Assertions.

Verify it works​

Once the assertion is enforced, test that it's protecting your contract:

# Check current owner
cast call <CONTRACT_ADDRESS> "owner()" --rpc-url <RPC_URL>

# Attempt to transfer ownership (this should timeout/fail)
cast send <CONTRACT_ADDRESS> "transferOwnership(address)" <NEW_ADDRESS> \
--rpc-url <RPC_URL> \
--private-key <OWNER_PRIVATE_KEY>

The ownership transfer should timeout, confirming the assertion blocked it.

Learn more​

Phylax community & support​