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

/// @dev Canonical SP1 verifier / SP1VerifierGateway ABI: verifyProof RETURNS
///      NOTHING and REVERTS on an invalid proof. On chain 2800 the deployed
///      SP1VerifierGateway is 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 and the
///      SP1 Groth16 route is selected by the proof's leading 4-byte selector
///      0x4388a21c (SP1 v6 groth16), exactly as AereZKMLVerifier uses it.
interface ISp1Verifier {
    function verifyProof(
        bytes32 programVKey,
        bytes calldata publicValues,
        bytes calldata proof
    ) external view;
}

/**
 * @title AereRollupValidity - on-chain anchor for ZERO-KNOWLEDGE VALIDITY PROOFS
 *        of AERE's rollup executor.
 *
 * @notice Verifies SP1 zkVM Groth16 proofs that AERE's deterministic rollup
 *         executor (the `aere-block-stm` sequential oracle) transitioned a prior
 *         state root `prevRoot` to a new state root `postRoot` by applying a
 *         specific batch of transactions committed as `batchHash`. On a valid
 *         proof that continues the canonical chain (prevRoot == the last accepted
 *         postRoot) it records a validated rollup epoch and advances `latestRoot`.
 *
 *         This is a VALIDITY (zk) rollup settlement anchor: unlike the optimistic
 *         AereRollupSettlement (propose + challenge window), an epoch recorded
 *         here is final the instant the proof verifies. No fraud-proof window is
 *         required because the zk proof already establishes correctness.
 *
 *         PROGRAM BINDING: PROGRAM_VKEY is the SP1 verification key of the exact
 *         guest ELF (program/src/main.rs) that ports AERE's transaction semantics
 *         (execute_txn), keccak256 and state-root folding VERBATIM from
 *         parallel-executor/src/{vm,keccak}.rs. Binding the vkey binds the exact
 *         executor whose transition is being proven.
 *
 *         PUBLIC-VALUES CONVENTION (exactly 160 bytes):
 *           abi.encode(
 *             uint256 chainId,     // must equal block.chainid (2800)
 *             bytes32 prevRoot,    // keccak256 state root before the batch
 *             bytes32 postRoot,    // keccak256 state root after the batch
 *             bytes32 batchHash,   // keccak256 of the canonical batch encoding
 *             uint256 numTxns      // number of transactions in the batch
 *           )
 *
 *         BATCH HASH ENCODING (matches the guest's batch_hash, so an operator can
 *         recompute it off-chain to identify the batch): num_txns (u32 BE), then
 *         per tx a 1-byte tag and big-endian fields, then gas (u32 BE):
 *           0x00 Transfer  : from u64 | to u64 | amount u128
 *           0x01 Sweep     : from u64 | to u64
 *           0x02 Increment : contract u64 | slot u64
 *           0x03 AmmSwap   : contract u64 | x_slot u64 | y_slot u64 | dx u128
 *
 *         IMMUTABILITY / TRUST: SP1_VERIFIER, PROGRAM_VKEY and GENESIS_ROOT are
 *         fixed at deploy. There is NO owner and NO admin: epoch submission is
 *         permissionless. Soundness rests entirely on the SP1 Groth16 proof plus
 *         the prevRoot == latestRoot continuity check, so any party running the
 *         prover can advance the canonical chain, and none can forge or fork it.
 *
 * ===========================================================================
 * HONEST SCOPE - READ THIS.
 * ===========================================================================
 *   This proves the state transition of AERE's CURRENT rollup executor, whose VM
 *   implements a BOUNDED set of transaction kinds (Transfer, Sweep, Increment,
 *   AmmSwap) over a balance/storage map. It is NOT arbitrary EVM bytecode.
 *
 *   It is a REAL validity proof of THAT executor: the proven computation is
 *   byte-identical to what the production `aere-block-stm` binary self-checks and
 *   what BlockSTMExecutor commits via AereRollupSettlement.proposeStateRoot.
 *
 *   The honest next step toward a FULL validity rollup is replacing the
 *   executor's VM with a real EVM (revm) inside the zkVM, which is a separate
 *   multi-quarter effort. This contract does NOT prove a full EVM, and no code,
 *   UI or claim built on it should imply that it does.
 * ===========================================================================
 */
