Use Case & Applications

Ensures security and integrity of decentralized social media protocols by maintaining critical invariants: data integrity (proper message signatures), identity protection (preventing username spoofing), DoS prevention (rate limiting), and platform stability. Critical for social protocols (Farcaster, Lens Protocol), identity systems managing unique identifiers, content publishing platforms, and governance systems requiring message validity. Without proper validation, attackers could post messages as other users, register duplicate usernames, flood the network, or create invalid content that breaks client applications.

Explanation

Provides comprehensive protection by monitoring three critical aspects:
  • Message Validity: Validates posted messages have proper signatures, meet content requirements, and pass protocol-specific checks
  • Username Uniqueness: Ensures usernames remain unique and ownership is properly tracked
  • Rate Limiting: Enforces posting rate limits to prevent network abuse
Uses these cheatcodes:
  • ph.forkPreState() / ph.forkPostState(): Check system state before and after operations
  • ph.getCallInputs(): Retrieve and decode function call parameters
  • registerCallTrigger(): Trigger when specific functions are called
The multi-layered approach checks basic invariants first, then performs deeper validation of protocol-specific rules for both performance efficiency and comprehensive coverage. For more information about cheatcodes, see the Cheatcodes Documentation.

Code Example

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

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

contract FarcasterProtocolAssertion is Assertion {
    // Constants
    uint256 constant MAX_CONTENT_LENGTH = 320; // Farcaster's max message length
    uint256 constant POST_COOLDOWN = 1 minutes;

    function triggers() external view override {
        // Register trigger for message validity on postMessage calls
        registerCallTrigger(this.assertMessageValidity.selector, IFarcaster.postMessage.selector);

        // Register trigger for username uniqueness on register calls
        registerCallTrigger(this.assertUniqueUsername.selector, IFarcaster.register.selector);

        // Register trigger for rate limits on postMessage calls
        registerCallTrigger(this.assertRateLimit.selector, IFarcaster.postMessage.selector);
    }

    function assertMessageValidity() external {
        // Get the assertion adopter address
        IFarcaster adopter = IFarcaster(ph.getAssertionAdopter());

        // Get all calls to postMessage function
        PhEvm.CallInputs[] memory callInputs = ph.getCallInputs(address(adopter), adopter.postMessage.selector);

        for (uint256 i = 0; i < callInputs.length; i++) {
            // Decode the message parameters from the call data
            IFarcaster.Message memory message = abi.decode(callInputs[i].input, (IFarcaster.Message));

            // Examine state after the message is posted to ensure all validations pass
            ph.forkPostTx();

            // Check basic message validity requirements
            require(message.author != address(0), "Invalid author: zero address");
            require(message.content.length > 0, "Invalid message: empty content");
            require(message.content.length <= MAX_CONTENT_LENGTH, "Invalid message: content exceeds max length");
            require(message.timestamp > 0, "Invalid message: missing timestamp");
            require(message.signature.length > 0, "Invalid message: missing signature");

            // Verify cryptographic signature is valid for the message
            require(adopter.verifySignature(message), "Security violation: invalid signature");

            // Check protocol-specific validation rules
            require(adopter.isValidMessage(message), "Message failed protocol validation");
        }
    }

    function assertUniqueUsername() external {
        // Get the assertion adopter address
        IFarcaster adopter = IFarcaster(ph.getAssertionAdopter());

        // Get all calls to register function
        PhEvm.CallInputs[] memory callInputs = ph.getCallInputs(address(adopter), adopter.register.selector);

        for (uint256 i = 0; i < callInputs.length; i++) {
            // Decode registration parameters
            (string memory username, address owner) = abi.decode(callInputs[i].input, (string, address));

            // Check pre-registration state to ensure username isn't already taken
            ph.forkPreCall(callInputs[i].id);
            require(!adopter.isRegistered(username), "Security violation: username already registered");

            // Check post-registration state to ensure registration succeeded and owner is correct
            ph.forkPostCall(callInputs[i].id);
            require(adopter.isRegistered(username), "Registration failed to complete");
            require(
                adopter.getUsernameOwner(username) == owner, "Security violation: owner mismatch after registration"
            );
        }
    }

    function assertRateLimit() external {
        // Get the assertion adopter address
        IFarcaster adopter = IFarcaster(ph.getAssertionAdopter());

        // Get all calls to postMessage function
        PhEvm.CallInputs[] memory callInputs = ph.getCallInputs(address(adopter), adopter.postMessage.selector);

        for (uint256 i = 0; i < callInputs.length; i++) {
            // Decode the message to get author
            IFarcaster.Message memory message = abi.decode(callInputs[i].input, (IFarcaster.Message));

            address user = message.author;

            // Check state before posting to validate rate limits
            ph.forkPreCall(callInputs[i].id);

            // Ensure cooldown between posts is respected
            uint256 lastPostTime = adopter.getLastPostTimestamp(user);
            require(block.timestamp >= lastPostTime + POST_COOLDOWN, "Rate limit violation: posting too frequently");
        }
    }
}

interface IFarcaster {
    // Message struct and functions
    struct Message {
        uint256 id;
        address author;
        bytes content;
        uint256 timestamp;
        bytes signature;
    }

    function isValidMessage(Message memory message) external view returns (bool);
    function verifySignature(Message memory message) external view returns (bool);
    function postMessage(Message memory message) external;

    // Username functions
    function register(string calldata username, address owner) external;
    function isRegistered(string calldata username) external view returns (bool);
    function getUsernameOwner(string calldata username) external view returns (address);

    // Rate limit functions
    function getLastPostTimestamp(address user) external view returns (uint256);
}
Note: Full examples with tests available in the Phylax Assertion Examples Repository.