Use Case & Applications

Monitors and limits token outflows to prevent catastrophic asset loss during exploits, creating a delay window for emergency response. Critical for lending protocols with token reserves (Compound, Aave, Morpho), liquidity pools and DEXs, treasury management systems and DAOs, yield aggregators, and cross-chain bridges holding tokens in escrow. By limiting outflow rates, protocols gain valuable time to respond to security incidents and activate circuit breakers, even if complete prevention isn’t possible.

Explanation

Implements percentage-based limit on token outflows in a single transaction:
  • forkPreState() / forkPostState(): Capture token balance before and after transaction
  • registerCallTrigger(): Trigger on every transaction without specifying particular function signature
  • Calculate percentage of tokens withdrawn in transaction
  • Revert if withdrawal percentage exceeds configured threshold
The assertion ensures normal protocol operations continue unimpeded while blocking suspicious large withdrawals and maintaining token outflows within reasonable operational parameters.
This assertion is meant to be an example of monitoring token outflows. Due to the nature of how assertions work, only the owner of a contract can enable assertions on it. Since ERC20 tokens are contracts that track balances, but you don’t physically store the tokens in your own contract, you cannot directly control balance changes in the token contract itself. This assertion would need to be implemented by the protocol contract owner to monitor their own contract’s token balances.
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";

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
}

interface IProtocol {
// Generic interface for the protocol contract being protected
}

contract ERC20DrainAssertion is Assertion {
    // The ERC20 token being monitored
    IERC20 public immutable token;

    // The protocol contract whose token balance is being protected
    address public immutable protocolContract;

    // Maximum percentage of token balance that can be withdrawn in a single tx (in basis points)
    // 1000 basis points = 10%
    uint256 public constant MAX_DRAIN_PERCENTAGE_BPS = 1000;

    // Denominator for basis points calculation
    uint256 public constant BPS_DENOMINATOR = 10000;

    constructor(address _token, address _protocolContract) {
        token = IERC20(_token);
        protocolContract = _protocolContract;
    }

    function triggers() external view override {
        // This assertion doesn't need specific function triggers since it monitors
        // the overall balance change regardless of which function caused it
        // We register a generic trigger that will run after every transaction
        registerCallTrigger(this.assertERC20Drain.selector);
    }

    /// @notice Checks that the token balance doesn't decrease by more than MAX_DRAIN_PERCENTAGE_BPS in a single transaction
    /// @dev This assertion captures state before and after the transaction to detect excessive token outflows
    function assertERC20Drain() external {
        // Get token balance before the transaction
        ph.forkPreState();
        uint256 preBalance = token.balanceOf(protocolContract);

        // Get token balance after the transaction
        ph.forkPostState();
        uint256 postBalance = token.balanceOf(protocolContract);

        // If balance decreased, check if the decrease exceeds our threshold
        if (preBalance > postBalance) {
            uint256 drainAmount = preBalance - postBalance;

            // Calculate maximum allowed drain amount (e.g., 10% of pre-balance)
            uint256 maxAllowedDrainAmount = (preBalance * MAX_DRAIN_PERCENTAGE_BPS) / BPS_DENOMINATOR;

            // Revert if the drain amount exceeds the allowed percentage
            require(
                drainAmount <= maxAllowedDrainAmount, "ERC20Drain: Token outflow exceeds maximum allowed percentage"
            );
        }
    }
}
Note: Full examples with tests available in the Phylax Assertion Examples Repository.