- Standard assertions (
assertEq,assertTrue, etc.) - Cheatcodes (
vm.prank,vm.deal,vm.warp, etc.) - Console logging
- Test setup with
setUp()function
Credible Layer Testing Interface
The Credible Layer extends Forge’s testing capabilities with additional utilities specifically for testing assertions. We want to keep the interface minimal and easy to use:Using the Testing Interface
Here’s how to use these utilities in your tests:Testing uses reverts to simulate production behavior where transactions are dropped. See Testing vs. Production for details.
- Use
cl.assertion()to register assertions that will be run on the next transaction - Only one assertion function can be registered at a time, which allows you to test assertion functions in isolation
- Use
vm.prank()to simulate a transaction from a specific address - Use
vm.expectRevert()to verify that the assertion reverts when expected vm.expectRevert()allows you to verify the exact error message returned by the assertion- The next transaction after
cl.assertion()will be validated against the assertion - State changes caused by a non-reverting transaction will be persisted
Testing Assertion Contracts with Constructor Arguments
Most of the time it’s best practice to useph.getAssertionAdopter() to get the assertion adopter instead of explicitly setting it in the constructor of the assertion contract.
There might however be cases where you want to define additional values that your assertion contract should have access to. For example, this could be the address of a token.
In this case you need to append the ABI-encoded constructor arguments to the contract bytecode.
The abi.encodePacked() function concatenates the bytecode with the encoded arguments, creating the complete deployment data needed to deploy the contract with the correct constructor parameters.
type(OwnableAssertion).creationCode- the contract bytecodeabi.encode(address(token))- the encoded constructor arguments
Testing Patterns
This section shows useful patterns that can be used when testing assertions.Batch Operation Testing Pattern
This pattern is used to test assertions that need to verify behavior across multiple operations in a single transaction. It involves creating a helper contract that performs multiple operations in a single function, allowing us to test the assertion’s ability to track and validate the cumulative effect of these operations.Key Components:
- A helper contract with a function that performs multiple operations
- Testing by calling the helper contract’s function with empty calldata
- Verification of the final state after all operations
Example Implementation:
If you use a fallback function, you need to call it with
address(batchUpdater).call("").
This is not the recommended way to do batch operations, since it’s a low level call and it doesn’t properly bubble up the revert reason. We recommend using the pattern above instead.If you do use a fallback function, you can use the following pattern to check the result of the batch operation:Benefits:
- Tests multiple operations in a single transaction
- Verifies that assertions can track cumulative effects
- More realistic testing of real-world scenarios
Protocol Mocking
When testing assertions, it can be difficult to trigger the conditions that would cause an assertion to fail in a real protocol. This is because protocols are designed to be secure and prevent invalid states from occurring. Protocol mocking provides a solution by creating simplified versions of protocols that can be intentionally put into invalid states. For example, you might create a mock protocol that allows direct manipulation of balances or total supply to test if your assertion correctly catches these invalid states.Key Components:
- Create a mock protocol with simplified logic
- Test by intentionally putting the protocol into an invalid state
- Verify that the assertion correctly catches the invalid state
Gas Limits
Assertion functions have a gas limit of 300k. This limit exists to ensure assertion validation doesn’t slow down block production. If an assertion exceeds this limit, it reverts withOutOfGas. In production, this causes the transaction to be dropped - even if there’s no actual violation. Since Assertion gas usage can fluctuate with the transaction they are validating, a sophisticated attacker could potentially craft a transaction that intentionally makes the Assertion execution to be over the gas limit. In that case, if we didn’t drop the invalidating transaction, the attacker would be able to use the gas limit to get around the system and forcefully include an invalidating transaction.
This makes gas limit issues critical to catch during testing.
The Happy Path Problem
Counterintuitively, the happy path is usually the most expensive. When no violation is detected:- All checks run to completion
- Loops iterate through all items
- No early returns short-circuit execution
Testing Recommendations
- Test with realistic data volumes - if your assertion loops through items, test with the maximum expected count
- Test batch operations at maximum expected size
- Use
pcl test -vvvto monitor gas usage - If approaching the limit, optimize or split the assertion
What’s Next?
- If you encounter issues while testing, check the Troubleshooting Guide
- Learn more about Writing Assertions
- Explore assertion examples in the Assertions Book

