Skip to main content
An assertion is a function that checks if a transaction violates a rule. If the rule is violated, the transaction is dropped during block building and never enters a block. Assertions are most useful when they describe an invalid effect, not every possible path that could produce it. A protocol may not know whether a future failure comes from an oracle issue, signer compromise, integration bug, rounding error, or governance action, but it can still define the state transition that should not be allowed.

How Assertions Work

When a transaction is submitted to the network:
  • The system identifies which assertions apply to the contracts being interacted with
  • Assertions are configured with triggers that specify when they should execute (on specific function calls, storage changes, or balance changes)
  • The PhEVM simulates the transaction and creates pre/post state snapshots
  • All relevant assertions execute against these states
  • If all assertions pass → transaction is included; if any fail → transaction is dropped

Example: Prevent Unauthorized Ownership Changes

function checkOwnershipChange() external view {
    PhEvm.ForkId memory preTx = _preTx();
    PhEvm.ForkId memory postTx = _postTx();

    address ownerBefore = address(uint160(uint256(ph.loadStateAt(address(protocol), bytes32(uint256(0)), preTx))));
    address ownerAfter = address(uint160(uint256(ph.loadStateAt(address(protocol), bytes32(uint256(0)), postTx))));

    require(ownerBefore == ownerAfter, "Owner cannot change");
}
When a transaction tries to change the owner, this assertion runs. If the owner changed, the transaction is dropped.

What They Can Do That Regular Solidity Can’t

Enforce effects instead of hooks Contract-level checks usually protect the code paths where they were added. Assertions can protect based on the resulting transaction effects, such as balance deltas, storage changes, nested calls, or debt changes. This helps cover paths that were not anticipated when the protocol contracts were originally written. Compare before/after states Regular Solidity only sees the current state during execution. Assertions can compare values before and after a transaction using cheatcodes like ph.loadStateAt with explicit ForkId snapshots. You can also inspect state around a specific matched call by combining ph.context() with call-scoped ForkId values. Protect immutable contracts You can add assertions to contracts without modifying them. No redeployment needed. Keep policy separate from protocol mechanics Assertions let teams review security rules separately from accounting, settlement, permissioning, and integration logic. This can make it easier to audit what the rule is meant to prevent and to update the rule without upgrading the protected contract. Check across multiple contracts An assertion can read and validate state across multiple contracts in a single check, even if those contracts are in different protocols. Run off-chain with higher gas limits Assertions run off-chain with higher gas limits than typical on-chain operations, allowing for complex computations that would be too expensive in regular smart contracts.

Common Use Cases

  • Prevent unauthorized parameter changes (owner, implementation addresses)
  • Enforce proportionality rules (withdrawals match shares burned)
  • Validate collateralization ratios in lending protocols
  • Monitor oracle price deviations
  • Check balance invariants (total supply equals sum of balances)
  • Enforce compliance rules (KYC/AML)
  • Validate cross-contract dependencies

Constraints

Gas Limit Assertion functions are limited to 300,000 gas per execution. If an assertion exceeds this limit, the transaction is dropped. This bound keeps validation predictable under adversarial inputs. Transactions that exhaust the assertion gas budget are treated as invalid, preserving the enforcement invariant. Stay mindful of gas consumption:
  • Use triggers to run assertions only when needed
  • Account for variable gas costs in cheatcodes such as getCallInputs(), where input lengths can vary
  • Optimize your assertion logic to stay within the gas limit
  • Test with realistic data volumes - see Gas Limits in Testing

Hacks Assertions Would Have Prevented

  • Radiant Capital ($58M) - Owner takeover via compromised multisig
  • Bybit ($1.4B) - Implementation change through compromised UI
  • Bunni ($8.4M) - Withdrawal proportionality rounding error
  • Euler Finance ($197M) - Missing health factor validation
  • Cream Finance ($130M) - Price manipulation through donations
  • Balancer ($120M) - Rounding error accumulation
See more examples in the Previous Hacks Index.

Next Steps

Writing Assertions

Step-by-step guide to writing your first assertion

Cheatcodes

Tools available for writing assertions

Triggers

Learn about assertion triggers and optimization

Assertions Book

Real-world examples and implementations