Use Case & Applications

Prevents unauthorized changes to proxy implementation addresses, which could allow attackers to replace contract logic with malicious code. Critical for proxy-based upgradeable contracts in DeFi protocols (lending pools, yield aggregators), governance systems with timelocks, and cross-chain bridges. For example, it’s possible to define a whitelist of allowed implementations - any other implementation would be considered an invalid state.

Explanation

Monitors changes to the implementation address storage slot in proxy contracts using:
  • ph.forkPreState() / ph.forkPostState(): Compare implementation address before and after transaction
  • getStateChangesAddress(): Track all changes to the implementation slot during transaction execution
  • registerStorageChangeTrigger(): Trigger when implementation address changes
The assertion performs both direct pre/post comparison and verification of all intermediate state changes to detect unauthorized modifications. 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 ImplementationChangeAssertion is Assertion {
    function triggers() external view override {
        // Register trigger for changes to the implementation address storage slot
        // The implementation address is typically stored in the first storage slot (slot 0)
        registerStorageChangeTrigger(this.implementationChange.selector, bytes32(uint256(0)));
    }

    // Assert that the implementation contract address doesn't change
    // during the state transition
    function implementationChange() external {
        // Get the assertion adopter address
        IImplementation adopter = IImplementation(ph.getAssertionAdopter());

        // Get pre-state implementation
        ph.forkPreTx();
        address preImpl = adopter.implementation();

        // Get post-state implementation
        ph.forkPostTx();
        address postImpl = adopter.implementation();

        // Get all state changes for the implementation slot
        address[] memory changes = getStateChangesAddress(
            address(adopter),
            bytes32(uint256(0)) // First storage slot for implementation address
        );

        // Verify implementation hasn't changed
        require(preImpl == postImpl, "Implementation changed");

        // Additional check: verify no unauthorized changes to implementation slot
        for (uint256 i = 0; i < changes.length; i++) {
            require(changes[i] == preImpl, "Unauthorized implementation change detected");
        }
    }
}

interface IImplementation {
    function implementation() external view returns (address);
}
Note: Full examples with tests available in the Phylax Assertion Examples Repository.