// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @dev Subset of the LIVE AereStorageProofVerifier
 *      (0xF9a1A183bEb3147D88dA5927301683fEbFb9362E) used by this anchor.
 *      `verify` is a stateless view that reverts on an invalid SP1 proof and
 *      otherwise returns the tuple committed in the 192-byte public values.
 *      `submitProofWithExpectedRoot` re-verifies and records the slot only if
 *      the proof's committed stateRoot equals `expectedStateRoot`.
 */
interface IAereStorageProofVerifier {
    function verify(bytes calldata publicValues, bytes calldata proof)
        external
        view
        returns (
            uint256 blockNumber,
            bytes32 stateRoot,
            address account,
            bytes32 slot,
            bytes32 value
        );

    function submitProofWithExpectedRoot(
        bytes calldata publicValues,
        bytes calldata proof,
        bytes32 expectedStateRoot
    ) external returns (bytes32 value);
}

/**
 * @title  AereStateRootAnchor
 * @notice Binds an AERE state root to the CANONICAL chain, closing the trust
 *         gap left open by AereStorageProofVerifier. That verifier proves a
 *         storage value against a GIVEN state root but cannot, on its own, show
 *         the root is canonical for a block on chain 2800. This contract does
 *         exactly that: it recovers a canonical block hash from the EVM and
 *         checks a caller-supplied RLP header against it, then records the
 *         header's state root as an anchored, trust-minimized value.
 *
 *         CANONICITY SOURCE ON CHAIN 2800 (probed on-chain, 2026-07-10).
 *         - BLOCKHASH opcode: LIVE. Serves the last 256 blocks. This is the
 *           ONLY canonicity source available on chain 2800 today.
 *         - EIP-2935 history-storage contract
 *           (0x0000F90827F1C53a10cb7A02335B175320002935 and the two earlier
 *           devnet addresses): NOT deployed (eth_getCode == 0x). So the
 *           extended ~8191-block window is NOT available here.
 *         Therefore this anchor uses BLOCKHASH and its window is 256 blocks.
 *         At AERE's 0.5s block time that is only ~128 seconds of history, so
 *         `anchorRecentHeader` MUST be called promptly after the target block
 *         is produced. Once anchored, the (blockNumber -> stateRoot) record is
 *         permanent and independent of the moving BLOCKHASH window.
 *
 *         The block hash is recovered strictly from consensus (the BLOCKHASH
 *         opcode), never from a caller argument, so an attacker cannot anchor a
 *         fabricated root: keccak256(rlpHeader) must equal the opcode's answer.
 *
 *         IMMUTABILITY: VERIFIER is fixed at deploy. No owner, no admin, no
 *         upgrade path. Anyone may anchor any in-window header and anyone may
 *         prove against an anchored root.
 */
