credible-std is a standard library for implementing assertions in the Phylax Credible Layer (PCL). It provides the core contracts and interfaces needed to create and manage assertions for smart contract security monitoring.
The Phylax Credible Layer (PCL) is a security framework that enables real-time monitoring and validation of smart contract behavior through assertions. credible-std provides the foundational contracts and utilities needed to implement these assertions.
Credible.sol
: Base contract that provides access to the PhEvm precompile for assertion validationAssertion.sol
: Abstract contract for implementing assertions with trigger registration and validation logicStateChanges.sol
: Utilities for tracking and validating contract state changes with type-safe conversionsTriggerRecorder.sol
: Manages assertion triggers for function calls, storage changes, and balance changesPhEvm.sol
: Interface for the PhEvm precompile that enables assertion validationCredibleTest.sol
: Testing utilities for assertion development and validation
- Trigger System: Register triggers for function calls, storage changes, and balance changes to monitor specific contract behaviors
- State Change Tracking: Type-safe utilities for monitoring and validating contract state changes with built-in conversion helpers
- Testing Framework: Comprehensive testing utilities for assertion development with built-in validation helpers
- PhEvm Integration: Direct access to the PhEvm precompile for advanced assertion logic and validation
You can find detailed documentation on the Credible Layer and how to use the credible-std library in the Credible Layer Documentation.
Add the following to your foundry.toml
:
[dependencies]
credible-std = { git = "https://github.com/phylaxsystems/credible-std.git" }
Then run:
forge install
Alternatively you can install the package using forge:
forge install phylax-systems/credible-std
Add the dependency to your package.json
:
{
"dependencies": {
"credible-std": "github:phylaxsystems/credible-std"
}
}
Then run:
npm install
- Create an assertion contract that inherits from
Assertion
- Initialize the assertion in the constructor with the contract address you want to monitor
- Register triggers in the
triggers()
function for when the assertion should be checked - Implement validation logic in your assertion function(s)
- Add the assertion to your test environment using
cl.addAssertion()
- Test the assertion using
cl.validate()
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Assertion} from "credible-std/src/Assertion.sol"; // Credible Layer precompiles
import {Ownable} from "../../src/Ownable.sol"; // Contract to write assertions for
contract OwnableAssertion is Assertion {
Ownable ownable;
constructor(address ownable_) {
ownable = Ownable(ownable_); // Define address of Ownable contract
}
// Define selectors for the assertions, several assertions can be defined here
// This function is required by the Assertion interface
function triggers() external view override {
registerCallTrigger(this.assertionOwnershipChange.selector); // Register the selector for the assertionOwnershipChange function
}
// This function is used to check if the ownership has changed
// Get the owner of the contract before and after the transaction
// Return false if the owner has changed, true if it has not
function assertionOwnershipChange() external {
ph.forkPreState(); // Fork the pre-state of the transaction
address preOwner = ownable.owner(); // Get the owner of the contract before the transaction
ph.forkPostState(); // Fork the post-state of the transaction
address postOwner = ownable.owner(); // Get the owner of the contract after the transaction
require(postOwner == preOwner, "Ownership has changed"); // revert if the owner has changed
}
}
For a detailed guide on how to write assertions check out the Writing Assertions section of the documentation.
The credible-std provides several cheatcodes for assertion validation:
forkPreState()
: Forks to the state prior to the assertion triggering transactionforkPostState()
: Forks to the state after the assertion triggering transactionload(address target, bytes32 slot)
: Loads a storage slot from an addressgetLogs()
: Retrieves logs from the assertion triggering transactiongetCallInputs(address target, bytes4 selector)
: Gets call inputs for a given target and selectorgetStateChanges(address contractAddress, bytes32 slot)
: Gets state changes for a given contract and storage slotgetAssertionAdopter()
: Get assertion adopter contract address associated with the assertion triggering transaction
These cheatcodes can be accessed through the ph
instance in your assertion contracts, which is provided by the Credible
base contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Credible} from "credible-std/src/Credible.sol";
import {OwnableAssertion} from "../src/OwnableAssertion.sol";
import {Ownable} from "../../src/Ownable.sol";
import {CredibleTest} from "credible-std/src/CredibleTest.sol";
import {Test} from "forge-std/Test.sol";
contract TestOwnableAssertion is CredibleTest, Test {
// Contract state variables
Ownable public assertionAdopter;
address public initialOwner = address(0xdead);
address public newOwner = address(0xdeadbeef);
function setUp() public {
assertionAdopter = new Ownable();
vm.deal(initialOwner, 1 ether);
}
function test_assertionOwnershipChanged() public {
address aaAddress = address(assertionAdopter);
string memory label = "OwnableAssertion";
// Associate the assertion with the protocol
// cl will manage the correct assertion execution under the hood when the protocol is being called
cl.addAssertion(label, aaAddress, type(OwnableAssertion).creationCode, abi.encode(assertionAdopter));
vm.prank(initialOwner);
vm.expectRevert("Assertions Reverted"); // If the assertion fails, it will revert with this message
cl.validate(
label, aaAddress, 0, abi.encodePacked(assertionAdopter.transferOwnership.selector, abi.encode(newOwner))
);
}
}
For a detailed guide on how to test assertions check out the Testing Assertions section of the documentation.