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

/**
 * @dev Subset of the LIVE AereStorageProofVerifier 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  AereHistoryStateRootAnchor
 * @notice EIP-2935 sibling of AereStateRootAnchor. It binds an AERE state root
 *         to the CANONICAL chain for blocks FAR OLDER than the 256-block
 *         BLOCKHASH window, closing the same trust gap AereStorageProofVerifier
 *         leaves open (that verifier proves a value against a GIVEN root but
 *         cannot show the root is canonical).
 *
 *         WHERE THE BLOCK HASH COMES FROM (this is the whole point).
 *         The canonical block hash is read, at call time, from the EIP-2935
 *         history-storage system contract at
 *         0x0000F90827F1C53a10cb7A02335B175320002935 via STATICCALL. That
 *         contract is a protocol-native ring buffer: on the AerePQC fork
 *         (futureEips milestone, same futureEipsTime activation as the native
 *         PQC precompiles) Besu's PraguePreExecutionProcessor writes the parent
 *         block hash into slot (number-1) % 8191 at the start of EVERY block,
 *         as a system call from address 0xffff...fffe. No externally-owned
 *         account, relayer, keeper, or Foundation key ever writes it. The value
 *         this anchor trusts is therefore produced by consensus itself, exactly
 *         like the BLOCKHASH opcode but with an 8191-block reach instead of 256.
 *
 *         At AERE's 0.5s block time, 8191 blocks is roughly 68 minutes of
 *         reach, versus the ~128 seconds BLOCKHASH gives. Once a header is
 *         anchored here, the (blockNumber -> stateRoot) record is permanent and
 *         independent of the moving 8191-block ring buffer.
 *
 *         The block hash is recovered strictly from the system contract, never
 *         from a caller argument, so an attacker cannot anchor a fabricated
 *         root: keccak256(rlpHeader) must equal the ring buffer'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 AereHistoryStateRootAnchor {
    /// @notice Live AereStorageProofVerifier this anchor drives for proofs.
    IAereStorageProofVerifier public immutable VERIFIER;

    /// @notice EIP-2935 history-storage system contract (protocol-written ring buffer).
    address public constant HISTORY_STORAGE_ADDRESS =
        0x0000F90827F1C53a10cb7A02335B175320002935;

    /// @notice EIP-2935 serve window: the ring buffer addresses the last 8191 blocks.
    uint256 public constant HISTORY_SERVE_WINDOW = 8191;

    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 OutOfHistoryWindow(uint256 blockNumber, uint256 currentBlock);
    error HistoryContractMissing();
    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: the EIP-2935 history-storage contract.
     *      Overridable so tests can inject a REAL AERE canonical hash without a
     *      live fork; the production path always STATICCALLs the system
     *      contract, whose storage is written by consensus (see contract note).
     *
     *      Read ABI (per EIP-2935 runtime code): input is the 32-byte big-endian
     *      block number; output is the 32-byte canonical hash. The contract
     *      REVERTS for any block outside [number-8191, number-1], so a revert or
     *      a non-32-byte return is treated as out-of-window.
     */
    function _historyBlockHash(uint256 blockNumber) internal view virtual returns (bytes32 h) {
        if (HISTORY_STORAGE_ADDRESS.code.length == 0) revert HistoryContractMissing();
        (bool ok, bytes memory ret) =
            HISTORY_STORAGE_ADDRESS.staticcall(abi.encode(blockNumber));
        if (!ok || ret.length != 32) revert OutOfHistoryWindow(blockNumber, block.number);
        h = abi.decode(ret, (bytes32));
        // A real block hash is never zero; zero means the slot was never written
        // for this block number (out of the served window).
        if (h == bytes32(0)) revert OutOfHistoryWindow(blockNumber, block.number);
    }

    /**
     * @notice Anchor the state root of a historical canonical block using the
     *         EIP-2935 ring buffer. Works for blocks up to 8191 back, far beyond
     *         the 256-block BLOCKHASH horizon.
     * @param  blockNumber The block whose header is being anchored. MUST be
     *         within the last 8191 blocks (EIP-2935 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.
     */
    function anchorHistoricalHeader(uint256 blockNumber, bytes calldata rlpHeader)
        external
        returns (bytes32 stateRoot)
    {
        bytes32 canonical = _historyBlockHash(blockNumber);

        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.
     *         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. Identical layout logic to
    // AereStateRootAnchor: the first four header fields are fixed size, so the
    // stateRoot sits at a fixed offset in the list payload.
    //   [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
    // ----------------------------------------------------------------------
    function _decodeStateRoot(bytes calldata rlpHeader) internal pure returns (bytes32 stateRoot) {
        uint256 len = rlpHeader.length;
        if (len < 121) revert MalformedHeader(1);

        uint8 first = uint8(rlpHeader[0]);
        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;
        if (payloadStart + payloadLen != len) revert MalformedHeader(3);
        if (payloadStart + 120 > len) revert MalformedHeader(4);

        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 EIP-2935 ring buffer serves right now
    ///         for `blockNumber`. Reverts OutOfHistoryWindow if not served.
    function historyBlockHash(uint256 blockNumber) external view returns (bytes32) {
        return _historyBlockHash(blockNumber);
    }

    /// @notice True iff `blockNumber` is currently served by the EIP-2935 buffer.
    function isWithinHistoryWindow(uint256 blockNumber) external view returns (bool) {
        if (HISTORY_STORAGE_ADDRESS.code.length == 0) return false;
        (bool ok, bytes memory ret) =
            HISTORY_STORAGE_ADDRESS.staticcall(abi.encode(blockNumber));
        if (!ok || ret.length != 32) return false;
        return abi.decode(ret, (bytes32)) != bytes32(0);
    }

    /// @notice True iff the EIP-2935 system contract has code (fork active here).
    function historyContractDeployed() external view returns (bool) {
        return HISTORY_STORAGE_ADDRESS.code.length != 0;
    }

    /// @notice Preview the state root a header decodes to, WITHOUT canonicity
    ///         checks or recording. Pure helper for off-chain header assembly.
    function previewStateRoot(bytes calldata rlpHeader) external pure returns (bytes32) {
        return _decodeStateRoot(rlpHeader);
    }
}