contract AereStateRootAnchor {
    /// @notice Live AereStorageProofVerifier this anchor drives for proofs.
    IAereStorageProofVerifier public immutable VERIFIER;

    /// @notice EVM BLOCKHASH window: the opcode addresses the last 256 blocks.
    uint256 public constant BLOCKHASH_WINDOW = 256;

    struct AnchoredRoot {
        bool exists;
        bytes32 stateRoot; // header field index 3
        bytes32 blockHash; // canonical hash the header was checked against
        uint64 blockNumber;
        uint64 verifiedAt; // block.timestamp when anchored
    }

    /// @notice blockNumber => anchored canonical state root record.
    mapping(uint256 => AnchoredRoot) private _anchored;

    event HeaderAnchored(
        uint256 indexed blockNumber,
        bytes32 indexed blockHash,
        bytes32 stateRoot,
        uint256 verifiedAt
    );

    event ProvenAgainstAnchor(
        uint256 indexed blockNumber,
        bytes32 indexed stateRoot,
        address indexed account,
        bytes32 slot,
        bytes32 value
    );

    error ZeroVerifier();
    error OutOfWindow(uint256 blockNumber, uint256 currentBlock);
    error HeaderHashMismatch(bytes32 got, bytes32 canonical);
    error MalformedHeader(uint256 code);
    error NotAnchored(uint256 blockNumber);
    error RootNotCanonical(bytes32 proofRoot, bytes32 anchoredRoot);

    constructor(address verifier) {
        if (verifier == address(0)) revert ZeroVerifier();
        VERIFIER = IAereStorageProofVerifier(verifier);
    }

    /**
     * @dev Canonical block-hash source. Overridable so tests can inject a REAL
     *      AERE canonical hash for a block outside the live 256-block window
     *      (e.g. genesis) without weakening the production path, which always
     *      uses the BLOCKHASH opcode.
     */
    function _blockHashOf(uint256 blockNumber) internal view virtual returns (bytes32) {
        return blockhash(blockNumber);
    }

    /**
     * @notice Anchor the state root of a recent canonical block.
     * @param  blockNumber The block whose header is being anchored. MUST be
     *         within the last 256 blocks (BLOCKHASH window) at call time.
     * @param  rlpHeader   The exact RLP-encoded block header whose keccak256 is
     *         the canonical block hash. The state root is header field index 3.
     * @return stateRoot   The decoded, now-anchored canonical state root.
     *
     * Reverts OutOfWindow if BLOCKHASH cannot address the block (older than 256
     * blocks, the current block, or a future block). Reverts HeaderHashMismatch
     * if the supplied header does not hash to the canonical block hash.
     */
    function anchorRecentHeader(uint256 blockNumber, bytes calldata rlpHeader)
        external
        returns (bytes32 stateRoot)
    {
        bytes32 canonical = _blockHashOf(blockNumber);
        // BLOCKHASH returns 0 for any block it cannot address. A real block hash
        // is never 0 (keccak of a nonempty header), so 0 == out of window.
        if (canonical == bytes32(0)) revert OutOfWindow(blockNumber, block.number);

        bytes32 got = keccak256(rlpHeader);
        if (got != canonical) revert HeaderHashMismatch(got, canonical);

        stateRoot = _decodeStateRoot(rlpHeader);

        _anchored[blockNumber] = AnchoredRoot({
            exists: true,
            stateRoot: stateRoot,
            blockHash: canonical,
            blockNumber: uint64(blockNumber),
            verifiedAt: uint64(block.timestamp)
        });

        emit HeaderAnchored(blockNumber, canonical, stateRoot, block.timestamp);
    }

    /**
     * @notice Drive the live storage-proof verifier against an ANCHORED root.
     *         This is the trust-minimized end of the two-call pattern: rather
     *         than trusting a caller-supplied stateRoot, the proof's committed
     *         root is checked to equal the root anchored for the SAME block
     *         number (that block number is committed inside the proof, so the
     *         caller cannot mismatch them). Only then is the slot recorded in
     *         the verifier via {submitProofWithExpectedRoot}.
     * @dev    The verifier reverts the whole call on an invalid SP1 proof or a
     *         wrong chainId, so no unverified state can be recorded.
     */
    function proveAgainstAnchor(bytes calldata publicValues, bytes calldata proof)
        external
        returns (bytes32 value)
    {
        (uint256 blockNumber, bytes32 stateRoot, address account, bytes32 slot, ) =
            VERIFIER.verify(publicValues, proof);

        AnchoredRoot memory a = _anchored[blockNumber];
        if (!a.exists) revert NotAnchored(blockNumber);
        if (a.stateRoot != stateRoot) revert RootNotCanonical(stateRoot, a.stateRoot);

        value = VERIFIER.submitProofWithExpectedRoot(publicValues, proof, a.stateRoot);
        emit ProvenAgainstAnchor(blockNumber, a.stateRoot, account, slot, value);
    }

    // ----------------------------------------------------------------------
    // RLP header state-root decode.
    //
    // A block header is a long RLP list. Its first three fields are fixed size:
    //   [0] parentHash   32 bytes  (prefix 0xa0)
    //   [1] ommersHash   32 bytes  (prefix 0xa0)
    //   [2] beneficiary  20 bytes  (prefix 0x94)
    //   [3] stateRoot    32 bytes  (prefix 0xa0)  <-- what we want
    // so the state root sits at a fixed offset inside the list payload,
    // regardless of any later fields (extraData, blob/beacon fields, or QBFT
    // commit-seal handling), all of which come AFTER stateRoot.
    // ----------------------------------------------------------------------
    function _decodeStateRoot(bytes calldata rlpHeader) internal pure returns (bytes32 stateRoot) {
        uint256 len = rlpHeader.length;
        // Smallest possible header list prefix (1) + 4 leading fields.
        if (len < 121) revert MalformedHeader(1);

        uint8 first = uint8(rlpHeader[0]);
        // Block headers are always long lists (payload > 55 bytes) -> 0xf8..0xff.
        if (first < 0xf8) revert MalformedHeader(2);

        uint256 numLenBytes = uint256(first) - 0xf7; // 1..8
        uint256 payloadLen;
        for (uint256 i = 0; i < numLenBytes; i++) {
            payloadLen = (payloadLen << 8) | uint256(uint8(rlpHeader[1 + i]));
        }
        uint256 payloadStart = 1 + numLenBytes;
        // The declared payload length must exactly cover the rest of the input.
        if (payloadStart + payloadLen != len) revert MalformedHeader(3);
        // Need room for the four leading fixed fields: 33 + 33 + 21 + 33 = 120.
        if (payloadStart + 120 > len) revert MalformedHeader(4);

        // Validate the fixed-size field prefixes so we know the layout is a
        // standard Ethereum-style header and offset 88 truly is the stateRoot.
        if (uint8(rlpHeader[payloadStart]) != 0xa0) revert MalformedHeader(5); // parentHash
        if (uint8(rlpHeader[payloadStart + 33]) != 0xa0) revert MalformedHeader(6); // ommersHash
        if (uint8(rlpHeader[payloadStart + 66]) != 0x94) revert MalformedHeader(7); // beneficiary
        if (uint8(rlpHeader[payloadStart + 87]) != 0xa0) revert MalformedHeader(8); // stateRoot prefix

        uint256 srContentOffset = payloadStart + 88;
        assembly {
            stateRoot := calldataload(add(rlpHeader.offset, srContentOffset))
        }
    }

    // --------------------------- views -----------------------------------

    function getAnchoredRoot(uint256 blockNumber)
        external
        view
        returns (bool exists, bytes32 stateRoot, bytes32 blockHash, uint64 blockNum, uint64 verifiedAt)
    {
        AnchoredRoot memory a = _anchored[blockNumber];
        return (a.exists, a.stateRoot, a.blockHash, a.blockNumber, a.verifiedAt);
    }

    /// @notice Anchored state root for `blockNumber`; reverts if not anchored.
    function anchoredStateRoot(uint256 blockNumber) external view returns (bytes32) {
        AnchoredRoot memory a = _anchored[blockNumber];
        if (!a.exists) revert NotAnchored(blockNumber);
        return a.stateRoot;
    }

    function isAnchored(uint256 blockNumber) external view returns (bool) {
        return _anchored[blockNumber].exists;
    }

    /// @notice The canonical block hash the anchor would use right now (0 if the
    ///         block is outside the addressable BLOCKHASH window).
    function canonicalBlockHash(uint256 blockNumber) external view returns (bytes32) {
        return _blockHashOf(blockNumber);
    }

    /// @notice True iff `blockNumber` is currently anchorable (in the BLOCKHASH window).
    function isWithinWindow(uint256 blockNumber) external view returns (bool) {
        return _blockHashOf(blockNumber) != bytes32(0);
    }

    /// @notice Convenience preview of the state root a given header decodes to,
    ///         WITHOUT checking canonicity or recording anything. Pure helper
    ///         for off-chain callers assembling a header before anchoring.
    function previewStateRoot(bytes calldata rlpHeader) external pure returns (bytes32) {
        return _decodeStateRoot(rlpHeader);
    }
}
