// 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)); } }