contract AereRollupValidity {

    /// @notice SP1 verifier gateway (routes to the Groth16 verifier by selector).
    address public immutable SP1_VERIFIER;

    /// @notice SP1 vkey binding the exact rollup-executor guest ELF.
    bytes32 public immutable PROGRAM_VKEY;

    /// @notice Pinned starting state root; epoch 0 must prove prevRoot == this.
    bytes32 public immutable GENESIS_ROOT;

    /// @notice The most recently accepted postRoot (starts at GENESIS_ROOT).
    bytes32 public latestRoot;

    /// @notice Number of validated epochs recorded.
    uint256 public epochCount;

    struct Epoch {
        bytes32 prevRoot;
        bytes32 postRoot;
        bytes32 batchHash;
        uint64  numTxns;
        uint64  at;         // block timestamp when the proof verified
    }

    /// @notice epochId (0-based) => validated epoch record.
    mapping(uint256 => Epoch) public epochs;

    /// @notice batchHash => 1-based epochId that recorded it (0 == not seen).
    mapping(bytes32 => uint256) public batchToEpoch;

    /* --------------------------------- events ------------------------------- */

    event EpochValidated(
        uint256 indexed epochId,
        bytes32 indexed prevRoot,
        bytes32 indexed postRoot,
        bytes32 batchHash,
        uint256 numTxns,
        address prover,
        uint256 at
    );

    /* --------------------------------- errors ------------------------------- */

    error InvalidProof();
    error BadPublicValuesLength(uint256 got);
    error WrongChain(uint256 inProof, uint256 onChain);
    error RootDiscontinuity(bytes32 prevInProof, bytes32 expected);
    error ZeroAddress();
    error ZeroVKey();

    /* ----------------------------- constructor ------------------------------ */

    constructor(address sp1Verifier, bytes32 programVKey, bytes32 genesisRoot) {
        if (sp1Verifier == address(0)) revert ZeroAddress();
        if (programVKey == bytes32(0)) revert ZeroVKey();
        SP1_VERIFIER = sp1Verifier;
        PROGRAM_VKEY = programVKey;
        GENESIS_ROOT = genesisRoot;
        latestRoot = genesisRoot;
    }

    /* ------------------------------ submit epoch ---------------------------- */

    /// @notice Verify a validity proof for the next rollup epoch and, on success,
    ///         record it and advance `latestRoot`. Permissionless.
    /// @param publicValues abi.encode(chainId, prevRoot, postRoot, batchHash, numTxns), 160 bytes.
    /// @param proof        SP1 Groth16 proof bytes (leading selector 0x4388a21c).
    /// @return epochId     the 0-based id of the recorded epoch.
    function submitEpoch(
        bytes calldata publicValues,
        bytes calldata proof
    ) external returns (uint256 epochId) {
        // 5 x 32 = 160 bytes for (uint256, bytes32, bytes32, bytes32, uint256).
        if (publicValues.length != 160) revert BadPublicValuesLength(publicValues.length);

        // 1) zk proof: reverts if the proof is not valid for PROGRAM_VKEY.
        try ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof) {
            // verified - did not revert
        } catch {
            revert InvalidProof();
        }

        (
            uint256 chainId,
            bytes32 prevRoot,
            bytes32 postRoot,
            bytes32 batchHash,
            uint256 numTxns
        ) = abi.decode(publicValues, (uint256, bytes32, bytes32, bytes32, uint256));

        // 2) bind to this chain.
        if (chainId != block.chainid) revert WrongChain(chainId, block.chainid);

        // 3) canonical-chain continuity: the batch must start from the current tip.
        if (prevRoot != latestRoot) revert RootDiscontinuity(prevRoot, latestRoot);

        // 4) record the validated epoch and advance the tip.
        epochId = epochCount;
        epochs[epochId] = Epoch({
            prevRoot: prevRoot,
            postRoot: postRoot,
            batchHash: batchHash,
            numTxns: uint64(numTxns),
            at: uint64(block.timestamp)
        });
        batchToEpoch[batchHash] = epochId + 1; // 1-based so 0 means "unseen"
        latestRoot = postRoot;
        epochCount = epochId + 1;

        emit EpochValidated(
            epochId, prevRoot, postRoot, batchHash, numTxns, msg.sender, block.timestamp
        );
    }

    /* ----------------------------------- views ------------------------------ */

    /// @notice Stateless verification: reverts if the proof is invalid for
    ///         PROGRAM_VKEY, otherwise returns the decoded (prevRoot, postRoot,
    ///         batchHash, numTxns). Records nothing and ignores chain continuity.
    function verify(
        bytes calldata publicValues,
        bytes calldata proof
    )
        external
        view
        returns (bytes32 prevRoot, bytes32 postRoot, bytes32 batchHash, uint256 numTxns)
    {
        if (publicValues.length != 160) revert BadPublicValuesLength(publicValues.length);
        ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof);
        (, prevRoot, postRoot, batchHash, numTxns) =
            abi.decode(publicValues, (uint256, bytes32, bytes32, bytes32, uint256));
    }

    /// @notice Convenience accessor for a recorded epoch.
    function getEpoch(uint256 epochId)
        external
        view
        returns (bytes32 prevRoot, bytes32 postRoot, bytes32 batchHash, uint64 numTxns, uint64 at)
    {
        Epoch memory e = epochs[epochId];
        return (e.prevRoot, e.postRoot, e.batchHash, e.numTxns, e.at);
    }
}
