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

/**
 * @title AereBlockSTMRegistry - read-write access annotations for parallel EVM
 * @notice The Block-STM scheduler achieves parallel transaction execution
 *         by speculatively running TXs in parallel and ABORTING + retrying
 *         any TX whose read-set was invalidated by a higher-priority TX's
 *         write-set. Reference algorithm: Gelashvili et al., "Block-STM:
 *         Scaling Blockchain Execution by Turning Ordering Curse to a
 *         Performance Blessing" (arXiv:2203.06871, 2022).
 *
 *         For that to work efficiently, the scheduler benefits from
 *         HINTS about which storage slots a TX is likely to read or
 *         write. Solidity doesn't expose this metadata. This contract
 *         provides an OPT-IN registry where contracts can publish their
 *         likely r/w sets at the function-selector grain - the consensus
 *         layer's Block-STM scheduler (Phase 2 Besu fork) reads it to
 *         improve parallelism + reduce abort rate.
 *
 *         WHO REGISTERS:
 *           - High-throughput hot contracts (AereCoreBookV0, AERE402,
 *             AereSink, AereLendingMarket).
 *           - Contracts whose abort rate the indexer flags as too high.
 *
 *         WHAT THEY REGISTER:
 *           - target contract address + 4-byte selector → array of slot
 *             hints (storage-slot keccak256 prefixes the TX is likely to
 *             touch + a read/write flag).
 *           - hints are ADVISORY. The scheduler still detects conflicts
 *             at runtime; a wrong hint just costs an abort.
 *
 *         WHO PUBLISHES:
 *           - The contract owner (or any address whitelisted in the
 *             contract's own ACL). Per-contract publication, no
 *             Foundation gate - the contract author knows its own
 *             access pattern best.
 *
 *         PHASE 2 PROMOTION:
 *           - When G1 Block-STM Besu fork is merged, the scheduler will
 *             call into a consensus precompile at 0x103 that reads this
 *             registry. The precompile interface is the
 *             `hintsFor(target, selector)` view below.
 *
 *         GOTCHA - NO SECURITY GUARANTEE:
 *           Hints don't change semantics. A wrong hint cannot make a TX
 *           succeed when it should have failed, only the other way
 *           around - and the scheduler always re-validates the actual
 *           r/w set after execution.
 *
 *         IMMUTABILITY:
 *           - Per-(target,selector) HintSet has a creator (the contract
 *             that registered itself). Only the creator can update.
 *           - No admin override.
 *
 *         WHAT IS SHIPPED TODAY (honest scope):
 *           - The AERE L1 base layer runs Hyperledger Besu and executes
 *             SEQUENTIALLY. This registry is advisory metadata for the
 *             EVENTUAL Besu Block-STM fork; it does NOT make the L1 parallel.
 *           - A real, correct, benchmarked Block-STM parallel executor DOES
 *             exist and is wired into the AERE ROLLUP layer: see
 *             `parallel-executor/` (Rust) and `rollup-sequencer/`. The rollup
 *             sequencer uses it to execute L2 batches across cores and commit a
 *             keccak256 state root via AereRollupSettlement.proposeStateRoot.
 *             Correctness (parallel == sequential) is proven over thousands of
 *             randomized conflicting batches. That is the concrete parallel-
 *             execution path on AERE today; L1 stays Besu-sequential.
 */
contract AereBlockSTMRegistry {

    /* ================================ types ================================ */

    enum Mode { READ, WRITE, READWRITE }

    struct SlotHint {
        bytes32 slotPrefix;   // keccak256 of the slot path prefix
        Mode    mode;
    }

    struct HintSet {
        address publisher;    // who published - only this address can update
        SlotHint[] hints;
        uint64  publishedAt;
    }

    /* ================================ storage ================================ */

    /// @notice (target, selector) → most recent HintSet.
    mapping(bytes32 => HintSet) internal _hints;

    /* ================================ events ================================ */

    event HintsPublished(address indexed target, bytes4 indexed selector, address indexed publisher, uint256 hintCount);
    event HintsCleared(address indexed target, bytes4 indexed selector, address indexed publisher);

    /* ================================ errors ================================ */

    error NotPublisher();
    error TooManyHints();

    /* ============================== publication ============================= */

    /// @notice Publish r/w hints for (target, selector). Only the first
    ///         publisher can later update or clear. We do not enforce
    ///         publisher == target - the target's owner can register
    ///         from a separate ops wallet.
    function publish(address target, bytes4 selector, SlotHint[] calldata hints) external {
        if (hints.length > 64) revert TooManyHints();
        bytes32 key = _key(target, selector);
        HintSet storage h = _hints[key];
        if (h.publisher == address(0)) {
            // AUDIT FIX (HIGH #27): first publisher must be the target itself
            // or its Ownable owner (best-effort). Prevents front-run squatting
            // by attackers who would poison the Block-STM scheduler.
            if (msg.sender != target && msg.sender != _tryOwner(target)) revert NotPublisher();
            h.publisher = msg.sender;
        } else if (h.publisher != msg.sender) {
            revert NotPublisher();
        }
        delete h.hints;
        for (uint256 i = 0; i < hints.length; i++) h.hints.push(hints[i]);
        h.publishedAt = uint64(block.timestamp);
        emit HintsPublished(target, selector, msg.sender, hints.length);
    }

    /// @dev best-effort Ownable.owner() probe; returns address(0) on failure.
    function _tryOwner(address target) internal view returns (address) {
        (bool ok, bytes memory data) = target.staticcall(abi.encodeWithSignature("owner()"));
        if (ok && data.length >= 32) return abi.decode(data, (address));
        return address(0);
    }

    /// @notice Clear hints for (target, selector). Only the original publisher.
    function clear(address target, bytes4 selector) external {
        bytes32 key = _key(target, selector);
        HintSet storage h = _hints[key];
        if (h.publisher != msg.sender) revert NotPublisher();
        delete h.hints;
        h.publishedAt = uint64(block.timestamp);
        emit HintsCleared(target, selector, msg.sender);
    }

    /* ================================ views ================================ */

    function hintsFor(address target, bytes4 selector) external view returns (
        address publisher, SlotHint[] memory hints, uint64 publishedAt
    ) {
        HintSet storage h = _hints[_key(target, selector)];
        return (h.publisher, h.hints, h.publishedAt);
    }

    function publisherOf(address target, bytes4 selector) external view returns (address) {
        return _hints[_key(target, selector)].publisher;
    }

    /* ================================ internal ============================== */

    function _key(address target, bytes4 selector) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(target, selector));
    }
}
