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

// Reuse the LIVE verifier interface and the AnchoredRoot semantics from the
// original anchor. Importing the V1 file also brings in IAereStorageProofVerifier
// so the two contracts share one interface definition and one ABI convention.
import "./AereHistoryStateRootAnchor.sol";

/**
 * @title  AereHistoryStateRootAnchorV2
 * @notice Corrected redeploy of AereHistoryStateRootAnchor. The anchoring path
 *         (EIP-2935 ring-buffer block-hash recovery, keccak match, RLP state
 *         root decode, permanent record) is byte-for-byte identical to V1. The
 *         only change is in {proveAgainstAnchor}.
 *
 *         WHAT V1 DID WRONG (audit FINDING 4, low).
 *         V1 called VERIFIER.verify() to recover the committed blockNumber and
 *         stateRoot, then called VERIFIER.submitProofWithExpectedRoot(), which
 *         re-runs the very same SP1 Groth16 pairing check on the very same
 *         publicValues and proof. The expensive proof verification therefore ran
 *         TWICE per call for no added safety, since submitProofWithExpectedRoot
 *         already reverts on an invalid proof and already enforces that the
 *         proof's committed root equals the expected root passed to it.
 *
 *         WHAT V2 DOES.
 *         V2 decodes the block number, account, and slot LOCALLY from the fixed
 *         192-byte public-values layout (no external call), looks up the root
 *         anchored for that block number, and calls ONLY
 *         submitProofWithExpectedRoot(publicValues, proof, anchoredRoot). The
 *         single verification inside submit still reverts on an invalid proof
 *         and still binds the committed root to the anchored root, so behaviour
 *         is unchanged while the redundant pairing check is removed.
 *
 *         PUBLIC-VALUES LAYOUT (abi.encode, 6 words, 192 bytes):
 *           [0:32]    chainId
 *           [32:64]   blockNumber
 *           [64:96]   stateRoot
 *           [96:128]  account
 *           [128:160] slot
 *           [160:192] value
 *
 *         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 AereHistoryStateRootAnchorV2 {
    /// @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;

    /// @notice Fixed byte length of the SP1 public values this anchor accepts.
    uint256 public constant PUBLIC_VALUES_LEN = 192;

    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 BadPublicValuesLength(uint256 got);

    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 the V1 contract
     *      note for the full rationale).
     *
     *      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. Logic is identical to V1.
     * @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,
     *         running the SP1 proof verification EXACTLY ONCE.
     * @dev    The block number, account, and slot are decoded locally from the
     *         fixed 192-byte public-values layout, so no call to VERIFIER.verify
     *         is made. The anchored root for that block number is passed as the
     *         expected root to submitProofWithExpectedRoot, which performs the
     *         single verification, reverts on an invalid proof, and reverts if
     *         the proof's committed root does not equal the anchored root. This
     *         is the same guarantee V1 provided, without the redundant second
     *         verification.
     */
    function proveAgainstAnchor(bytes calldata publicValues, bytes calldata proof)
        external
        returns (bytes32 value)
    {
        if (publicValues.length != PUBLIC_VALUES_LEN) {
            revert BadPublicValuesLength(publicValues.length);
        }

        (uint256 blockNumber, address account, bytes32 slot) =
            _decodePublicValues(publicValues);

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

        // Single verification. submitProofWithExpectedRoot reverts on an invalid
        // SP1 proof and reverts unless the proof's committed root == a.stateRoot,
        // so no unverified or off-root state can be recorded.
        value = VERIFIER.submitProofWithExpectedRoot(publicValues, proof, a.stateRoot);
        emit ProvenAgainstAnchor(blockNumber, a.stateRoot, account, slot, value);
    }

    /**
     * @dev Decode blockNumber (word 1), account (word 3), and slot (word 4) from
     *      the fixed 192-byte public-values layout. chainId, stateRoot, and value
     *      are not needed here: the committed stateRoot is bound to the anchored
     *      root inside submitProofWithExpectedRoot, and chainId is enforced by the
     *      verifier itself.
     */
    function _decodePublicValues(bytes calldata publicValues)
        internal
        pure
        returns (uint256 blockNumber, address account, bytes32 slot)
    {
        uint256 accountWord;
        assembly {
            blockNumber := calldataload(add(publicValues.offset, 32))
            accountWord := calldataload(add(publicValues.offset, 96))
            slot := calldataload(add(publicValues.offset, 128))
        }
        // Low 20 bytes of word 3, matching abi.decode of an address.
        account = address(uint160(accountWord));
    }

    // ----------------------------------------------------------------------
    // RLP header state-root decode. Identical layout logic to
    // AereHistoryStateRootAnchor: 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);
    }
}
