forest-filecoin 0.33.0

Rust Filecoin implementation.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.30;

/// @title Tracer Contract
/// @notice Test contract for validating Forest's trace_call RPC implementation.
/// @dev This contract is used internally for:
///   - Integration tests comparing Forest's trace_call output with other implementations (e.g., Anvil)
///   - Manual testing of trace, stateDiff
///   - Generating test vectors for EVM execution tracing
///
/// NOT intended for production use. Functions are designed to exercise specific
/// EVM behaviors (storage writes, subcalls, reverts, events) for trace validation.
///
/// See: docs/docs/developers/guides/trace_call_guide.md
contract Tracer {
    uint256 public x; // slot 0 - initialized to 42
    mapping(address => uint256) public balances; // slot 1 - mapping base

    // Storage slots for stateDiff testing (uninitialized = start empty)
    uint256 public storageTestA; // slot 2 - for add/change/delete tests
    uint256 public storageTestB; // slot 3 - for multiple slot tests
    uint256 public storageTestC; // slot 4 - for multiple slot tests
    uint256[] public dynamicArray; // slot 5 - for array storage tests

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor() payable {
        x = 42;
    }

    // Allow contract to receive ETH
    receive() external payable {}

    // 1. Simple storage write
    function setX(uint256 _x) external {
        x = _x;
    }

    // 2. Balance update (SSTORE) - Contract receives ETH
    function deposit() external payable {
        balances[msg.sender] = msg.value;
    }

    // 2b. Send ETH to address - Tests balance decrease/increase
    function sendEth(address payable to) external payable {
        to.transfer(msg.value);
    }

    // 2c. Withdraw ETH - Contract sends ETH to caller
    function withdraw(uint256 amount) external {
        require(
            address(this).balance >= amount,
            "insufficient contract balance"
        );
        payable(msg.sender).transfer(amount);
    }

    // 3. Transfer between two accounts (SSTORE x2)
    function transfer(address to, uint256 amount) external {
        require(balances[msg.sender] >= amount, "insufficient balance");
        balances[msg.sender] -= amount;
        balances[to] += amount;
        emit Transfer(msg.sender, to, amount);
    }

    // 4. CALL (external call to self – creates CALL opcode)
    function callSelf(uint256 _x) external {
        (bool ok, ) = address(this).call(
            abi.encodeWithSelector(this.setX.selector, _x)
        );
        require(ok, "call failed");
    }

    // 5. DELEGATECALL (to self – shows delegatecall trace)
    function delegateSelf(uint256 _x) external {
        (bool ok, ) = address(this).delegatecall(
            abi.encodeWithSelector(this.setX.selector, _x)
        );
        require(ok, "delegatecall failed");
    }

    // 6. STATICCALL (read-only)
    function staticRead() external view returns (uint256) {
        return x;
    }

    // 7. CREATE (deploy a tiny contract)
    function createChild() external returns (address child) {
        bytes
            memory code = hex"6080604052348015600f57600080fd5b5060019050601c806100226000396000f3fe6080604052";
        assembly {
            child := create(0, add(code, 0x20), 0x1c)
        }
    }

    // 8. SELFDESTRUCT (send ETH to caller)
    // Deprecated (EIP-6780): selfdestruct only sends ETH (code & storage stay)
    function destroyAndSend() external {
        selfdestruct(payable(msg.sender));
    }

    // 9. Precompile use – keccak256
    function keccakIt(bytes32 input) external pure returns (bytes32) {
        return keccak256(abi.encodePacked(input));
    }

    // 10. Revert
    function doRevert() external pure {
        revert("from some fiasco");
    }

    // ========== DEEP TRACE FUNCTIONS ==========

    // 11. Deep recursive CALL trace
    // Creates trace depth of `depth` levels
    function deepTrace(uint256 depth) external returns (uint256) {
        if (depth == 0) {
            x = x + 1; // Storage write at deepest level
            return x;
        }
        (bool ok, bytes memory result) = address(this).call(
            abi.encodeWithSelector(this.deepTrace.selector, depth - 1)
        );
        require(ok, "deep call failed");
        return abi.decode(result, (uint256));
    }

    // 12. Mixed call types trace
    // Alternates between CALL, DELEGATECALL, and STATICCALL
    function mixedTrace(uint256 depth) external returns (uint256) {
        if (depth == 0) {
            return x;
        }

        uint256 callType = depth % 3;

        if (callType == 0) {
            // Regular CALL
            (bool ok, bytes memory result) = address(this).call(
                abi.encodeWithSelector(this.mixedTrace.selector, depth - 1)
            );
            require(ok, "call failed");
            return abi.decode(result, (uint256));
        } else if (callType == 1) {
            // DELEGATECALL
            (bool ok, bytes memory result) = address(this).delegatecall(
                abi.encodeWithSelector(this.mixedTrace.selector, depth - 1)
            );
            require(ok, "delegatecall failed");
            return abi.decode(result, (uint256));
        } else {
            // STATICCALL (read-only)
            (bool ok, bytes memory result) = address(this).staticcall(
                abi.encodeWithSelector(this.mixedTrace.selector, depth - 1)
            );
            require(ok, "staticcall failed");
            return abi.decode(result, (uint256));
        }
    }

    // 13. Wide trace - multiple sibling calls at same level
    // Creates `width` parallel calls, each going `depth` levels deep
    // Example: wideTrace(3, 2) creates 3 siblings, each 2 levels deep
    function wideTrace(
        uint256 width,
        uint256 depth
    ) external returns (uint256 sum) {
        if (depth == 0) {
            return 1;
        }

        for (uint256 i = 0; i < width; i++) {
            (bool ok, bytes memory result) = address(this).call(
                abi.encodeWithSelector(
                    this.wideTrace.selector,
                    width,
                    depth - 1
                )
            );
            require(ok, "wide call failed");
            sum += abi.decode(result, (uint256));
        }
        return sum;
    }

    // 14. Complex trace - combines everything
    // Level 0: CALL to setX
    // Level 1: DELEGATECALL to inner
    // Level 2: Multiple CALLs
    // Level 3: STATICCALL
    function complexTrace() external returns (uint256) {
        // First: regular call to setX
        (bool ok1, ) = address(this).call(
            abi.encodeWithSelector(this.setX.selector, 100)
        );
        require(ok1, "setX failed");

        // Second: delegatecall that does more calls
        (bool ok2, bytes memory result) = address(this).delegatecall(
            abi.encodeWithSelector(this.innerComplex.selector)
        );
        require(ok2, "innerComplex failed");

        return abi.decode(result, (uint256));
    }

    // Helper for complexTrace
    function innerComplex() external returns (uint256) {
        // Multiple sibling calls
        (bool ok1, ) = address(this).call(
            abi.encodeWithSelector(this.setX.selector, 200)
        );
        require(ok1, "inner call 1 failed");

        (bool ok2, ) = address(this).call(
            abi.encodeWithSelector(this.setX.selector, 300)
        );
        require(ok2, "inner call 2 failed");

        // Staticcall to read
        (bool ok3, bytes memory result) = address(this).staticcall(
            abi.encodeWithSelector(this.staticRead.selector)
        );
        require(ok3, "staticcall failed");

        return abi.decode(result, (uint256));
    }

    // 15. Failing nested trace - revert at depth
    // Useful for testing partial trace on failure
    function failAtDepth(
        uint256 depth,
        uint256 failAt
    ) external returns (uint256) {
        if (depth == failAt) {
            revert("intentional failure at depth");
        }
        if (depth == 0) {
            return x;
        }
        (bool ok, bytes memory result) = address(this).call(
            abi.encodeWithSelector(this.failAtDepth.selector, depth - 1, failAt)
        );
        require(ok, "nested call failed");
        return abi.decode(result, (uint256));
    }

    // ========== STORAGE DIFF TEST FUNCTIONS ==========
    // These functions test stateDiff storage tracking:
    // - Added (+): Write non-zero to empty slot (slot was 0)
    // - Changed (*): Change non-zero to different non-zero
    // - Removed (-): Set non-zero slot to 0

    // 16. Storage Add - Write to empty slot
    // First call creates "Added" (+) entry in stateDiff.storage
    function storageAdd(uint256 value) external {
        require(value != 0, "use non-zero for add test");
        storageTestA = value;
    }

    // 17. Storage Change - Modify existing slot
    // Creates "Changed" (*) entry in stateDiff.storage
    function storageChange(uint256 newValue) external {
        require(storageTestA != 0, "slot must have value first");
        require(
            newValue != 0 && newValue != storageTestA,
            "use different non-zero"
        );
        storageTestA = newValue;
    }

    // 18. Storage Delete - Set slot to zero
    // Creates "Removed" (-) entry in stateDiff.storage
    function storageDelete() external {
        require(storageTestA != 0, "slot must have value first");
        storageTestA = 0;
    }

    // 19. Storage Multiple - Change multiple slots in one call
    // Useful for testing multiple storage entries in stateDiff
    function storageMultiple(uint256 a, uint256 b, uint256 c) external {
        storageTestA = a;
        storageTestB = b;
        storageTestC = c;
    }

    // 20. Storage Mixed - Add, Change, and Delete in one call
    // Requires: storageTestA has value, storageTestB is empty
    function storageMixed(uint256 newA, uint256 newC) external {
        // Change existing (storageTestA should have value)
        storageTestA = newA;
        // Delete (set to 0)
        storageTestB = 0;
        // Add new value
        storageTestC = newC;
    }

    // 21. Array Push - Adds new storage slot
    // Dynamic arrays use keccak256(slot) + index for element storage
    function arrayPush(uint256 value) external {
        dynamicArray.push(value);
    }

    // 22. Array Pop - Removes storage slot (sets to 0)
    function arrayPop() external {
        require(dynamicArray.length > 0, "array is empty");
        dynamicArray.pop();
    }

    // 23. Reset storage test slots to initial state (all zeros)
    function storageReset() external {
        storageTestA = 0;
        storageTestB = 0;
        storageTestC = 0;
        delete dynamicArray;
    }

    // 24. Get storage test values (for verification)
    function getStorageTestValues()
        external
        view
        returns (uint256, uint256, uint256, uint256)
    {
        return (storageTestA, storageTestB, storageTestC, dynamicArray.length);
    }
}