Skip to main content

When to Use This Pattern

Prevents unauthorized fee manipulation in AMMs and DeFi protocols by using a whitelist approach to control fee changes. Critical for AMMs (Velodrome, Aerodrome, Curve), DEX aggregators, lending protocols with origination fees, yield aggregators with performance fees, and cross-chain bridges with transfer fees. Unexpected fee changes can lead to unauthorized manipulation, protocol revenue loss, user losses, or market manipulation through fee bypasses.

What This Pattern Checks

Uses a whitelist approach to control fee changes:
  • registerTxEndTrigger(): Detect changes to fee storage slot
  • ph.loadStateAt(): Retrieve new fee value after change
  • Maintains hardcoded allowed fee values for stable pools (0.1%, 0.15%) and non-stable pools (0.25%, 0.30%)
When triggered, verifies the new fee matches one of the allowed values, preventing unauthorized modifications while allowing proper protocol updates. For more information about cheatcodes, see the Cheatcodes Documentation.

Assertion Pattern

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

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

contract AmmFeeVerificationAssertion is Assertion {
    bytes32 internal constant FEE_SLOT = bytes32(uint256(1));
    bytes32 internal constant STABLE_SLOT = bytes32(uint256(2));

    uint256 private constant STABLE_POOL_FEE_1 = 1;
    uint256 private constant STABLE_POOL_FEE_2 = 15;
    uint256 private constant NON_STABLE_POOL_FEE_1 = 25;
    uint256 private constant NON_STABLE_POOL_FEE_2 = 30;

    constructor() {
        registerAssertionSpec(AssertionSpec.Reshiram);
    }

    function triggers() external view override {
        registerTxEndTrigger(this.assertFeeVerification.selector);
    }

    /// @notice Checks that the post-transaction fee is one of the allowed values.
    function assertFeeVerification() external view {
        address pool = ph.getAssertionAdopter();
        PhEvm.ForkId memory postFork = _postTx();

        bool isStable = uint256(ph.loadStateAt(pool, STABLE_SLOT, postFork)) != 0;
        uint256 newFee = uint256(ph.loadStateAt(pool, FEE_SLOT, postFork));
        bool isAllowed = isStable
            ? (newFee == STABLE_POOL_FEE_1 || newFee == STABLE_POOL_FEE_2)
            : (newFee == NON_STABLE_POOL_FEE_1 || newFee == NON_STABLE_POOL_FEE_2);

        require(isAllowed, "Fee change to unauthorized value");
    }
}
Full examples and mock protocol code are available in credible-std.