Blockchain / Global / Address

Contract

0x70d7Ff071940a1f28b17c2ff68609866B0bF684c

Balance
0 SOMI
Transactions sent
1
Type
Contract · 17,663 bytes of code

OracleHub Verified

Compiler
solc 0.8.34+commit.80d5c536
Language
Solidity
Optimization
Yes, 100 runs
EVM version
cancun
License
—
Verified
2026-10-12

src/adapters/OracleHub.sol

and 26 more files

Solidity
// SPDX-License-Identifier: MIT

pragma solidity 0.8.34;

import {MarketsCore} from "../MarketsCore.sol";
import {IOracleAdapter} from "../interfaces/IOracleAdapter.sol";
import {IOracleAnswerReceiver, PAYOUT_VECTOR_DENOMINATOR} from "../interfaces/IOracleAnswerReceiver.sol";
import {IProphecyOracle} from "../interfaces/IProphecyOracle.sol";
import {
    ISomniaEventHandler,
    ISomniaReactivityPrecompile,
    SomniaExtensions
} from "../interfaces/ISomniaReactivityPrecompile.sol";
import {
    Answer,
    QuestionDefinition,
    QuestionSourceType,
    QuestionStatus,
    VoidReason
} from "../types/OracleTypes.sol";
import {
    Ownable2StepUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {
    ERC165Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @title OracleHub
/// @notice Oracle v2 (DESIGN §8e): the ONE governance-approved oracle adapter — the
///         protocol's oracle brain. Wraps ProphecyOracle + Somnia reactivity into the
///         `IOracleAdapter` interface and adds, on top of the plain adapter it replaces:
///
///         1. CONTENT-ADDRESSED SCHEDULING (question dedup, §7.3): `scheduleQuestion`
///            keys template definitions (JSON candle sources) by everything
///            answer-relevant; the same definition returns the SAME question id and is
///            charged only marginal cost (`getSchedulingCost` quotes 0 for a reuse).
///            Non-template definitions bypass dedup and pass straight through. When a
///            shared question saturates the module's fan-out cap the hub schedules a
///            second identical question instead of reverting (cap-split).
///         2. EARMARK-AT-CREATION RESOLUTION FUNDING + BOUNDED-DRAIN METERING (§8e):
///            the resolution reserve is ATTACHED to the market's creation value and
///            LOCKED per-market at `onBind` (`earmarked[operatorId]`, receiver-gated,
///            payable — `msg.value == resolveReserve()`). It is NEVER withdrawable
///            while the market is live, so resolution funds are ALWAYS present and can
///            never be pulled out from under a bound market. At resolution the callback
///            measures each market's resolve cost with `gasleft()`, charges the earmark
///            up to `min(cost, reserve)`, RELEASES the earmark, and credits any surplus
///            (`reserve − charged`) to the operator's withdrawable `credit`. A cost
///            exceeding the reserve (rare) is the hub's EXPLICIT, event-visible subsidy
///            — never a silent leak. Conservation is wei-exact and reconstructable from
///            `CallbackAccounted`: `Σ charged + subsidy == (measuredGas + overhead) *
///            gasprice`. FUNDS-ALWAYS-PRESENT invariant: `earmarked[op] ==
///            outstanding[op] × resolveReserve()` at every quiescent point.
///         3. VECTOR DELIVERY (§2B) with BOUNDED DRAIN (§8e): the `AnswerPosted`
///            callback FIRST stores the answer for the qid (O(1), fan-out-independent),
///            enqueues the qid, then drains its bound-market list resolving up to
///            `maxResolvesPerCallback` markets (or until `gasleft()` dips below the
///            reserve). If markets remain the hub arms a `Schedule` continuation sub for
///            the NEXT block to resume the drain exactly where it stopped — mirroring
///            `MarketCreator`'s roll-drain. Thousands of markets resolve across a handful
///            of sub-second blocks; no single callback exceeds its gas envelope.
///
///         The native-token bond required by the reactivity precompile plus every
///         operator's earmarked reserves and accrued credit are held by this contract;
///         owner withdrawals (`withdraw`) only ever move an operator's own withdrawable
///         `credit` out, gated to that operator's owner — the earmark is untouchable.
///         UUPS-upgradeable so gas params and logic can be
///         updated without losing the subscription ID or the on-chain address.
contract OracleHub is
    ERC165Upgradeable,
    ISomniaEventHandler,
    IOracleAdapter,
    UUPSUpgradeable,
    Ownable2StepUpgradeable,
    ReentrancyGuardTransient
{
    /// @notice The stored answer for a qid whose AnswerPosted has fired — set O(1)
    ///         BEFORE any resolve so the drain tail is safe no matter how large.
    /// @param outcomeIdx Winning outcome index reported by the oracle (bucket mode).
    /// @param voided True if the oracle voided the question.
    /// @param present True once the answer has been stored (distinguishes a real
    ///        outcome 0 from an absent answer).
    struct StoredAnswer {
        uint8 outcomeIdx;
        bool voided;
        bool present;
    }

    /// @custom:storage-location erc7201:prophecy.storage.OracleHub
    struct OracleHubStorage {
        // Mutable gas params — can be updated via setGasParams() without redeploying.
        /// @notice Miner tip (wei) passed to the reactivity precompile for callbacks.
        uint64 priorityFeePerGas;
        /// @notice EIP-1559 fee cap (wei) for reactivity callbacks; also prices `resolveReserve`.
        uint64 maxFeePerGas;
        /// @notice Gas budget allocated to each reactivity callback invocation.
        uint64 gasLimit;
        /// @notice Precompile-assigned AnswerPosted subscription id; 0 until `enableReactivity`.
        uint256 subscriptionId;
        /// @notice Conservative per-market resolve-slice gas (module `_resolve` +
        ///         auto-finalize). Sizes `resolveReserve()` (the create gate) — never
        ///         what is charged (the charge is the measured cost).
        uint64 perMarketResolveGas;
        /// @notice Explicitly-attributed per-callback overhead the in-EVM measurement
        ///         cannot capture (intrinsic gas, dispatch, drain bookkeeping). Split
        ///         pro-rata across the markets resolved in a callback; surfaced in
        ///         `CallbackAccounted`.
        uint64 callbackBaseGas;
        /// @notice Belt-and-suspenders cap on markets resolved per callback (§8e ≈ 6).
        uint32 maxResolvesPerCallback;
        /// @notice Gas that must remain before starting another resolve in the drain
        ///         loop (§8e ≈ 3M — one more resolve's headroom).
        uint64 resolveGasReserve;
        /// @notice Dedup registry (§7.3): canonical question key => the currently
        ///         ACTIVE oracle question id for that key (rebound on cap-split).
        mapping(bytes32 questionKey => uint256 oracleQuestionId) questionIdByKey;
        /// @notice Lifetime bind count per question id — drives the cap-split
        ///         decision. Never decremented (markets bind before resolution).
        mapping(uint256 oracleQuestionId => uint32 count) bindCount;
        /// @notice Every marketId bound to a question id (the fan-out list the drain
        ///         walks). Appended by `onBind`.
        mapping(uint256 oracleQuestionId => bytes32[] markets) qidMarkets;
        /// @notice marketId => the operator whose earmark funds its resolve; zeroed at
        ///         resolution to mark the market settled (both drain + backstop paths).
        mapping(bytes32 marketId => uint32 operatorId) operatorOf;
        /// @notice Native LOCKED for an operator's outstanding (bound-but-unresolved)
        ///         markets. Grows by `resolveReserve()` per `onBind`, shrinks by the
        ///         market's reserve at resolution. NEVER withdrawable — this is the
        ///         funds-always-present guarantee (`earmarked == outstanding × reserve`).
        mapping(uint32 operatorId => uint256 locked) earmarked;
        /// @notice Withdrawable surplus per operator: the accumulated `reserve − charged`
        ///         released post-resolution when the metered cost came in under the
        ///         earmark. The ONLY balance `withdraw` may draw from.
        mapping(uint32 operatorId => uint256 balance) credit;
        /// @notice Count of an operator's bound-but-unresolved markets. Bumped in
        ///         `onBind`, decremented at resolution. `earmarked == outstanding ×
        ///         resolveReserve()` at every quiescent point.
        mapping(uint32 operatorId => uint256 count) outstanding;
        /// @notice Stored oracle answer per qid (set O(1) on AnswerPosted, before drain).
        mapping(uint256 oracleQuestionId => StoredAnswer answer) storedAnswer;
        /// @notice FIFO queue of qids with a stored answer and unresolved markets.
        uint256[] pendingQids;
        /// @notice Head cursor into `pendingQids` (front of the FIFO; entries below it
        ///         are already fully drained). Cheaper than shifting the array.
        uint256 pendingHead;
        /// @notice Resume cursor into `qidMarkets[frontQid]` for the in-progress drain.
        uint256 resolveResumeIndex;
        /// @notice Precompile-assigned id of the live `Schedule` continuation sub, or 0
        ///         when the drain queue is empty (no continuation armed).
        uint256 continuationSubId;
        // ---- Append-only fields below this line ----
        /// @notice The EXACT reserve native each market earmarked at `onBind`. Release
        ///         reads THIS (not the live `resolveReserve()`), so retuning the gas
        ///         params (setGasParams/setDrainParams) between a market's bind and its
        ///         resolution can never desync `earmarked` or underflow it — each market
        ///         releases precisely what it locked. Deleted on settle.
        mapping(bytes32 marketId => uint256 reserve) reservedFor;
        /// @notice The account that FRONTED a market's reserve at `onBind` — the create
        ///         caller. The resolution surplus is credited back to THIS address (not
        ///         the operator), so a user creating a market on an open venue is never
        ///         out-of-pocket for more than the true resolution gas. `address(0)` for
        ///         markets bound via the legacy 3-arg `onBind` (pre-A1) — those fall back
        ///         to crediting the operator (old behaviour). Deleted on settle.
        mapping(bytes32 marketId => address payer) payerOf;
        /// @notice Withdrawable surplus credit owned by the reserve-payer (the create
        ///         caller). Fed by `payerOf`-routed surplus at resolve; drained by
        ///         `withdrawMyCredit`. Distinct from operator `credit` — never mixed.
        mapping(address payer => uint256 balance) payerCredit;
    }

    /// @notice Precomputed topic0 for
    ///         `AnswerPosted(uint256,uint8,string,int256,bool,uint8,uint256[])`.
    /// @dev String literal fed to keccak256 is the canonical event signature and cannot be shortened.
    ///      The `uint8` between `bool voided` and `uint256[] receiptIds` is the ABI encoding of the
    ///      `VoidReason` enum the oracle adds so subscribers can distinguish normal resolutions
    ///      (`reason=None`) from each void cause.
    bytes32 public constant ANSWER_POSTED_TOPIC =
    // solhint-disable-next-line gas-small-strings
    keccak256("AnswerPosted(uint256,uint8,string,int256,bool,uint8,uint256[])");

    /// @notice topic0 of the precompile's `Schedule(uint256)` event — the continuation
    ///         sub fires this to resume an in-progress drain in the next block.
    bytes32 public constant SCHEDULE_TOPIC = ISomniaReactivityPrecompile.Schedule.selector;

    /// @notice Bind ceiling per question id before the hub cap-splits (schedules a
    ///         second identical question for the same key instead of reverting).
    /// @dev Mirrors the module's `_MAX_MARKETS_PER_QUESTION` fan-out cap — the hub
    ///      rotates the key to a fresh question BEFORE the module-side cap can revert
    ///      an honest create.
    uint32 public constant MAX_BINDS_PER_QUESTION = 64;

    // keccak256(abi.encode(uint256(keccak256("prophecy.storage.OracleHub")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant _OracleHubStorageLocation =
        0xc76582d1d71e6255255c2c645d090a8d3d27e591eaecfafbd9a64727e1708d00;

    // Immutables — baked into implementation bytecode; accessible via delegatecall.
    /// @notice ProphecyOracle entry-point contract used for submit/query.
    IProphecyOracle public immutable ORACLE;
    /// @notice OracleCore address that actually emits `AnswerPosted`; subscribed to for reactivity.
    address public immutable ORACLE_CORE;
    /// @notice Somnia reactivity precompile used to subscribe/unsubscribe.
    ISomniaReactivityPrecompile public immutable REACTIVITY;
    /// @notice Module that receives resolve callbacks (BinaryMarketsModule).
    IOracleAnswerReceiver public immutable RECEIVER;
    /// @notice MarketsCore — read to gate `withdraw` on the operator's owner.
    MarketsCore public immutable MARKETS_CORE;

    /// @notice Transient re-entrancy-style flag distinguishing the two settlement paths
    ///         (§8e). Set true for the duration of `_drain`; `onResolved` (the module's
    ///         resolve-commit hook) checks it: TRUE => the drain's `_debitResolved`
    ///         settles this market (metered), so `onResolved` RETURNS; FALSE =>
    ///         `pokeOracle` drove the resolve (no metering available), so `onResolved`
    ///         settles the backstop (flat reserve). Guarantees exactly-once settlement
    ///         across both paths — never double-charged. Cleared at end of tx.
    bool private transient _draining;

    /// @notice Emitted when native currency is received via `receive()` (hub float / bond top-up).
    /// @param from Address that sent the native currency.
    /// @param amount Amount of native currency received, in wei.
    event Deposited(address indexed from, uint256 indexed amount);
    /// @notice Emitted when a market's resolution reserve is earmarked (locked) at
    ///         `onBind` — attached with the market-creation value, per market.
    /// @param operatorId Operator whose earmark grew.
    /// @param marketId Market whose reserve was locked.
    /// @param amount Wei earmarked (== `resolveReserve()` at bind time).
    event ReserveEarmarked(uint32 indexed operatorId, bytes32 indexed marketId, uint256 amount);
    /// @notice Emitted at resolution (drain path) when the metered cost came in under
    ///         the earmark and the surplus is credited to the operator's withdrawable
    ///         balance.
    /// @param operatorId Operator credited.
    /// @param marketId Market whose reserve surplus was credited.
    /// @param amount Wei credited (`reserve − charged`).
    event SurplusCredited(uint32 indexed operatorId, bytes32 indexed marketId, uint256 amount);
    /// @notice Emitted when an operator's withdrawable credit is withdrawn via `withdraw`.
    /// @param operatorId Operator whose credit was drawn down.
    /// @param to Recipient of the native transfer.
    /// @param amount Wei withdrawn.
    event CreditWithdrawn(uint32 indexed operatorId, address indexed to, uint256 amount);
    /// @notice A1: resolution surplus credited back to the market's reserve-payer (the
    ///         create caller) instead of the operator. Emitted in place of
    ///         `SurplusCredited` whenever the market recorded a payer at bind.
    /// @param payer Account that fronted the reserve; owns the credited surplus.
    /// @param marketId Market whose resolution produced the surplus.
    /// @param amount Wei credited to the payer's withdrawable balance.
    event PayerSurplusCredited(address indexed payer, bytes32 indexed marketId, uint256 amount);
    /// @notice A1: emitted when a payer withdraws their surplus credit via `withdrawMyCredit`.
    /// @param payer Account whose payer-credit was drawn down.
    /// @param to Recipient of the native transfer.
    /// @param amount Wei withdrawn.
    event PayerCreditWithdrawn(address indexed payer, address indexed to, uint256 amount);
    /// @notice Emitted when the reactivity gas parameters are updated by the owner.
    /// @param priorityFeePerGas New miner tip (wei) for future subscriptions.
    /// @param maxFeePerGas New EIP-1559 fee cap (wei) for future subscriptions.
    /// @param gasLimit New callback gas budget for future subscriptions.
    event GasParamsUpdated(
        uint64 indexed priorityFeePerGas, uint64 indexed maxFeePerGas, uint64 indexed gasLimit
    );
    /// @notice Emitted when the drain-metering parameters are updated by the owner.
    /// @param perMarketResolveGas New per-market reserve-sizing gas constant.
    /// @param callbackBaseGas New attributed callback-overhead constant.
    /// @param maxResolvesPerCallback New per-callback resolve cap.
    /// @param resolveGasReserve New drain-loop gas reserve.
    event DrainParamsUpdated(
        uint64 perMarketResolveGas,
        uint64 callbackBaseGas,
        uint32 maxResolvesPerCallback,
        uint64 resolveGasReserve
    );

    /// @notice Emitted when a NEW oracle question is scheduled through the hub.
    /// @param oracleQuestionId Oracle-assigned question id.
    /// @param questionKey Canonical dedup key (zero for non-template definitions).
    /// @param scheduler Caller that paid the oracle submission cost.
    /// @param oracleCost Native amount forwarded to the oracle for this submission.
    event QuestionScheduled(
        uint256 indexed oracleQuestionId,
        bytes32 indexed questionKey,
        address indexed scheduler,
        uint256 oracleCost
    );
    /// @notice Emitted when `scheduleQuestion` deduplicates onto an existing question
    ///         (same canonical key, fan-out cap not reached). Any attached value is refunded.
    /// @param oracleQuestionId The existing question id returned.
    /// @param questionKey Canonical dedup key that matched.
    /// @param scheduler Caller whose definition deduplicated.
    event QuestionReused(
        uint256 indexed oracleQuestionId, bytes32 indexed questionKey, address indexed scheduler
    );
    /// @notice Emitted when a key's active question saturated `MAX_BINDS_PER_QUESTION`
    ///         and the hub scheduled a second identical question in its place.
    /// @param questionKey Canonical dedup key that rotated.
    /// @param supersededQuestionId The saturated question id (stays live for its bound markets).
    /// @param newQuestionId The fresh question id the key now resolves to.
    event QuestionCapSplit(
        bytes32 indexed questionKey, uint256 indexed supersededQuestionId, uint256 indexed newQuestionId
    );

    /// @notice Emitted when a market is bound to a question (records the operator
    ///         attribution + fan-out entry the drain later resolves).
    /// @param oracleQuestionId Question id the market bound to.
    /// @param marketId Market bound.
    /// @param operatorId Operator whose earmark funds this market's resolve.
    /// @param bindCount Lifetime bind count for the question after this bind.
    event MarketBound(
        uint256 indexed oracleQuestionId,
        bytes32 indexed marketId,
        uint32 indexed operatorId,
        uint32 bindCount
    );

    /// @notice Emitted once per resolution delivery with the translated payout vector.
    /// @param oracleQuestionId Question whose answer was delivered.
    /// @param marketId Market the vector was delivered to.
    /// @param payoutDenominator Denominator of the delivered vector (`PAYOUT_VECTOR_DENOMINATOR`).
    /// @param payoutNumerators Translated numerators (one-hot on a win, uniform on a void).
    /// @param voided True if the oracle voided the question.
    event AnswerDelivered(
        uint256 indexed oracleQuestionId,
        bytes32 indexed marketId,
        uint32 payoutDenominator,
        uint256[] payoutNumerators,
        bool voided
    );

    /// @notice Emitted once per market debited in the drain: the EXACT measured resolve
    ///         cost charged against this market's earmark (capped at the reserve).
    /// @param marketId Market resolved.
    /// @param operatorId Operator debited.
    /// @param measuredGas `gasleft()`-wrapped gas of this market's `resolveOne` slice.
    /// @param overheadShare This market's pro-rata share of the callback overhead gas.
    /// @param cost Wei the resolve cost worked out to `(measuredGas + overheadShare) * gasprice`.
    /// @param charged Wei actually debited from the earmark (== `min(cost, reserve)`).
    event MarketResolveCharged(
        bytes32 indexed marketId,
        uint32 indexed operatorId,
        uint256 measuredGas,
        uint256 overheadShare,
        uint256 cost,
        uint256 charged
    );

    /// @notice Per-callback conservation record (the on-chain audit trail for the
    ///         exact-metering invariant). Reconstructible to the wei from events:
    ///         `totalCost == (measuredGas + overheadGasAttributed) * gasPrice`;
    ///         Σ of this callback's `MarketResolveCharged.charged == totalCharged`;
    ///         `totalCharged + subsidy == totalCost` (the subsidy is the earmark-capped
    ///         shortfall — the hub's explicit, visible subsidy, never a silent leak).
    /// @param marketsResolved Number of markets resolved in this callback.
    /// @param gasPrice `tx.gasprice` of the callback.
    /// @param measuredGas Σ `gasleft()`-wrapped gas across the resolved markets.
    /// @param overheadGasAttributed The `callbackBaseGas` overhead term attributed this callback.
    /// @param totalCost The exact wei of resolve cost: `(measuredGas + overhead) * gasPrice`.
    /// @param totalCharged Σ wei actually debited from market earmarks.
    /// @param subsidy `totalCost − totalCharged` — the hub's explicit subsidy (0 when covered).
    /// @param pendingRemaining Markets still queued after this callback (0 = drain complete).
    event CallbackAccounted(
        uint256 marketsResolved,
        uint256 gasPrice,
        uint256 measuredGas,
        uint256 overheadGasAttributed,
        uint256 totalCost,
        uint256 totalCharged,
        uint256 subsidy,
        uint256 pendingRemaining
    );

    /// @notice Emitted when a `Schedule` continuation sub is (re)armed to resume the
    ///         drain in the next block, or torn down when the queue empties.
    /// @param subscriptionId Precompile sub id of the armed continuation (0 = torn down).
    /// @param pendingRemaining Markets still queued when the (re)arm/teardown happened.
    event DrainContinuation(uint256 subscriptionId, uint256 pendingRemaining);

    /// @notice Thrown when `onEvent` is called by anything other than the Somnia reactivity precompile.
    error NotReactivity();
    /// @notice Thrown when an AnswerPosted callback fires with an emitter other than ORACLE_CORE.
    error WrongEmitter();
    /// @notice Thrown when a reactivity callback's topic0 matches neither AnswerPosted nor Schedule.
    error WrongEvent();
    /// @notice Thrown when the native withdraw transfer fails.
    error WithdrawFailed();
    /// @notice Thrown when `withdraw` would exceed the operator's withdrawable credit.
    error InsufficientCredit();
    /// @notice Thrown when `withdraw` is called by anything other than the operator's owner.
    error NotOperatorOwner();
    /// @notice Thrown when `withdraw` names operator id 0 (reserved).
    error InvalidOperator();
    /// @notice Thrown when `enableReactivity` is called after a subscription is already active.
    error AlreadySubscribed();
    /// @notice Thrown when `enableReactivity` runs against a zero-address reactivity precompile.
    error NoPrecompile();
    /// @notice Thrown when the constructor's `IOracleAnswerReceiver` probe fails ERC-165.
    error InvalidReceiver();
    /// @notice Thrown when `onBind` / `onResolved` is called by anything other than the
    ///         configured RECEIVER.
    error NotReceiver();
    /// @notice Thrown when `onBind`'s attached value is not exactly `resolveReserve()`
    ///         (the earmark-at-creation reserve, DESIGN §8e).
    error WrongReserveAttached();
    /// @notice Thrown when `scheduleQuestion` is underfunded for a NEW question's oracle cost.
    error UnderpaidSchedulingCost();
    /// @notice Thrown when refunding excess `scheduleQuestion` value fails.
    error RefundFailed();
    /// @notice Thrown when `pullAnswer` / `pullNumericAnswer` targets a question the
    ///         oracle has not finalised yet.
    error QuestionNotFinal();

    /// @notice Set immutables and disable initializers on the implementation.
    /// @param oracle_ ProphecyOracle entry-point used for submit/query calls.
    /// @param oracleCore_ OracleCore address emitting `AnswerPosted`; subscribed to for reactivity.
    /// @param reactivity_ Somnia reactivity precompile used to manage the subscription.
    /// @param receiver_ Module that receives resolve callbacks.
    /// @param marketsCore_ MarketsCore — read to gate `withdraw` on the operator's owner.
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(
        IProphecyOracle oracle_,
        address oracleCore_,
        ISomniaReactivityPrecompile reactivity_,
        IOracleAnswerReceiver receiver_,
        MarketsCore marketsCore_
    ) {
        _disableInitializers();
        try receiver_.supportsInterface(type(IOracleAnswerReceiver).interfaceId) returns (bool ok) {
            if (!ok) revert InvalidReceiver();
        } catch {
            revert InvalidReceiver();
        }
        ORACLE = oracle_;
        ORACLE_CORE = oracleCore_;
        REACTIVITY = reactivity_;
        RECEIVER = receiver_;
        MARKETS_CORE = marketsCore_;
    }

    /// @notice Accept native currency (reactivity bond top-up / hub float). NOT credited
    ///         to any operator — resolution funding is attached per-market at `onBind`.
    receive() external payable {
        emit Deposited(msg.sender, msg.value);
    }

    /// @notice Initialize proxy storage. Called once via the ERC1967 proxy constructor.
    /// @param admin_ Address granted owner and upgrade authority.
    /// @param priorityFeePerGas_ Miner tip (wei) passed to the reactivity precompile for callbacks.
    /// @param maxFeePerGas_ EIP-1559 fee cap (wei) for reactivity callbacks.
    /// @param gasLimit_ Gas budget allocated to each reactivity callback invocation.
    /// @param perMarketResolveGas_ Conservative per-market resolve-slice gas (reserve sizing).
    /// @param callbackBaseGas_ Explicitly-attributed per-callback overhead gas (metering).
    /// @param maxResolvesPerCallback_ Belt-and-suspenders per-callback resolve cap (§8e ≈ 6).
    /// @param resolveGasReserve_ Gas reserve that breaks the drain loop to the next block (§8e ≈ 3M).
    function initialize(
        address admin_,
        uint64 priorityFeePerGas_,
        uint64 maxFeePerGas_,
        uint64 gasLimit_,
        uint64 perMarketResolveGas_,
        uint64 callbackBaseGas_,
        uint32 maxResolvesPerCallback_,
        uint64 resolveGasReserve_
    ) external initializer {
        __ERC165_init();
        __Ownable_init(admin_);
        // No __UUPSUpgradeable_init() since OZ 5.5: UUPSUpgradeable is no longer
        // transpiled (stateless; the old transpiled init was an empty no-op).
        OracleHubStorage storage $ = _getOracleHubStorage();
        $.priorityFeePerGas = priorityFeePerGas_;
        $.maxFeePerGas = maxFeePerGas_;
        $.gasLimit = gasLimit_;
        $.perMarketResolveGas = perMarketResolveGas_;
        $.callbackBaseGas = callbackBaseGas_;
        $.maxResolvesPerCallback = maxResolvesPerCallback_;
        $.resolveGasReserve = resolveGasReserve_;
    }

    /// @notice Update reactivity gas parameters. Takes effect on the next
    ///         `enableReactivity()` / `migrateSubscription()` call; existing
    ///         subscriptions are unaffected. `maxFeePerGas` ALSO reprices `resolveReserve()`.
    /// @param priorityFeePerGas_ New miner tip (wei) for future subscriptions.
    /// @param maxFeePerGas_ New EIP-1559 fee cap (wei) for future subscriptions.
    /// @param gasLimit_ New callback gas budget for future subscriptions.
    function setGasParams(uint64 priorityFeePerGas_, uint64 maxFeePerGas_, uint64 gasLimit_)
        external
        onlyOwner
    {
        OracleHubStorage storage $ = _getOracleHubStorage();
        $.priorityFeePerGas = priorityFeePerGas_;
        $.maxFeePerGas = maxFeePerGas_;
        $.gasLimit = gasLimit_;
        emit GasParamsUpdated(priorityFeePerGas_, maxFeePerGas_, gasLimit_);
    }

    /// @notice Update the bounded-drain metering parameters. `perMarketResolveGas`
    ///         sizes the create-gate reserve; `callbackBaseGas` is the pro-rata overhead
    ///         attributed per callback; `maxResolvesPerCallback` + `resolveGasReserve`
    ///         bound one callback's work. Calibrate against `verify-oracle-gas`.
    /// @param perMarketResolveGas_ New per-market resolve-slice gas constant.
    /// @param callbackBaseGas_ New attributed callback overhead gas constant.
    /// @param maxResolvesPerCallback_ New per-callback resolve cap.
    /// @param resolveGasReserve_ New drain-loop gas reserve.
    function setDrainParams(
        uint64 perMarketResolveGas_,
        uint64 callbackBaseGas_,
        uint32 maxResolvesPerCallback_,
        uint64 resolveGasReserve_
    ) external onlyOwner {
        OracleHubStorage storage $ = _getOracleHubStorage();
        $.perMarketResolveGas = perMarketResolveGas_;
        $.callbackBaseGas = callbackBaseGas_;
        $.maxResolvesPerCallback = maxResolvesPerCallback_;
        $.resolveGasReserve = resolveGasReserve_;
        emit DrainParamsUpdated(
            perMarketResolveGas_, callbackBaseGas_, maxResolvesPerCallback_, resolveGasReserve_
        );
    }

    /// @notice Register the Somnia reactivity AnswerPosted subscription. Admin-gated, one-shot.
    /// @return subId Precompile-assigned subscription id, also stored in `subscriptionId`.
    function enableReactivity() external onlyOwner returns (uint256 subId) {
        if (_getOracleHubStorage().subscriptionId != 0) revert AlreadySubscribed();
        return _subscribeAnswers();
    }

    /// @notice Unsubscribe the current AnswerPosted subscription and re-subscribe with the
    ///         latest topic + gas params. Used when the upstream event signature changes.
    /// @return subId Precompile-assigned subscription id, also stored in `subscriptionId`.
    function migrateSubscription() external onlyOwner returns (uint256 subId) {
        OracleHubStorage storage $ = _getOracleHubStorage();
        uint256 old = $.subscriptionId;
        if (old != 0) {
            REACTIVITY.unsubscribe(old);
            $.subscriptionId = 0;
        }
        return _subscribeAnswers();
    }

    // ---- Withdrawable operator credit -------------------------------

    /// @notice Withdraw from an operator's WITHDRAWABLE credit (the surplus accrued when
    ///         resolved markets metered under their earmark). Gated to the operator's
    ///         owner (read from `MarketsCore`). Draws ONLY from `credit` — the earmark
    ///         backing live markets is untouchable, so resolution funds are always
    ///         present. Cannot touch another operator's balance or the hub's own float.
    /// @param operatorId Operator whose credit to draw down.
    /// @param amount Wei to withdraw.
    /// @param to Recipient of the native transfer; must accept native.
    function withdraw(uint32 operatorId, uint256 amount, address payable to) external {
        if (operatorId == 0) revert InvalidOperator();
        if (msg.sender != MARKETS_CORE.getOperator(operatorId).owner) revert NotOperatorOwner();
        OracleHubStorage storage $ = _getOracleHubStorage();
        uint256 bal = $.credit[operatorId];
        if (amount > bal) revert InsufficientCredit();
        $.credit[operatorId] = bal - amount;
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert WithdrawFailed();
        emit CreditWithdrawn(operatorId, to, amount);
    }

    /// @notice A1: withdraw YOUR payer-credit — the resolution surplus refunded to the
    ///         account that fronted a market's reserve at create. Permissionless in the
    ///         sense that `msg.sender` can only ever draw down its OWN balance; it can
    ///         never touch operator credit or the earmark backing live markets.
    /// @param amount Wei to withdraw.
    /// @param to Recipient of the native transfer; must accept native.
    function withdrawMyCredit(uint256 amount, address payable to) external {
        OracleHubStorage storage $ = _getOracleHubStorage();
        uint256 bal = $.payerCredit[msg.sender];
        if (amount > bal) revert InsufficientCredit();
        $.payerCredit[msg.sender] = bal - amount;
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert WithdrawFailed();
        emit PayerCreditWithdrawn(msg.sender, to, amount);
    }

    /// @dev Route a resolution surplus to the market's recorded reserve-payer (A1) if
    ///      one exists, else the operator (legacy). Clears `payerOf` regardless (so a
    ///      zero-surplus settle still cleans up). No-op on a zero amount for a
    ///      payer-less market (nothing to credit).
    function _creditSurplus(OracleHubStorage storage $, bytes32 marketId, uint32 operatorId, uint256 amount)
        internal
    {
        address payer = $.payerOf[marketId];
        if (payer != address(0)) {
            if (amount > 0) {
                $.payerCredit[payer] += amount;
                emit PayerSurplusCredited(payer, marketId, amount);
            }
            delete $.payerOf[marketId];
        } else if (amount > 0) {
            $.credit[operatorId] += amount;
            emit SurplusCredited(operatorId, marketId, amount);
        }
    }

    // ---- ISomniaEventHandler ----------------------------------------

    /// @notice Called by the Somnia reactivity precompile. Dispatches on the fired
    ///         event: an `AnswerPosted` on ORACLE_CORE stores the answer + enqueues the
    ///         qid + drains; a `Schedule` continuation fire resumes the in-progress drain.
    /// @dev Reverts if called by anything other than the reactivity precompile.
    /// @param emitter Contract that emitted the log.
    /// @param eventTopics Indexed log topics; topics[0] is the selector.
    /// @param data ABI-encoded non-indexed fields (AnswerPosted only).
    function onEvent(address emitter, bytes32[] calldata eventTopics, bytes calldata data)
        external
        override
        nonReentrant
    {
        if (msg.sender != address(REACTIVITY)) revert NotReactivity();
        if (eventTopics.length == 0) revert WrongEvent();
        bytes32 topic0 = eventTopics[0];

        if (topic0 == SCHEDULE_TOPIC) {
            // Continuation fire: the one-shot Schedule sub auto-removed on fire — clear
            // our bookkeeping + best-effort unsubscribe, then resume the drain.
            OracleHubStorage storage $ = _getOracleHubStorage();
            $.continuationSubId = 0;
            _drain();
            return;
        }

        if (topic0 != ANSWER_POSTED_TOPIC) revert WrongEvent();
        if (emitter != ORACLE_CORE) revert WrongEmitter();
        if (eventTopics.length < 2) revert WrongEvent();

        uint256 oracleQuestionId = uint256(eventTopics[1]);
        (uint8 outcomeIdx,,, bool voided,,) =
            abi.decode(data, (uint8, string, int256, bool, VoidReason, uint256[]));

        OracleHubStorage storage $s = _getOracleHubStorage();
        // FIRST: store the answer O(1) (fan-out-independent — the tail is safe no matter
        // how many markets are bound), then enqueue + drain.
        $s.storedAnswer[oracleQuestionId] =
            StoredAnswer({outcomeIdx: outcomeIdx, voided: voided, present: true});
        // Only enqueue qids that actually have bound markets (foreign AnswerPosted on the
        // shared core are ignored — the hub never resolves questions nobody bound).
        if ($s.qidMarkets[oracleQuestionId].length != 0) {
            $s.pendingQids.push(oracleQuestionId);
        }
        _drain();
    }

    // ---- IOracleAdapter ----------------------------------------------

    /// @notice Schedule a question, content-addressed (DESIGN §8c). A template
    ///         definition whose canonical key is already scheduled returns the EXISTING
    ///         question id and refunds the attached value; new questions (or a saturated
    ///         key — cap-split) forward exactly the oracle's submission cost and refund
    ///         the excess. Non-template definitions bypass dedup entirely.
    /// @param def Full question definition.
    /// @return oracleQuestionId Oracle-assigned (or deduplicated) question id.
    function scheduleQuestion(QuestionDefinition calldata def)
        external
        payable
        override
        nonReentrant
        returns (uint256 oracleQuestionId)
    {
        OracleHubStorage storage $ = _getOracleHubStorage();
        bytes32 key = _dedupKey(def);
        if (key != bytes32(0)) {
            uint256 existing = $.questionIdByKey[key];
            if (existing != 0 && $.bindCount[existing] < MAX_BINDS_PER_QUESTION) {
                emit QuestionReused(existing, key, msg.sender);
                _refundExcess(msg.value);
                return existing;
            }
        }

        uint256 cost = ORACLE.getSubmissionCost(def);
        if (msg.value < cost) revert UnderpaidSchedulingCost();
        oracleQuestionId = ORACLE.submitQuestion{value: cost}(def);
        if (key != bytes32(0)) {
            uint256 superseded = $.questionIdByKey[key];
            $.questionIdByKey[key] = oracleQuestionId;
            if (superseded != 0) emit QuestionCapSplit(key, superseded, oracleQuestionId);
        }
        emit QuestionScheduled(oracleQuestionId, key, msg.sender, cost);
        _refundExcess(msg.value - cost);
    }

    /// @notice Record a market bind (DESIGN §8e, EARMARK-AT-CREATION): attribute the
    ///         market to its operator, append it to the qid fan-out list, bump the bind
    ///         count (cap-split input), and LOCK the resolution reserve. The reserve is
    ///         attached with the market-creation value: `msg.value` MUST equal exactly
    ///         `resolveReserve()` (revert `WrongReserveAttached` otherwise). The attached
    ///         wei is earmarked to the operator and its outstanding count bumped, so
    ///         resolution funds are ALWAYS present and can never be withdrawn out from
    ///         under this live market. Receiver-only, payable. Released at resolution.
    /// @param marketId Module-scoped market id being bound (the settlement-attribution key).
    /// @param operatorId Origin operator whose earmark funds this market's resolve.
    /// @param oracleQuestionId Question id the market bound to.
    /// @dev Legacy 3-arg form (pre-A1): no payer recorded, so the resolution surplus
    ///      falls back to the operator credit (old behaviour). Kept for a break-free
    ///      upgrade window — the module upgrades to the 4-arg form separately.
    function onBind(bytes32 marketId, uint32 operatorId, uint256 oracleQuestionId) external payable override {
        _onBind(marketId, operatorId, oracleQuestionId, address(0));
    }

    /// @notice Payer-aware bind (A1). Identical to the 3-arg form, but records the
    ///         `payer` that fronted the reserve (the create caller) so the resolution
    ///         SURPLUS is credited back to THAT account, not the operator. This is the
    ///         fairness fix for open venues: a user who creates a market is never out
    ///         more than the true resolution gas — the over-reserve returns to them.
    /// @param marketId Module-scoped market id being bound.
    /// @param operatorId Origin operator (earmark/solvency accounting stays operator-scoped).
    /// @param oracleQuestionId Question id the market bound to.
    /// @param payer Account that attached the reserve — receives the resolution surplus.
    function onBind(bytes32 marketId, uint32 operatorId, uint256 oracleQuestionId, address payer)
        external
        payable
    {
        _onBind(marketId, operatorId, oracleQuestionId, payer);
    }

    /// @dev Shared bind logic. `payer == address(0)` => surplus falls back to operator.
    function _onBind(bytes32 marketId, uint32 operatorId, uint256 oracleQuestionId, address payer) internal {
        if (msg.sender != address(RECEIVER)) revert NotReceiver();
        OracleHubStorage storage $ = _getOracleHubStorage();
        if (msg.value != _resolveReserve($)) revert WrongReserveAttached();
        $.earmarked[operatorId] += msg.value;
        // Store the EXACT amount locked for THIS market — release reads it back so a
        // later gas-param retune can never desync/underflow the earmark.
        $.reservedFor[marketId] = msg.value;
        if (payer != address(0)) $.payerOf[marketId] = payer;
        ++$.outstanding[operatorId];
        $.operatorOf[marketId] = operatorId;
        $.qidMarkets[oracleQuestionId].push(marketId);
        uint32 count = ++$.bindCount[oracleQuestionId];
        emit ReserveEarmarked(operatorId, marketId, msg.value);
        emit MarketBound(oracleQuestionId, marketId, operatorId, count);
    }

    /// @notice Resolve-commit hook (DESIGN §8e, EARMARK backstop). Called by the module
    ///         from `_resolve` at the irreversible resolve/void commit point — the ONE
    ///         gate every resolution passes (guaranteed once-per-market by the module's
    ///         `if (Resolved || Voided) return` guard), on BOTH settlement paths.
    /// @dev Two paths, one hook, never double-charged:
    ///      - DRAIN path (`_drain → RECEIVER.resolveOne → module._resolve → onResolved`):
    ///        `_draining` is TRUE, so this RETURNS — the drain's `_debitResolved` settles
    ///        this market with EXACT metering (releases earmark, credits surplus).
    ///      - BACKSTOP path (`module.pokeOracle → _resolve → onResolved`): `_draining` is
    ///        FALSE (no hub drain in flight), so settle the backstop here: no metering is
    ///        available, so charge the FULL reserve flat (release the earmark, decrement
    ///        outstanding, mark settled — `credit` unchanged, the reserve is consumed).
    ///      Receiver-gated; skips if the market is already settled or not hub-bound
    ///      (`operatorOf == 0`). Pure storage op — re-entrancy-safe.
    /// @param marketId Market whose resolution just committed.
    function onResolved(bytes32 marketId) external override {
        if (msg.sender != address(RECEIVER)) revert NotReceiver();
        if (_draining) return; // drain path: `_debitResolved` settles it (metered).
        OracleHubStorage storage $ = _getOracleHubStorage();
        uint32 operatorId = $.operatorOf[marketId];
        if (operatorId == 0) return; // already settled / not hub-bound.
        // Backstop path (`pokeOracle`): the CALLER paid this resolution's gas as a
        // normal tx — the hub's subscription float spent nothing. So the market's
        // full earmarked reserve is refunded to the operator's withdrawable credit
        // (nothing was consumed on the hub's side); the poker is the keeper who
        // absorbs the poke gas. Release the earmark and mint the refund as credit.
        // Release the EXACT reserve this market locked at bind (not the live
        // `resolveReserve()`, which may have been retuned since).
        uint256 reserve = $.reservedFor[marketId];
        $.earmarked[operatorId] -= reserve;
        --$.outstanding[operatorId];
        $.operatorOf[marketId] = 0;
        delete $.reservedFor[marketId];
        // Emit the same accounting events the drain does so the indexer stays
        // EXACT (not an event-derived upper bound): a zero-cost resolve charge
        // (hub spent nothing) plus the full-reserve surplus credit. A1: the surplus
        // (here the whole reserve — the poker paid gas) goes to the market's payer
        // when one was recorded, else the operator.
        emit MarketResolveCharged(marketId, operatorId, 0, 0, 0, 0);
        _creditSurplus($, marketId, operatorId, reserve);
    }

    /// @notice Returns the number of outcome slots for a ProphecyOracle question.
    /// @param oracleQuestionId Numeric question ID as stored in ProphecyOracle.
    /// @return slotCount Number of discrete outcomes.
    function getSlotCount(uint256 oracleQuestionId) external view override returns (uint8 slotCount) {
        return uint8(ORACLE.getOutcomeLabels(oracleQuestionId).length);
    }

    /// @notice Pulls the settled answer directly from ProphecyOracle (pull-fallback path).
    /// @param oracleQuestionId Numeric question ID as stored in ProphecyOracle.
    /// @return outcomeIdx Zero-based index of the winning outcome.
    /// @return voided True if the question was voided rather than resolved normally.
    function pullAnswer(uint256 oracleQuestionId)
        external
        view
        override
        returns (uint8 outcomeIdx, bool voided)
    {
        _requireFinal(oracleQuestionId);
        Answer memory a = ORACLE.getAnswer(oracleQuestionId);
        return (a.outcomeIdx, a.voided);
    }

    /// @notice Pulls the raw numeric answer directly from ProphecyOracle. Backs
    ///         reference-resolved markets. Reverts while the question is unanswered.
    /// @param oracleQuestionId Numeric question ID as stored in ProphecyOracle.
    /// @return numericValue Aggregated numeric answer in the question's native scale.
    /// @return voided True if the question was voided rather than resolved normally.
    function pullNumericAnswer(uint256 oracleQuestionId)
        external
        view
        override
        returns (int256 numericValue, bool voided)
    {
        _requireFinal(oracleQuestionId);
        Answer memory a = ORACLE.getAnswer(oracleQuestionId);
        return (a.numericValue, a.voided);
    }

    /// @notice Marginal scheduling cost for `def` (DESIGN §8c): 0 when the canonical
    ///         key is already scheduled below the bind cap, the full oracle submission
    ///         cost otherwise (new question or cap-split).
    /// @param def Question definition to price.
    /// @return cost Fee in wei that must be attached when calling scheduleQuestion.
    function getSchedulingCost(QuestionDefinition calldata def)
        external
        view
        override
        returns (uint256 cost)
    {
        bytes32 key = _dedupKey(def);
        if (key != bytes32(0)) {
            OracleHubStorage storage $ = _getOracleHubStorage();
            uint256 existing = $.questionIdByKey[key];
            if (existing != 0 && $.bindCount[existing] < MAX_BINDS_PER_QUESTION) return 0;
        }
        return ORACLE.getSubmissionCost(def);
    }

    /// @notice The resolution reserve earmarked (locked) per market at `onBind` — one
    ///         worst-case resolve: `perMarketResolveGas × maxFeePerGas`. This exact wei
    ///         MUST be attached to `onBind` (the market-creation value).
    /// @return reserve Native amount (wei) that funds one market's resolution.
    function resolveReserve() external view override returns (uint256 reserve) {
        return _resolveReserve(_getOracleHubStorage());
    }

    // ---- Storage getters ---------------------------------------------

    /// @notice Miner tip (wei) passed to the reactivity precompile for callbacks.
    function priorityFeePerGas() external view returns (uint64) {
        return _getOracleHubStorage().priorityFeePerGas;
    }

    /// @notice EIP-1559 fee cap (wei) for reactivity callbacks.
    function maxFeePerGas() external view returns (uint64) {
        return _getOracleHubStorage().maxFeePerGas;
    }

    /// @notice Gas budget allocated to each reactivity callback invocation.
    function gasLimit() external view returns (uint64) {
        return _getOracleHubStorage().gasLimit;
    }

    /// @notice Precompile-assigned AnswerPosted subscription id; 0 until `enableReactivity`.
    function subscriptionId() external view returns (uint256) {
        return _getOracleHubStorage().subscriptionId;
    }

    /// @notice Per-market resolve-slice gas constant (reserve sizing only).
    function perMarketResolveGas() external view returns (uint64) {
        return _getOracleHubStorage().perMarketResolveGas;
    }

    /// @notice Explicitly-attributed callback overhead gas constant (metering).
    function callbackBaseGas() external view returns (uint64) {
        return _getOracleHubStorage().callbackBaseGas;
    }

    /// @notice Belt-and-suspenders cap on markets resolved per callback.
    function maxResolvesPerCallback() external view returns (uint32) {
        return _getOracleHubStorage().maxResolvesPerCallback;
    }

    /// @notice Gas reserve that breaks the drain loop to the next block.
    function resolveGasReserve() external view returns (uint64) {
        return _getOracleHubStorage().resolveGasReserve;
    }

    /// @notice Native LOCKED for an operator's outstanding markets (never withdrawable).
    /// @param operatorId Operator to query.
    /// @return locked Wei earmarked (`== outstandingOf × resolveReserve()`).
    function earmarkedOf(uint32 operatorId) external view returns (uint256 locked) {
        return _getOracleHubStorage().earmarked[operatorId];
    }

    /// @notice Withdrawable surplus credited to an operator post-resolution.
    /// @param operatorId Operator to query.
    /// @return balance Wei of withdrawable credit.
    function creditOf(uint32 operatorId) external view returns (uint256 balance) {
        return _getOracleHubStorage().credit[operatorId];
    }

    /// @notice Count of an operator's bound-but-unresolved markets.
    /// @param operatorId Operator to query.
    /// @return count Outstanding market count.
    function outstandingOf(uint32 operatorId) external view returns (uint256 count) {
        return _getOracleHubStorage().outstanding[operatorId];
    }

    /// @notice Wei an operator's owner may withdraw right now (== `creditOf`; the earmark
    ///         backing live markets is never withdrawable).
    /// @param operatorId Operator to query.
    /// @return amount Withdrawable wei.
    function withdrawableOf(uint32 operatorId) external view returns (uint256 amount) {
        return _getOracleHubStorage().credit[operatorId];
    }

    /// @notice A1: a payer's withdrawable surplus credit (refunded resolution reserve).
    /// @param payer Account that fronted market reserves.
    /// @return amount Wei the payer can withdraw via `withdrawMyCredit`.
    function payerCreditOf(address payer) external view returns (uint256 amount) {
        return _getOracleHubStorage().payerCredit[payer];
    }

    /// @notice A1: the account that fronted a market's reserve (surplus recipient), or
    ///         `address(0)` for legacy/operator-credited markets.
    function payerOf(bytes32 marketId) external view returns (address payer) {
        return _getOracleHubStorage().payerOf[marketId];
    }

    /// @notice The ACTIVE question id for a canonical key (0 = never scheduled).
    /// @param questionKey Canonical dedup key (see `questionKeyOf`).
    /// @return oracleQuestionId Active question id the key resolves to.
    function questionIdByKey(bytes32 questionKey) external view returns (uint256 oracleQuestionId) {
        return _getOracleHubStorage().questionIdByKey[questionKey];
    }

    /// @notice Lifetime bind count for a question id.
    /// @param oracleQuestionId Question id to query.
    /// @return count Number of markets ever bound.
    function bindCount(uint256 oracleQuestionId) external view returns (uint32 count) {
        return _getOracleHubStorage().bindCount[oracleQuestionId];
    }

    /// @notice Every marketId bound to a question id (the fan-out list).
    /// @param oracleQuestionId Question id to query.
    /// @return markets Market ids bound to the question.
    function marketsForQuestion(uint256 oracleQuestionId) external view returns (bytes32[] memory markets) {
        return _getOracleHubStorage().qidMarkets[oracleQuestionId];
    }

    /// @notice Operator attributed to a market's earmark (0 once the market has settled).
    /// @param marketId Market to query.
    /// @return operatorId The market's origin operator, or 0 if settled / never bound.
    function operatorOf(bytes32 marketId) external view returns (uint32 operatorId) {
        return _getOracleHubStorage().operatorOf[marketId];
    }

    /// @notice The exact native reserve a market earmarked at `onBind`. This is the amount
    ///         its release will subtract from `earmarked` — snapshotted at bind so a later
    ///         gas-param retune can never desync it. 0 once the market has settled.
    /// @param marketId Market to query.
    /// @return reserve Reserve locked for this market, or 0 if settled / never bound.
    function reservedFor(bytes32 marketId) external view returns (uint256 reserve) {
        return _getOracleHubStorage().reservedFor[marketId];
    }

    /// @notice Number of markets still queued for resolution across all pending qids.
    /// @return remaining Markets awaiting drain (0 = idle / fully drained).
    function pendingResolves() external view returns (uint256 remaining) {
        return _pendingResolves(_getOracleHubStorage());
    }

    /// @notice Precompile-assigned id of the live `Schedule` continuation sub (0 = none).
    function continuationSubId() external view returns (uint256) {
        return _getOracleHubStorage().continuationSubId;
    }

    /// @notice Canonical dedup key for a definition; zero for non-template definitions.
    /// @param def Question definition to key.
    /// @return key The canonical key, or zero when dedup does not apply.
    function questionKeyOf(QuestionDefinition calldata def) external pure returns (bytes32 key) {
        return _dedupKey(def);
    }

    // ---- ERC-165 -----------------------------------------------------

    /// @notice Returns true for IOracleAdapter and ERC-165 itself; false for everything else.
    /// @param interfaceId ERC-165 interface identifier to probe.
    /// @return supported True if this contract advertises support for `interfaceId`.
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC165Upgradeable, IERC165)
        returns (bool supported)
    {
        return interfaceId == type(IOracleAdapter).interfaceId || super.supportsInterface(interfaceId);
    }

    // ---- Internal: bounded-drain resolution ----------------------------

    /// @dev Drain the pending-qid FIFO, resolving markets one at a time and debiting
    ///      each market's operator's prepaid balance by the EXACT measured cost, until
    ///      the per-callback cap or the gas reserve is hit. Mirrors
    ///      `MarketCreator._onSchedule`: if markets remain, arm a `Schedule` continuation
    ///      sub for the NEXT block to resume; when the queue empties, tear it down.
    ///
    ///      CONSERVATION (assertable to the wei from events):
    ///        totalCost   == (Σ measuredGas_i + callbackBaseGas) * tx.gasprice
    ///        overhead    == callbackBaseGas, split pro-rata across the resolved markets
    ///        charged_i   == min(cost_i, reserve_i)   (the market's earmark)
    ///        totalCharged + subsidy == totalCost   (subsidy = the visible shortfall)
    ///
    ///      `_draining` is TRUE for the whole call: the module's `onResolved` hook (fired
    ///      from inside `RECEIVER.resolveOne`) sees it and RETURNS, leaving settlement to
    ///      `_debitResolved` here — so the drain path is metered, never double-settled.
    // solhint-disable-next-line function-max-lines
    function _drain() internal {
        _draining = true;
        OracleHubStorage storage $ = _getOracleHubStorage();
        uint256 cap = $.maxResolvesPerCallback;
        uint256 reserve = $.resolveGasReserve;
        uint256 overheadTotal = $.callbackBaseGas;

        uint256 resolved;
        uint256 measuredTotal;
        uint256 chargedTotal;
        uint256 gasPrice = tx.gasprice;

        // Loop: always attempt at least one resolve per callback (the `resolved > 0`
        // guard) so a too-tight gas grant can never livelock the same cursor forever.
        // The per-market body lives in `_drainOne` so the loop frame stays within
        // the legacy code generator's reachable-stack window.
        while (true) {
            (,, bool have) = _frontMarket($);
            if (!have) break;
            if (resolved > 0 && (resolved >= cap || gasleft() < reserve)) break;

            measuredTotal += _drainOne($);
            ++resolved;
        }

        if (resolved != 0) {
            chargedTotal = _debitResolved($, measuredTotal, overheadTotal, resolved, gasPrice);
        }

        uint256 remaining = _pendingResolves($);
        _armOrTearContinuation($, remaining);

        if (resolved != 0) {
            uint256 totalCost = (measuredTotal + overheadTotal) * gasPrice;
            emit CallbackAccounted(
                resolved,
                gasPrice,
                measuredTotal,
                overheadTotal,
                totalCost,
                chargedTotal,
                totalCost - chargedTotal,
                remaining
            );
        }

        _draining = false;
    }

    /// @dev One iteration of `_drain`'s FIFO loop: translate the front market's
    ///      stored answer, deliver it (revert-isolated), emit, advance the cursor.
    ///      Extracted so the loop frame fits the legacy code generator's stack
    ///      window. The gas-measurement window (`g0` → `used`) wraps exactly the
    ///      same statements as the historical inline body.
    function _drainOne(OracleHubStorage storage $) private returns (uint256 used) {
        (uint256 qid, bytes32 marketId,) = _frontMarket($);

        StoredAnswer memory ans = $.storedAnswer[qid];
        (uint32 denom, uint256[] memory nums) = _translate(qid, ans.outcomeIdx, ans.voided);

        uint256 g0 = gasleft();
        // try/catch: a per-market revert must never revert the callback — the market
        // stays resolvable via `pokeOracle`, and the gas the attempt burned is still
        // real cost debited to the operator.
        try RECEIVER.resolveOne(marketId, denom, nums, ans.voided) {} catch {}
        used = g0 - gasleft();
        emit AnswerDelivered(qid, marketId, denom, nums, ans.voided);

        _advanceCursor($);
    }

    /// @dev Re-walk the markets resolved in THIS callback and settle each against its
    ///      earmark by its exact metered cost. The settle is a SECOND pass over the same
    ///      cursor range so per-market gas measurement (the first pass) is not polluted
    ///      by the settle's own SSTOREs.
    ///
    ///      This callback resolved `resolved` markets ending exactly at the current
    ///      FIFO position; walk backwards from there to recover the (qid, marketId)
    ///      slice, attributing overhead 1-wei-remainder-exact so
    ///      `Σ overheadShare == overheadTotal` and thus `Σ cost == totalCost`.
    ///
    ///      EARMARK SETTLEMENT per market: `charged = min(cost, reserve)`; release the
    ///      whole `reserve` from `earmarked`; credit any surplus (`reserve − charged`) to
    ///      the operator's withdrawable `credit`; a `cost > reserve` shortfall is the
    ///      hub's explicit subsidy (surfaced in `MarketResolveCharged` / `CallbackAccounted`);
    ///      decrement `outstanding` and zero `operatorOf` to mark the market settled.
    ///      The `operatorOf == 0` guard makes it idempotent should the same market ever
    ///      re-enter the slice (defensive — the FIFO never re-drains a market).
    function _debitResolved(
        OracleHubStorage storage $,
        uint256 measuredTotal,
        uint256 overheadTotal,
        uint256 resolved,
        uint256 gasPrice
    ) internal returns (uint256 chargedTotal) {
        // Overhead is split by measured-gas weight is overkill; a flat per-market split
        // with an exact remainder keeps `Σ overheadShare == overheadTotal` (the
        // conservation the event must reconstruct). measuredTotal is surfaced whole in
        // the event; per-market `measuredGas` here is the pro-rata attribution used only
        // for the per-market `MarketResolveCharged` breadcrumb (its Σ == measuredTotal).
        uint256 measShare = measuredTotal / resolved;
        uint256 measRem = measuredTotal % resolved;
        uint256 ohShare = overheadTotal / resolved;
        uint256 ohRem = overheadTotal % resolved;

        // The resolved slice ends at the current FIFO cursor; recover it going backward.
        // Per-market settlement lives in `_settleOne` so the loop frame stays within
        // the legacy code generator's reachable-stack window.
        bytes32[] memory slice = _resolvedSlice($, resolved);
        for (uint256 i; i < resolved; ++i) {
            uint256 meas = i < measRem ? measShare + 1 : measShare;
            uint256 oh = i < ohRem ? ohShare + 1 : ohShare;
            chargedTotal += _settleOne($, slice[i], meas, oh, gasPrice);
        }
    }

    /// @dev Settle ONE resolved market against its earmark (the historical inline
    ///      body of `_debitResolved`'s loop, unchanged): `charged = min(cost, reserve)`,
    ///      release the whole reserve, credit the surplus, mark settled. Skips if
    ///      already settled (defensive idempotency).
    function _settleOne(
        OracleHubStorage storage $,
        bytes32 marketId,
        uint256 meas,
        uint256 oh,
        uint256 gasPrice
    ) private returns (uint256 charged) {
        uint32 operatorId = $.operatorOf[marketId];
        // Release the EXACT reserve THIS market locked at bind — not the live
        // `resolveReserve()`, which may have been retuned since (that would
        // desync/underflow `earmarked`).
        uint256 reserve = $.reservedFor[marketId];
        uint256 cost = (meas + oh) * gasPrice;
        charged = cost < reserve ? cost : reserve;
        if (operatorId != 0) {
            $.earmarked[operatorId] -= reserve;
            // A1: the surplus (reserve − actual charged) goes back to the market's
            // reserve-payer when one was recorded, else the operator (legacy).
            // Called unconditionally so `payerOf` is cleared even on a 0 surplus.
            _creditSurplus($, marketId, operatorId, reserve - charged);
            --$.outstanding[operatorId];
            $.operatorOf[marketId] = 0;
            delete $.reservedFor[marketId];
        }
        emit MarketResolveCharged(marketId, operatorId, meas, oh, cost, charged);
    }

    /// @dev Recover the `resolved` marketIds drained in this callback, in resolution
    ///      order, by replaying the FIFO backward from the current (post-drain) cursor.
    function _resolvedSlice(OracleHubStorage storage $, uint256 resolved)
        internal
        view
        returns (bytes32[] memory slice)
    {
        slice = new bytes32[](resolved);
        // Walk the FIFO backward: reconstruct each step's (qid, index) as it was BEFORE
        // `_advanceCursor`, mirroring the forward drain exactly.
        uint256 head = $.pendingHead;
        uint256 idx = $.resolveResumeIndex;
        for (uint256 k; k < resolved; ++k) {
            // Step one position back in the forward order.
            if (idx == 0) {
                // We were at the start of the current front qid → the previous step was
                // the last market of the previous (already-popped) qid. Move head back.
                --head;
                idx = $.qidMarkets[$.pendingQids[head]].length;
            }
            --idx;
            slice[resolved - 1 - k] = $.qidMarkets[$.pendingQids[head]][idx];
        }
    }

    /// @dev Return the market at the FRONT of the drain FIFO (front qid + resume index),
    ///      skipping fully-drained / empty qids. `have == false` when the FIFO is empty.
    function _frontMarket(OracleHubStorage storage $)
        internal
        returns (uint256 qid, bytes32 marketId, bool have)
    {
        while ($.pendingHead < $.pendingQids.length) {
            qid = $.pendingQids[$.pendingHead];
            bytes32[] storage markets = $.qidMarkets[qid];
            if ($.resolveResumeIndex < markets.length) {
                return (qid, markets[$.resolveResumeIndex], true);
            }
            // Front qid exhausted — pop it (advance head) and reset the resume index.
            ++$.pendingHead;
            $.resolveResumeIndex = 0;
        }
        return (0, bytes32(0), false);
    }

    /// @dev Advance the front-qid resume cursor by one; pop the qid when exhausted.
    function _advanceCursor(OracleHubStorage storage $) internal {
        uint256 head = $.pendingHead;
        if (head >= $.pendingQids.length) return;
        uint256 next = $.resolveResumeIndex + 1;
        if (next >= $.qidMarkets[$.pendingQids[head]].length) {
            ++$.pendingHead;
            $.resolveResumeIndex = 0;
        } else {
            $.resolveResumeIndex = next;
        }
    }

    /// @dev Total markets still queued for resolution across the pending FIFO (from the
    ///      head cursor onward, accounting for the front qid's resume index).
    function _pendingResolves(OracleHubStorage storage $) internal view returns (uint256 remaining) {
        uint256 len = $.pendingQids.length;
        for (uint256 h = $.pendingHead; h < len; ++h) {
            uint256 n = $.qidMarkets[$.pendingQids[h]].length;
            uint256 from = h == $.pendingHead ? $.resolveResumeIndex : 0;
            if (n > from) remaining += n - from;
        }
    }

    /// @dev Arm a next-block `Schedule` continuation sub when markets remain, or tear the
    ///      live one down (and compact the FIFO) when the queue empties. Mirrors
    ///      `MarketCreator`'s one-sub continuation.
    function _armOrTearContinuation(OracleHubStorage storage $, uint256 remaining) internal {
        if (remaining != 0) {
            uint256 old = $.continuationSubId;
            if (old != 0) {
                // solhint-disable-next-line avoid-low-level-calls
                (bool ok,) =
                    address(REACTIVITY).call(abi.encodeCall(ISomniaReactivityPrecompile.unsubscribe, (old)));
                ok;
            }
            // `+ 2` (NOT `+ 1`): the precompile validates the fire time against
            // its own MILLISECOND clock and rejects anything with under ~1s of
            // lead, consuming ALL forwarded gas on the revert — `+ 1` yields
            // 1..1000ms of effective lead depending on where inside the second
            // this callback lands (the coin flip that stranded the testnet
            // MarketCreator's 8-series spill, 2026-07-22). `+ 2` guarantees
            // > 1s of lead at any intra-second position.
            // forge-lint: disable-next-line(block-timestamp)
            uint256 subId = _subscribeSchedule(block.timestamp + 2);
            $.continuationSubId = subId;
            emit DrainContinuation(subId, remaining);
            return;
        }
        // Queue empty — tear down the continuation and compact the drained FIFO.
        uint256 sub = $.continuationSubId;
        if (sub != 0) {
            $.continuationSubId = 0;
            // solhint-disable-next-line avoid-low-level-calls
            (bool ok,) =
                address(REACTIVITY).call(abi.encodeCall(ISomniaReactivityPrecompile.unsubscribe, (sub)));
            ok;
        }
        if ($.pendingQids.length != 0) {
            delete $.pendingQids;
            $.pendingHead = 0;
            $.resolveResumeIndex = 0;
        }
        emit DrainContinuation(0, 0);
    }

    // ---- Internal: subscription + refund + translation -----------------

    /// @dev Push-refund excess `scheduleQuestion` value to the caller.
    function _refundExcess(uint256 amount) internal {
        if (amount == 0) return;
        (bool ok,) = msg.sender.call{value: amount}("");
        if (!ok) revert RefundFailed();
    }

    /// @dev Subscribe to the oracle's `AnswerPosted` event (the persistent sub).
    function _subscribeAnswers() internal returns (uint256 subId) {
        OracleHubStorage storage $ = _getOracleHubStorage();
        if (address(REACTIVITY) == address(0)) revert NoPrecompile();

        ISomniaReactivityPrecompile.SubscriptionData memory sub = SomniaExtensions.eventSub(
            ANSWER_POSTED_TOPIC, ORACLE_CORE, address(this), $.priorityFeePerGas, $.maxFeePerGas, $.gasLimit
        );
        $.subscriptionId = REACTIVITY.subscribe(sub);
        return $.subscriptionId;
    }

    /// @dev Arm a one-shot `Schedule` continuation sub for `firesAtSec` (next block) to
    ///      resume the drain. Wildcards origin/caller (system-emitted Schedule events),
    ///      isGuaranteed=false (the precompile reverts guaranteed event subs).
    function _subscribeSchedule(uint256 firesAtSec) internal returns (uint256 subId) {
        OracleHubStorage storage $ = _getOracleHubStorage();
        ISomniaReactivityPrecompile.SubscriptionData memory sub = SomniaExtensions.scheduleSubAt(
            firesAtSec * 1000, address(this), $.priorityFeePerGas, $.maxFeePerGas, $.gasLimit
        );
        // The injected REACTIVITY immutable (not the canonical constant) so the
        // anvil LiveReactivityStub substitutes cleanly.
        sub.emitter = address(REACTIVITY);
        return REACTIVITY.subscribe(sub);
    }

    /// @dev UUPS upgrade gate. Only the owner may authorise an upgrade.
    function _authorizeUpgrade(address) internal override onlyOwner {}

    /// @dev The per-market resolution reserve: one worst-case resolve at the sub's fee
    ///      cap — the exact wei earmarked at `onBind` and released at resolution.
    function _resolveReserve(OracleHubStorage storage $) internal view returns (uint256) {
        return uint256($.perMarketResolveGas) * $.maxFeePerGas;
    }

    /// @dev Translate a raw oracle answer into the vector wire shape (§2B):
    ///      discrete winner → one-hot, void → uniform.
    function _translate(uint256 oracleQuestionId, uint8 outcomeIdx, bool voided)
        internal
        view
        returns (uint32 denominator, uint256[] memory nums)
    {
        denominator = PAYOUT_VECTOR_DENOMINATOR;
        uint256 slots = _slotCountOf(oracleQuestionId);
        if (voided) {
            nums = new uint256[](slots);
            uint256 share = uint256(denominator) / slots;
            for (uint256 i; i < slots; ++i) {
                nums[i] = share;
            }
            nums[0] += uint256(denominator) - share * slots;
        } else {
            uint256 len = outcomeIdx < slots ? slots : uint256(outcomeIdx) + 1;
            nums = new uint256[](len);
            nums[outcomeIdx] = denominator;
        }
    }

    /// @dev Slot count for translation, floored at 2 and non-reverting.
    function _slotCountOf(uint256 oracleQuestionId) internal view returns (uint256 slots) {
        slots = 2;
        try ORACLE.getOutcomeLabels(oracleQuestionId) returns (string[] memory labels) {
            if (labels.length > 2) slots = labels.length;
        } catch {
            // Keep the binary default.
        }
    }

    /// @dev Revert unless the oracle has finalised (resolved or voided) the question.
    function _requireFinal(uint256 oracleQuestionId) internal view {
        QuestionStatus st = ORACLE.getStatus(oracleQuestionId);
        if (st != QuestionStatus.Resolved && st != QuestionStatus.Voided) revert QuestionNotFinal();
    }

    /// @dev Canonical dedup key (§7.3). Template-scoped: ONLY definitions whose every
    ///      source is a JSON source participate; anything else returns zero.
    function _dedupKey(QuestionDefinition calldata def) internal pure returns (bytes32 key) {
        uint256 n = def.sources.length;
        if (n == 0) return bytes32(0);
        for (uint256 i; i < n; ++i) {
            if (def.sources[i].sourceType != QuestionSourceType.JSON) return bytes32(0);
        }
        // forge-lint: disable-next-line(asm-keccak256)
        return keccak256(
            abi.encode(
                keccak256(abi.encode(def.sources)),
                def.resolutionTime,
                keccak256(abi.encode(def.validAnswers)),
                def.minAgreement,
                def.subcommitteeSize,
                def.subcommitteeThreshold,
                def.validAnswers.numericDecimals
            )
        );
    }

    /// @dev ERC-7201 namespaced storage accessor.
    function _getOracleHubStorage() private pure returns (OracleHubStorage storage $) {
        assembly {
            $.slot := _OracleHubStorageLocation
        }
    }
}

Other files

  • lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    
    pragma solidity ^0.8.20;
    
    import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is set to the address provided by the deployer. This can
     * later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
        /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
        struct OwnableStorage {
            address _owner;
        }
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
    
        function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
            assembly {
                $.slot := OwnableStorageLocation
            }
        }
    
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
    
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        function __Ownable_init(address initialOwner) internal onlyInitializing {
            __Ownable_init_unchained(initialOwner);
        }
    
        function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
    
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
    
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            OwnableStorage storage $ = _getOwnableStorage();
            return $._owner;
        }
    
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
    
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(newOwner);
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            OwnableStorage storage $ = _getOwnableStorage();
            address oldOwner = $._owner;
            $._owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    
  • lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.22;
    
    import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
    
  • lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    
    pragma solidity ^0.8.20;
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract ContextUpgradeable is Initializable {
        function __Context_init() internal onlyInitializing {
        }
    
        function __Context_init_unchained() internal onlyInitializing {
        }
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    
  • lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
    
    pragma solidity ^0.8.20;
    
    import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    /**
     * @dev Contract module which provides access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * This extension of the {Ownable} contract includes a two-step mechanism to transfer
     * ownership, where the new owner must call {acceptOwnership} in order to replace the
     * old one. This can help prevent common mistakes, such as transfers of ownership to
     * incorrect accounts, or to contracts that are unable to interact with the
     * permission system.
     *
     * The initial owner is specified at deployment time in the constructor for `Ownable`. This
     * can later be changed with {transferOwnership} and {acceptOwnership}.
     *
     * This module is used through inheritance. It will make available all functions
     * from parent (Ownable).
     */
    abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
        /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
        struct Ownable2StepStorage {
            address _pendingOwner;
        }
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;
    
        function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
            assembly {
                $.slot := Ownable2StepStorageLocation
            }
        }
    
        event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
    
        function __Ownable2Step_init() internal onlyInitializing {
        }
    
        function __Ownable2Step_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev Returns the address of the pending owner.
         */
        function pendingOwner() public view virtual returns (address) {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            return $._pendingOwner;
        }
    
        /**
         * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
         * Can only be called by the current owner.
         *
         * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
         */
        function transferOwnership(address newOwner) public virtual override onlyOwner {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            $._pendingOwner = newOwner;
            emit OwnershipTransferStarted(owner(), newOwner);
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual override {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            delete $._pendingOwner;
            super._transferOwnership(newOwner);
        }
    
        /**
         * @dev The new owner accepts the ownership transfer.
         */
        function acceptOwnership() public virtual {
            address sender = _msgSender();
            if (pendingOwner() != sender) {
                revert OwnableUnauthorizedAccount(sender);
            }
            _transferOwnership(sender);
        }
    }
    
  • lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/ERC165.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
    
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
     * for the additional interface id that will be supported. For example:
     *
     * ```solidity
     * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
     *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
     * }
     * ```
     */
    abstract contract ERC165Upgradeable is Initializable, IERC165 {
        function __ERC165_init() internal onlyInitializing {
        }
    
        function __ERC165_init_unchained() internal onlyInitializing {
        }
        /// @inheritdoc IERC165
        function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
            return interfaceId == type(IERC165).interfaceId;
        }
    }
    
  • lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1967.sol)
    
    pragma solidity >=0.4.11;
    
    /**
     * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
     */
    interface IERC1967 {
        /**
         * @dev Emitted when the implementation is upgraded.
         */
        event Upgraded(address indexed implementation);
    
        /**
         * @dev Emitted when the admin account has changed.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
    
        /**
         * @dev Emitted when the beacon is changed.
         */
        event BeaconUpgraded(address indexed beacon);
    }
    
  • lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (interfaces/draft-IERC1822.sol)
    
    pragma solidity >=0.4.16;
    
    /**
     * @dev ERC-1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
     * proxy whose upgrades are fully controlled by the current implementation.
     */
    interface IERC1822Proxiable {
        /**
         * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
         * address.
         *
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
         * function revert if invoked through a proxy.
         */
        function proxiableUUID() external view returns (bytes32);
    }
    
  • lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (proxy/ERC1967/ERC1967Utils.sol)
    
    pragma solidity ^0.8.21;
    
    import {IBeacon} from "../beacon/IBeacon.sol";
    import {IERC1967} from "../../interfaces/IERC1967.sol";
    import {Address} from "../../utils/Address.sol";
    import {StorageSlot} from "../../utils/StorageSlot.sol";
    
    /**
     * @dev This library provides getters and event emitting update functions for
     * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots.
     */
    library ERC1967Utils {
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
        /**
         * @dev The `implementation` of the proxy is invalid.
         */
        error ERC1967InvalidImplementation(address implementation);
    
        /**
         * @dev The `admin` of the proxy is invalid.
         */
        error ERC1967InvalidAdmin(address admin);
    
        /**
         * @dev The `beacon` of the proxy is invalid.
         */
        error ERC1967InvalidBeacon(address beacon);
    
        /**
         * @dev An upgrade function sees `msg.value > 0` that may be lost.
         */
        error ERC1967NonPayable();
    
        /**
         * @dev Returns the current implementation address.
         */
        function getImplementation() internal view returns (address) {
            return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the ERC-1967 implementation slot.
         */
        function _setImplementation(address newImplementation) private {
            if (newImplementation.code.length == 0) {
                revert ERC1967InvalidImplementation(newImplementation);
            }
            StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
        }
    
        /**
         * @dev Performs implementation upgrade with additional setup call if data is nonempty.
         * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
         * to avoid stuck value in the contract.
         *
         * Emits an {IERC1967-Upgraded} event.
         */
        function upgradeToAndCall(address newImplementation, bytes memory data) internal {
            _setImplementation(newImplementation);
            emit IERC1967.Upgraded(newImplementation);
    
            if (data.length > 0) {
                Address.functionDelegateCall(newImplementation, data);
            } else {
                _checkNonPayable();
            }
        }
    
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
        /**
         * @dev Returns the current admin.
         *
         * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
         * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
         * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
         */
        function getAdmin() internal view returns (address) {
            return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
        }
    
        /**
         * @dev Stores a new address in the ERC-1967 admin slot.
         */
        function _setAdmin(address newAdmin) private {
            if (newAdmin == address(0)) {
                revert ERC1967InvalidAdmin(address(0));
            }
            StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
        }
    
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {IERC1967-AdminChanged} event.
         */
        function changeAdmin(address newAdmin) internal {
            emit IERC1967.AdminChanged(getAdmin(), newAdmin);
            _setAdmin(newAdmin);
        }
    
        /**
         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
         * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    
        /**
         * @dev Returns the current beacon.
         */
        function getBeacon() internal view returns (address) {
            return StorageSlot.getAddressSlot(BEACON_SLOT).value;
        }
    
        /**
         * @dev Stores a new beacon in the ERC-1967 beacon slot.
         */
        function _setBeacon(address newBeacon) private {
            if (newBeacon.code.length == 0) {
                revert ERC1967InvalidBeacon(newBeacon);
            }
    
            StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
    
            address beaconImplementation = IBeacon(newBeacon).implementation();
            if (beaconImplementation.code.length == 0) {
                revert ERC1967InvalidImplementation(beaconImplementation);
            }
        }
    
        /**
         * @dev Change the beacon and trigger a setup call if data is nonempty.
         * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
         * to avoid stuck value in the contract.
         *
         * Emits an {IERC1967-BeaconUpgraded} event.
         *
         * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
         * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
         * efficiency.
         */
        function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
            _setBeacon(newBeacon);
            emit IERC1967.BeaconUpgraded(newBeacon);
    
            if (data.length > 0) {
                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
            } else {
                _checkNonPayable();
            }
        }
    
        /**
         * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
         * if an upgrade doesn't perform an initialization call.
         */
        function _checkNonPayable() private {
            if (msg.value > 0) {
                revert ERC1967NonPayable();
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (proxy/beacon/IBeacon.sol)
    
    pragma solidity >=0.4.16;
    
    /**
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
     */
    interface IBeacon {
        /**
         * @dev Must return an address that can be used as a delegate call target.
         *
         * {UpgradeableBeacon} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }
    
  • lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
     * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
     * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
     * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
     *
     * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
     * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
     * case an upgrade adds a module that needs to be initialized.
     *
     * For example:
     *
     * [.hljs-theme-light.nopadding]
     * ```solidity
     * contract MyToken is ERC20Upgradeable {
     *     function initialize() initializer public {
     *         __ERC20_init("MyToken", "MTK");
     *     }
     * }
     *
     * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
     *     function initializeV2() reinitializer(2) public {
     *         __ERC20Permit_init("MyToken");
     *     }
     * }
     * ```
     *
     * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
     * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
     *
     * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
     * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
     *
     * [CAUTION]
     * ====
     * Avoid leaving a contract uninitialized.
     *
     * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
     * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
     * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
     *
     * [.hljs-theme-light.nopadding]
     * ```
     * /// @custom:oz-upgrades-unsafe-allow constructor
     * constructor() {
     *     _disableInitializers();
     * }
     * ```
     * ====
     */
    abstract contract Initializable {
        /**
         * @dev Storage of the initializable contract.
         *
         * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
         * when using with upgradeable contracts.
         *
         * @custom:storage-location erc7201:openzeppelin.storage.Initializable
         */
        struct InitializableStorage {
            /**
             * @dev Indicates that the contract has been initialized.
             */
            uint64 _initialized;
            /**
             * @dev Indicates that the contract is in the process of being initialized.
             */
            bool _initializing;
        }
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
    
        /**
         * @dev The contract is already initialized.
         */
        error InvalidInitialization();
    
        /**
         * @dev The contract is not initializing.
         */
        error NotInitializing();
    
        /**
         * @dev Triggered when the contract has been initialized or reinitialized.
         */
        event Initialized(uint64 version);
    
        /**
         * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
         * `onlyInitializing` functions can be used to initialize parent contracts.
         *
         * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
         * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
         * production.
         *
         * Emits an {Initialized} event.
         */
        modifier initializer() {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            // Cache values to avoid duplicated sloads
            bool isTopLevelCall = !$._initializing;
            uint64 initialized = $._initialized;
    
            // Allowed calls:
            // - initialSetup: the contract is not in the initializing state and no previous version was
            //                 initialized
            // - construction: the contract is initialized at version 1 (no reinitialization) and the
            //                 current contract is just being deployed
            bool initialSetup = initialized == 0 && isTopLevelCall;
            bool construction = initialized == 1 && address(this).code.length == 0;
    
            if (!initialSetup && !construction) {
                revert InvalidInitialization();
            }
            $._initialized = 1;
            if (isTopLevelCall) {
                $._initializing = true;
            }
            _;
            if (isTopLevelCall) {
                $._initializing = false;
                emit Initialized(1);
            }
        }
    
        /**
         * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
         * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
         * used to initialize parent contracts.
         *
         * A reinitializer may be used after the original initialization step. This is essential to configure modules that
         * are added through upgrades and that require initialization.
         *
         * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
         * cannot be nested. If one is invoked in the context of another, execution will revert.
         *
         * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
         * a contract, executing them in the right order is up to the developer or operator.
         *
         * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
         *
         * Emits an {Initialized} event.
         */
        modifier reinitializer(uint64 version) {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            if ($._initializing || $._initialized >= version) {
                revert InvalidInitialization();
            }
            $._initialized = version;
            $._initializing = true;
            _;
            $._initializing = false;
            emit Initialized(version);
        }
    
        /**
         * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
         * {initializer} and {reinitializer} modifiers, directly or indirectly.
         */
        modifier onlyInitializing() {
            _checkInitializing();
            _;
        }
    
        /**
         * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
         */
        function _checkInitializing() internal view virtual {
            if (!_isInitializing()) {
                revert NotInitializing();
            }
        }
    
        /**
         * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
         * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
         * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
         * through proxies.
         *
         * Emits an {Initialized} event the first time it is successfully executed.
         */
        function _disableInitializers() internal virtual {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
    
            if ($._initializing) {
                revert InvalidInitialization();
            }
            if ($._initialized != type(uint64).max) {
                $._initialized = type(uint64).max;
                emit Initialized(type(uint64).max);
            }
        }
    
        /**
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
         */
        function _getInitializedVersion() internal view returns (uint64) {
            return _getInitializableStorage()._initialized;
        }
    
        /**
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
         */
        function _isInitializing() internal view returns (bool) {
            return _getInitializableStorage()._initializing;
        }
    
        /**
         * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
         *
         * NOTE: Consider following the ERC-7201 formula to derive storage locations.
         */
        function _initializableStorageSlot() internal pure virtual returns (bytes32) {
            return INITIALIZABLE_STORAGE;
        }
    
        /**
         * @dev Returns a pointer to the storage namespace.
         */
        // solhint-disable-next-line var-name-mixedcase
        function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
            bytes32 slot = _initializableStorageSlot();
            assembly {
                $.slot := slot
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.5.0) (proxy/utils/UUPSUpgradeable.sol)
    
    pragma solidity ^0.8.22;
    
    import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol";
    import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
    
    /**
     * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
     * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
     *
     * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
     * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
     * `UUPSUpgradeable` with a custom implementation of upgrades.
     *
     * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
     *
     * @custom:stateless
     */
    abstract contract UUPSUpgradeable is IERC1822Proxiable {
        /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
        address private immutable __self = address(this);
    
        /**
         * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
         * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
         * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
         * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
         * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
         * during an upgrade.
         */
        string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
    
        /**
         * @dev The call is from an unauthorized context.
         */
        error UUPSUnauthorizedCallContext();
    
        /**
         * @dev The storage `slot` is unsupported as a UUID.
         */
        error UUPSUnsupportedProxiableUUID(bytes32 slot);
    
        /**
         * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
         * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
         * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
         * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
         * fail.
         */
        modifier onlyProxy() {
            _checkProxy();
            _;
        }
    
        /**
         * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
         * callable on the implementing contract but not through proxies.
         */
        modifier notDelegated() {
            _checkNotDelegated();
            _;
        }
    
        /**
         * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
         * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
         *
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
         * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
         */
        function proxiableUUID() external view notDelegated returns (bytes32) {
            return ERC1967Utils.IMPLEMENTATION_SLOT;
        }
    
        /**
         * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
         * encoded in `data`.
         *
         * Calls {_authorizeUpgrade}.
         *
         * Emits an {Upgraded} event.
         *
         * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
         */
        function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
            _authorizeUpgrade(newImplementation);
            _upgradeToAndCallUUPS(newImplementation, data);
        }
    
        /**
         * @dev Reverts if the execution is not performed via delegatecall or the execution
         * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
         */
        function _checkProxy() internal view virtual {
            if (
                address(this) == __self || // Must be called through delegatecall
                ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
            ) {
                revert UUPSUnauthorizedCallContext();
            }
        }
    
        /**
         * @dev Reverts if the execution is performed via delegatecall.
         * See {notDelegated}.
         */
        function _checkNotDelegated() internal view virtual {
            if (address(this) != __self) {
                // Must not be called through delegatecall
                revert UUPSUnauthorizedCallContext();
            }
        }
    
        /**
         * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
         * {upgradeToAndCall}.
         *
         * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
         *
         * ```solidity
         * function _authorizeUpgrade(address) internal onlyOwner {}
         * ```
         */
        function _authorizeUpgrade(address newImplementation) internal virtual;
    
        /**
         * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
         *
         * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
         * is expected to be the implementation slot in ERC-1967.
         *
         * Emits an {IERC1967-Upgraded} event.
         */
        function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
            try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
                    revert UUPSUnsupportedProxiableUUID(slot);
                }
                ERC1967Utils.upgradeToAndCall(newImplementation, data);
            } catch {
                // The implementation is not UUPS
                revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/Address.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.5.0) (utils/Address.sol)
    
    pragma solidity ^0.8.20;
    
    import {Errors} from "./Errors.sol";
    import {LowLevelCall} from "./LowLevelCall.sol";
    
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev There's no code at `target` (it is not a contract).
         */
        error AddressEmptyCode(address target);
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            if (address(this).balance < amount) {
                revert Errors.InsufficientBalance(address(this).balance, amount);
            }
            if (LowLevelCall.callNoReturn(recipient, amount, "")) {
                // call successful, nothing to do
                return;
            } else if (LowLevelCall.returnDataSize() > 0) {
                LowLevelCall.bubbleRevert();
            } else {
                revert Errors.FailedCall();
            }
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason or custom error, it is bubbled
         * up by this function (like regular Solidity function calls). However, if
         * the call reverted with no returned reason, this function reverts with a
         * {Errors.FailedCall} error.
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            if (address(this).balance < value) {
                revert Errors.InsufficientBalance(address(this).balance, value);
            }
            bool success = LowLevelCall.callNoReturn(target, value, data);
            if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
                return LowLevelCall.returnData();
            } else if (success) {
                revert AddressEmptyCode(target);
            } else if (LowLevelCall.returnDataSize() > 0) {
                LowLevelCall.bubbleRevert();
            } else {
                revert Errors.FailedCall();
            }
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            bool success = LowLevelCall.staticcallNoReturn(target, data);
            if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
                return LowLevelCall.returnData();
            } else if (success) {
                revert AddressEmptyCode(target);
            } else if (LowLevelCall.returnDataSize() > 0) {
                LowLevelCall.bubbleRevert();
            } else {
                revert Errors.FailedCall();
            }
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            bool success = LowLevelCall.delegatecallNoReturn(target, data);
            if (success && (LowLevelCall.returnDataSize() > 0 || target.code.length > 0)) {
                return LowLevelCall.returnData();
            } else if (success) {
                revert AddressEmptyCode(target);
            } else if (LowLevelCall.returnDataSize() > 0) {
                LowLevelCall.bubbleRevert();
            } else {
                revert Errors.FailedCall();
            }
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
         * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
         * of an unsuccessful call.
         *
         * NOTE: This function is DEPRECATED and may be removed in the next major release.
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata
        ) internal view returns (bytes memory) {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (success && (returndata.length > 0 || target.code.length > 0)) {
                return returndata;
            } else if (success) {
                revert AddressEmptyCode(target);
            } else if (returndata.length > 0) {
                LowLevelCall.bubbleRevert(returndata);
            } else {
                revert Errors.FailedCall();
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
         * revert reason or with a default {Errors.FailedCall} error.
         */
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else if (returndata.length > 0) {
                LowLevelCall.bubbleRevert(returndata);
            } else {
                revert Errors.FailedCall();
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/Errors.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Collection of common custom errors used in multiple contracts
     *
     * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
     * It is recommended to avoid relying on the error API for critical functionality.
     *
     * _Available since v5.1._
     */
    library Errors {
        /**
         * @dev The ETH balance of the account is not enough to perform the operation.
         */
        error InsufficientBalance(uint256 balance, uint256 needed);
    
        /**
         * @dev A call to an address target failed. The target may have reverted.
         */
        error FailedCall();
    
        /**
         * @dev The deployment failed.
         */
        error FailedDeployment();
    
        /**
         * @dev A necessary precompile is missing.
         */
        error MissingPrecompile(address);
    }
    
  • lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.5.0) (utils/LowLevelCall.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Library of low level call functions that implement different calling strategies to deal with the return data.
     *
     * WARNING: Using this library requires an advanced understanding of Solidity and how the EVM works. It is recommended
     * to use the {Address} library instead.
     */
    library LowLevelCall {
        /// @dev Performs a Solidity function call using a low level `call` and ignoring the return data.
        function callNoReturn(address target, bytes memory data) internal returns (bool success) {
            return callNoReturn(target, 0, data);
        }
    
        /// @dev Same as {callNoReturn}, but allows to specify the value to be sent in the call.
        function callNoReturn(address target, uint256 value, bytes memory data) internal returns (bool success) {
            assembly ("memory-safe") {
                success := call(gas(), target, value, add(data, 0x20), mload(data), 0x00, 0x00)
            }
        }
    
        /// @dev Performs a Solidity function call using a low level `call` and returns the first 64 bytes of the result
        /// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
        ///
        /// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
        /// and this function doesn't zero it out.
        function callReturn64Bytes(
            address target,
            bytes memory data
        ) internal returns (bool success, bytes32 result1, bytes32 result2) {
            return callReturn64Bytes(target, 0, data);
        }
    
        /// @dev Same as {callReturnBytes32Pair}, but allows to specify the value to be sent in the call.
        function callReturn64Bytes(
            address target,
            uint256 value,
            bytes memory data
        ) internal returns (bool success, bytes32 result1, bytes32 result2) {
            assembly ("memory-safe") {
                success := call(gas(), target, value, add(data, 0x20), mload(data), 0x00, 0x40)
                result1 := mload(0x00)
                result2 := mload(0x20)
            }
        }
    
        /// @dev Performs a Solidity function call using a low level `staticcall` and ignoring the return data.
        function staticcallNoReturn(address target, bytes memory data) internal view returns (bool success) {
            assembly ("memory-safe") {
                success := staticcall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x00)
            }
        }
    
        /// @dev Performs a Solidity function call using a low level `staticcall` and returns the first 64 bytes of the result
        /// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
        ///
        /// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
        /// and this function doesn't zero it out.
        function staticcallReturn64Bytes(
            address target,
            bytes memory data
        ) internal view returns (bool success, bytes32 result1, bytes32 result2) {
            assembly ("memory-safe") {
                success := staticcall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x40)
                result1 := mload(0x00)
                result2 := mload(0x20)
            }
        }
    
        /// @dev Performs a Solidity function call using a low level `delegatecall` and ignoring the return data.
        function delegatecallNoReturn(address target, bytes memory data) internal returns (bool success) {
            assembly ("memory-safe") {
                success := delegatecall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x00)
            }
        }
    
        /// @dev Performs a Solidity function call using a low level `delegatecall` and returns the first 64 bytes of the result
        /// in the scratch space of memory. Useful for functions that return a tuple of single-word values.
        ///
        /// WARNING: Do not assume that the results are zero if `success` is false. Memory can be already allocated
        /// and this function doesn't zero it out.
        function delegatecallReturn64Bytes(
            address target,
            bytes memory data
        ) internal returns (bool success, bytes32 result1, bytes32 result2) {
            assembly ("memory-safe") {
                success := delegatecall(gas(), target, add(data, 0x20), mload(data), 0x00, 0x40)
                result1 := mload(0x00)
                result2 := mload(0x20)
            }
        }
    
        /// @dev Returns the size of the return data buffer.
        function returnDataSize() internal pure returns (uint256 size) {
            assembly ("memory-safe") {
                size := returndatasize()
            }
        }
    
        /// @dev Returns a buffer containing the return data from the last call.
        function returnData() internal pure returns (bytes memory result) {
            assembly ("memory-safe") {
                result := mload(0x40)
                mstore(result, returndatasize())
                returndatacopy(add(result, 0x20), 0x00, returndatasize())
                mstore(0x40, add(result, add(0x20, returndatasize())))
            }
        }
    
        /// @dev Revert with the return data from the last call.
        function bubbleRevert() internal pure {
            assembly ("memory-safe") {
                let fmp := mload(0x40)
                returndatacopy(fmp, 0x00, returndatasize())
                revert(fmp, returndatasize())
            }
        }
    
        function bubbleRevert(bytes memory returndata) internal pure {
            assembly ("memory-safe") {
                revert(add(returndata, 0x20), mload(returndata))
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.5.0) (utils/ReentrancyGuardTransient.sol)
    
    pragma solidity ^0.8.24;
    
    import {TransientSlot} from "./TransientSlot.sol";
    
    /**
     * @dev Variant of {ReentrancyGuard} that uses transient storage.
     *
     * NOTE: This variant only works on networks where EIP-1153 is available.
     *
     * _Available since v5.1._
     *
     * @custom:stateless
     */
    abstract contract ReentrancyGuardTransient {
        using TransientSlot for *;
    
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant REENTRANCY_GUARD_STORAGE =
            0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
    
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
    
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
    
        /**
         * @dev A `view` only version of {nonReentrant}. Use to block view functions
         * from being called, preventing reading from inconsistent contract state.
         *
         * CAUTION: This is a "view" modifier and does not change the reentrancy
         * status. Use it only on view functions. For payable or non-payable functions,
         * use the standard {nonReentrant} modifier instead.
         */
        modifier nonReentrantView() {
            _nonReentrantBeforeView();
            _;
        }
    
        function _nonReentrantBeforeView() private view {
            if (_reentrancyGuardEntered()) {
                revert ReentrancyGuardReentrantCall();
            }
        }
    
        function _nonReentrantBefore() private {
            // On the first call to nonReentrant, REENTRANCY_GUARD_STORAGE.asBoolean().tload() will be false
            _nonReentrantBeforeView();
    
            // Any calls to nonReentrant after this point will fail
            _reentrancyGuardStorageSlot().asBoolean().tstore(true);
        }
    
        function _nonReentrantAfter() private {
            _reentrancyGuardStorageSlot().asBoolean().tstore(false);
        }
    
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            return _reentrancyGuardStorageSlot().asBoolean().tload();
        }
    
        function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
            return REENTRANCY_GUARD_STORAGE;
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Library for reading and writing primitive types to specific storage slots.
     *
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
     *
     * Example usage to set ERC-1967 implementation slot:
     * ```solidity
     * contract ERC1967 {
     *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
     *
     *     function _getImplementation() internal view returns (address) {
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
     *     }
     *
     *     function _setImplementation(address newImplementation) internal {
     *         require(newImplementation.code.length > 0);
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     *
     * TIP: Consider using this library along with {SlotDerivation}.
     */
    library StorageSlot {
        struct AddressSlot {
            address value;
        }
    
        struct BooleanSlot {
            bool value;
        }
    
        struct Bytes32Slot {
            bytes32 value;
        }
    
        struct Uint256Slot {
            uint256 value;
        }
    
        struct Int256Slot {
            int256 value;
        }
    
        struct StringSlot {
            string value;
        }
    
        struct BytesSlot {
            bytes value;
        }
    
        /**
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
         */
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
         */
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
         */
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
         */
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns a `Int256Slot` with member `value` located at `slot`.
         */
        function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns a `StringSlot` with member `value` located at `slot`.
         */
        function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
         */
        function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
            assembly ("memory-safe") {
                r.slot := store.slot
            }
        }
    
        /**
         * @dev Returns a `BytesSlot` with member `value` located at `slot`.
         */
        function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
            assembly ("memory-safe") {
                r.slot := slot
            }
        }
    
        /**
         * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
         */
        function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
            assembly ("memory-safe") {
                r.slot := store.slot
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.3.0) (utils/TransientSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/TransientSlot.js.
    
    pragma solidity ^0.8.24;
    
    /**
     * @dev Library for reading and writing value-types to specific transient storage slots.
     *
     * Transient slots are often used to store temporary values that are removed after the current transaction.
     * This library helps with reading and writing to such slots without the need for inline assembly.
     *
     *  * Example reading and writing values using transient storage:
     * ```solidity
     * contract Lock {
     *     using TransientSlot for *;
     *
     *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
     *     bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
     *
     *     modifier locked() {
     *         require(!_LOCK_SLOT.asBoolean().tload());
     *
     *         _LOCK_SLOT.asBoolean().tstore(true);
     *         _;
     *         _LOCK_SLOT.asBoolean().tstore(false);
     *     }
     * }
     * ```
     *
     * TIP: Consider using this library along with {SlotDerivation}.
     */
    library TransientSlot {
        /**
         * @dev UDVT that represents a slot holding an address.
         */
        type AddressSlot is bytes32;
    
        /**
         * @dev Cast an arbitrary slot to a AddressSlot.
         */
        function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
            return AddressSlot.wrap(slot);
        }
    
        /**
         * @dev UDVT that represents a slot holding a bool.
         */
        type BooleanSlot is bytes32;
    
        /**
         * @dev Cast an arbitrary slot to a BooleanSlot.
         */
        function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) {
            return BooleanSlot.wrap(slot);
        }
    
        /**
         * @dev UDVT that represents a slot holding a bytes32.
         */
        type Bytes32Slot is bytes32;
    
        /**
         * @dev Cast an arbitrary slot to a Bytes32Slot.
         */
        function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) {
            return Bytes32Slot.wrap(slot);
        }
    
        /**
         * @dev UDVT that represents a slot holding a uint256.
         */
        type Uint256Slot is bytes32;
    
        /**
         * @dev Cast an arbitrary slot to a Uint256Slot.
         */
        function asUint256(bytes32 slot) internal pure returns (Uint256Slot) {
            return Uint256Slot.wrap(slot);
        }
    
        /**
         * @dev UDVT that represents a slot holding a int256.
         */
        type Int256Slot is bytes32;
    
        /**
         * @dev Cast an arbitrary slot to a Int256Slot.
         */
        function asInt256(bytes32 slot) internal pure returns (Int256Slot) {
            return Int256Slot.wrap(slot);
        }
    
        /**
         * @dev Load the value held at location `slot` in transient storage.
         */
        function tload(AddressSlot slot) internal view returns (address value) {
            assembly ("memory-safe") {
                value := tload(slot)
            }
        }
    
        /**
         * @dev Store `value` at location `slot` in transient storage.
         */
        function tstore(AddressSlot slot, address value) internal {
            assembly ("memory-safe") {
                tstore(slot, value)
            }
        }
    
        /**
         * @dev Load the value held at location `slot` in transient storage.
         */
        function tload(BooleanSlot slot) internal view returns (bool value) {
            assembly ("memory-safe") {
                value := tload(slot)
            }
        }
    
        /**
         * @dev Store `value` at location `slot` in transient storage.
         */
        function tstore(BooleanSlot slot, bool value) internal {
            assembly ("memory-safe") {
                tstore(slot, value)
            }
        }
    
        /**
         * @dev Load the value held at location `slot` in transient storage.
         */
        function tload(Bytes32Slot slot) internal view returns (bytes32 value) {
            assembly ("memory-safe") {
                value := tload(slot)
            }
        }
    
        /**
         * @dev Store `value` at location `slot` in transient storage.
         */
        function tstore(Bytes32Slot slot, bytes32 value) internal {
            assembly ("memory-safe") {
                tstore(slot, value)
            }
        }
    
        /**
         * @dev Load the value held at location `slot` in transient storage.
         */
        function tload(Uint256Slot slot) internal view returns (uint256 value) {
            assembly ("memory-safe") {
                value := tload(slot)
            }
        }
    
        /**
         * @dev Store `value` at location `slot` in transient storage.
         */
        function tstore(Uint256Slot slot, uint256 value) internal {
            assembly ("memory-safe") {
                tstore(slot, value)
            }
        }
    
        /**
         * @dev Load the value held at location `slot` in transient storage.
         */
        function tload(Int256Slot slot) internal view returns (int256 value) {
            assembly ("memory-safe") {
                value := tload(slot)
            }
        }
    
        /**
         * @dev Store `value` at location `slot` in transient storage.
         */
        function tstore(Int256Slot slot, int256 value) internal {
            assembly ("memory-safe") {
                tstore(slot, value)
            }
        }
    }
    
  • lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
    
    pragma solidity >=0.4.16;
    
    /**
     * @dev Interface of the ERC-165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[ERC].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
        /**
         * @dev Returns true if this contract implements the interface defined by
         * `interfaceId`. See the corresponding
         * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
         * to learn more about how these ids are created.
         *
         * This function call must use less than 30 000 gas.
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    
  • src/MarketsCore.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    import {IMarketsModule} from "./interfaces/IMarketsModule.sol";
    import {IVenuePolicy} from "./interfaces/IVenuePolicy.sol";
    import {
        Ownable2StepUpgradeable
    } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
    import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    
    /// @title MarketsCore
    /// @notice Permissionless registry of operators (brand identities — frontends,
    ///         aggregators, or any counterparty surfacing the headless protocol)
    ///         and the typed venues they run. Fee params are type-specific opaque
    ///         bytes (not hardwired to any one market shape), decoded by the bound
    ///         module at market creation.
    ///
    ///         The model has two levels:
    ///
    ///         - **Operator** — the identity. Holds the owner key, the default
    ///           fee recipient, an operator-wide `IVenuePolicy`, and the enabled
    ///           flag. `operatorId 0` is reserved ("no operator").
    ///         - **Venue** — a scoped trading configuration under an operator,
    ///           bound to exactly ONE market type at creation (immutable
    ///           thereafter). Holds the type-specific fee params as opaque
    ///           `bytes` (only the market type's bound module knows the shape,
    ///           does the decode, and enforces its own protocol fee cap — new
    ///           market types need ZERO registry changes), an optional EIP-712
    ///           signer (non-zero ⇒ creation requires a venue signature), an
    ///           optional policy, and a creation enable flag. `venueId` is a
    ///           contract-generated opaque `bytes32` (keccak of operatorId + a
    ///           per-operator sequence); callers never choose it.
    ///
    ///         Module binding lives HERE, next to the operator/venue state it
    ///         gates: governance binds the ONE canonical module per market type via
    ///         `registerModule` (the `moduleOf` table); `venueForCreate` accepts a
    ///         market-creation call only from the bound module, so a market's
    ///         attribution to an operator/venue is a chain fact, not an off-chain
    ///         convention. Users reach a module's typed creation entry point
    ///         directly — this registry is consulted BY the module, never a
    ///         dispatch surface itself. Adding a market type = deploy a module
    ///         implementing `IMarketsModule` + `registerModule(marketType, module)`,
    ///         zero changes here beyond that one call.
    ///
    ///         Fee semantics for BINARY_V1 venues: origin fees (the venue that
    ///         creates a market) are decoded by the binary module at market creation
    ///         and frozen into the BinaryPool's init parameters — the pool has no
    ///         post-init fee setter, so snapshot-at-creation holds by construction.
    ///         Routing fees (the venue a trade routes through) ride the pool's
    ///         per-order builder rail, where the pool enforces the venue-advertised
    ///         ceiling per order and each user opts in via the pool's `approveBuilder`.
    ///
    ///         Disable semantics: `enabled` (operator) / `creationEnabled` (venue)
    ///         gate only NEW market origination. Existing markets keep their frozen
    ///         pool params and stay fully operable — users can always exit positions.
    contract MarketsCore is UUPSUpgradeable, Ownable2StepUpgradeable {
        /*//////////////////////////////////////////////////////////////
                                 ENUMS / STRUCTS
        //////////////////////////////////////////////////////////////*/
    
        /// @notice On-chain operator record — the brand identity that owns venues.
        /// @param owner The address authorised to update the operator and manage its venues.
        /// @param feeRecipient Default recipient for all this operator's venues; a venue
        ///        can override it via `VenueConfig.feeRecipientOverride`. Must be non-zero
        ///        while the operator is enabled (a zero recipient would burn fees).
        /// @param enabled Brand-wide creation switch: flip off to stop NEW market
        ///        origination under ALL of this operator's venues in one call
        ///        (`venueForCreate` reverts `OperatorDisabled`). Coarser than the
        ///        per-venue `creationEnabled`; existing markets are unaffected.
        /// @param policy Optional operator-wide IVenuePolicy consulted in ADDITION to the
        ///        per-venue policy (AND-composition) — the brand-wide gate for rules
        ///        (KYC, geo) that apply across every venue; address(0) = no operator-wide gate.
        /// @param context Opaque, operator-supplied metadata bytes. The registry attaches
        ///        NO semantics and never decodes them — purely a place to attach off-chain
        ///        data (indexed from the events). Size-capped by `_MAX_CONTEXT_BYTES`.
        struct Operator {
            address owner;
            address feeRecipient;
            bool enabled;
            IVenuePolicy policy;
            bytes context;
        }
    
        /// @notice Mutable venue configuration. Everything a venue owner can change after
        ///         creation — deliberately EXCLUDES `marketType`, which is set once in
        ///         `createVenue` and immutable (mutating it would orphan existing markets
        ///         whose pool params were decoded under the old type).
        /// @param feeParams Type-specific fee parameters, abi-encoded. The registry never
        ///        decodes these — the market type's bound module does (e.g. the binary
        ///        module decodes `BinaryVenueParams` at market creation and enforces its
        ///        own protocol fee cap, in its own units, on the decoded rates). Malformed
        ///        bytes make market creation revert at decode — no funds at risk, and
        ///        existing markets are untouched because their params are already frozen
        ///        in the engine.
        /// @param feeRecipientOverride Per-venue fee recipient; address(0) falls back to
        ///        the operator's `feeRecipient` at resolution time.
        /// @param policy Optional per-venue IVenuePolicy. One policy contract serves every
        ///        decision point the interface exposes (create today; trade/route when a
        ///        market type gains a registry-aware trade path). address(0) = no
        ///        per-venue gate; the operator-wide policy still composes (AND).
        ///        Creation additionally requires SOME create-side policy (operator-wide or
        ///        per-venue) so openness is an explicit choice — point it at `OpenPolicy`.
        /// @param signer Per-venue EIP-712 signer. A non-zero signer IS the signal that
        ///        market creation under this venue requires a venue-signed authorization
        ///        (no separate flag — set the signer to require sigs, zero it to open the
        ///        venue). Venues under one operator can use independent, independently
        ///        rotated keys.
        /// @param creationEnabled Whether this venue may currently back NEW markets
        ///        (ENFORCED — `venueForCreate` reverts when false). Existing markets are
        ///        unaffected; this only gates fresh origination.
        /// @param context Opaque, operator-supplied metadata bytes. The registry attaches
        ///        NO semantics and never decodes them — purely a place to attach off-chain
        ///        data (indexed from the events). Size-capped by `_MAX_CONTEXT_BYTES`.
        struct VenueConfig {
            bytes feeParams;
            address feeRecipientOverride;
            IVenuePolicy policy;
            address signer;
            bool creationEnabled;
            bytes context;
        }
    
        /// @notice Full venue record: the immutable market type plus the mutable config.
        /// @param marketType Market-type id (see `MarketTypeIds`); set once at
        ///        `createVenue`, immutable, and doubles as the existence sentinel
        ///        (bytes4(0) = venue does not exist — createVenue rejects a zero type).
        /// @param config Mutable configuration; see `VenueConfig`.
        struct Venue {
            bytes4 marketType;
            VenueConfig config;
        }
    
        /*//////////////////////////////////////////////////////////////
                                Contract storage
        //////////////////////////////////////////////////////////////*/
    
        /// @custom:storage-location erc7201:prophecy.storage.MarketsCore
        /// @dev Storage layout is append-only — see the `// ---- Append-only fields below
        ///      this line ----` marker inside the struct. Any new field MUST be appended
        ///      at the end so the ERC-7201 namespaced base location plus existing slot
        ///      indices remain stable across upgrades.
        /// @param nextOperatorId Auto-incrementing id assigned on the next
        ///        `registerOperator` call. Starts at 1 so 0 stays reserved.
        /// @param _operators operatorId → Operator record.
        /// @param pendingOwner Staged new owner for a pending two-step operator ownership
        ///        transfer. Zero means no transfer is in progress.
        /// @param _venues operatorId → venueId → Venue record.
        /// @param _venueIdsOf operatorId → list of venue ids ever created, for on-chain
        ///        enumeration. Venue creation is a low-frequency operator action, so the
        ///        array stays small; events + indexer remain the canonical enumeration.
        /// @param moduleOf marketType → the ONE module governance has bound for the
        ///        type. The gate `venueForCreate` consults: only the bound module
        ///        may consume a venue of that type.
        struct MarketsCoreStorage {
            uint32 nextOperatorId;
            mapping(uint32 operatorId => Operator) _operators;
            mapping(uint32 operatorId => address) pendingOwner;
            mapping(uint32 operatorId => mapping(bytes32 venueId => Venue)) _venues;
            mapping(uint32 operatorId => bytes32[]) _venueIdsOf;
            mapping(bytes4 marketType => address module) moduleOf;
            // ---- Append-only fields below this line ----
            // Global sequence for contract-generated venue ids (keccak of
            // (operatorId, seq)); starts effectively at 1 on first createVenue.
            uint256 nextVenueSeq;
        }
    
        /// @notice Sanity ceiling on `feeParams` byte length.
        /// @dev Not a security boundary (operators pay their own storage) — rejects
        ///      obvious garbage before it is committed. A version-tagged
        ///      `BinaryVenueParams` encodes to 192 bytes; 1 KiB leaves generous
        ///      headroom for richer future types.
        uint256 internal constant _MAX_FEE_PARAMS_BYTES = 1_024;
    
        /// @notice Sanity ceiling on the opaque `context` byte length attached to an
        ///         operator or venue.
        /// @dev Not a security boundary (the caller pays their own storage) — just rejects
        ///      unbounded blobs before they are committed. 4 KiB is generous for the
        ///      intended small metadata payloads.
        uint256 internal constant _MAX_CONTEXT_BYTES = 4_096;
    
        // keccak256(abi.encode(uint256(keccak256("prophecy.storage.MarketsCore")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant _MarketsCoreStorageLocation =
            0xc527b8bf189e1f392da00951e102e5509fb1fa85f292f86cd2df8a74ea459000;
    
        /*//////////////////////////////////////////////////////////////
                                      EVENTS
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Emitted when a new operator registers.
        /// @param operatorId Auto-incremented id assigned to the operator.
        /// @param owner Address that registered the operator and can manage it.
        /// @param feeRecipient Default fee recipient for the operator's venues.
        /// @param enabled True if the operator is live at registration.
        /// @param policy Operator-wide IVenuePolicy composed (AND) with each venue's own
        ///        policy — the operator-level gate is for brand-wide rules (KYC, geo) that
        ///        apply across every venue; per-venue policies still layer on top.
        /// @param context Opaque operator-supplied metadata (registry attaches no semantics).
        event OperatorRegistered(
            uint32 indexed operatorId,
            address indexed owner,
            address indexed feeRecipient,
            bool enabled,
            address policy,
            bytes context
        );
    
        /// @notice Emitted when the operator owner updates the operator's mutable fields.
        /// @param operatorId Target operator.
        /// @param feeRecipient New default fee recipient.
        /// @param enabled New enabled flag.
        /// @param policy New operator-wide policy; zero for none.
        /// @param context New opaque operator-supplied metadata (replaces the prior value).
        event OperatorUpdated(
            uint32 indexed operatorId,
            address indexed feeRecipient,
            bool indexed enabled,
            address policy,
            bytes context
        );
    
        /// @notice Emitted when the operator owner toggles the `enabled` flag standalone.
        /// @param operatorId Target operator.
        /// @param enabled New enabled state.
        event OperatorEnabledChanged(uint32 indexed operatorId, bool indexed enabled);
    
        /// @notice Emitted when an operator owner stages a two-step ownership transfer.
        /// @param operatorId Target operator.
        /// @param currentOwner Owner staging the transfer.
        /// @param pendingOwner Address staged as the incoming owner; still needs to accept.
        event OperatorOwnershipTransferStarted(
            uint32 indexed operatorId, address indexed currentOwner, address indexed pendingOwner
        );
    
        /// @notice Emitted when a pending owner accepts an operator ownership transfer.
        /// @param operatorId Target operator.
        /// @param oldOwner Previous owner.
        /// @param newOwner Newly installed owner (= caller of `acceptOperatorOwnership`).
        event OperatorOwnershipTransferred(
            uint32 indexed operatorId, address indexed oldOwner, address indexed newOwner
        );
    
        /// @notice Emitted when a pending operator ownership transfer is cancelled,
        ///         either by the current owner re-staging themself as `newOwner`
        ///         or by an explicit clear.
        /// @param operatorId Target operator.
        /// @param cancelledPendingOwner Address that had been staged but never accepted.
        event OperatorOwnershipTransferCancelled(
            uint32 indexed operatorId, address indexed cancelledPendingOwner
        );
    
        /// @notice Emitted when a venue is created under an operator. `marketType` is
        ///         immutable from this point on.
        /// @param operatorId Parent operator.
        /// @param venueId Contract-generated opaque `bytes32` venue id (keccak of operatorId + sequence).
        /// @param marketType Market-type id the venue is bound to, forever.
        /// @param feeParams Type-specific fee parameters (opaque to the registry).
        /// @param feeRecipientOverride Per-venue recipient; zero falls back to the operator's.
        /// @param policy Per-venue policy; zero for none.
        /// @param signer Per-venue EIP-712 signer; non-zero ⇒ creation requires a venue sig.
        /// @param creationEnabled Origin-side enable flag.
        /// @param context Opaque venue-supplied metadata (registry attaches no semantics).
        event VenueCreated(
            uint32 indexed operatorId,
            bytes32 indexed venueId,
            bytes4 indexed marketType,
            bytes feeParams,
            address feeRecipientOverride,
            address policy,
            address signer,
            bool creationEnabled,
            bytes context
        );
    
        /// @notice Emitted when the operator owner updates a venue's mutable config.
        ///         Field semantics match `VenueCreated`; `marketType` cannot change.
        /// @param operatorId Parent operator.
        /// @param venueId Target venue.
        /// @param feeParams New type-specific fee parameters.
        /// @param feeRecipientOverride New per-venue recipient override.
        /// @param policy New per-venue policy.
        /// @param signer New per-venue EIP-712 signer.
        /// @param creationEnabled New origin-side enable flag.
        /// @param context New opaque venue-supplied metadata (replaces the prior value).
        event VenueUpdated(
            uint32 indexed operatorId,
            bytes32 indexed venueId,
            bytes feeParams,
            address feeRecipientOverride,
            address policy,
            address signer,
            bool creationEnabled,
            bytes context
        );
    
        /// @notice Emitted when the operator owner toggles a venue's creation flag standalone.
        /// @param operatorId Parent operator.
        /// @param venueId Target venue.
        /// @param creationEnabled New origin-side enable flag.
        event VenueEnabledChanged(uint32 indexed operatorId, bytes32 indexed venueId, bool creationEnabled);
    
        /// @notice Emitted when governance binds (or rebinds) the canonical module
        ///         for a market type.
        /// @param marketType Market-type id.
        /// @param module The module now authorised to originate markets of this type.
        event ModuleRegistered(bytes4 indexed marketType, address indexed module);
    
        /// @notice Emitted when governance retires a market type: the binding is
        ///         cleared, so no NEW markets of this type can be originated —
        ///         while every existing market keeps running untouched (their
        ///         lifecycle lives on the module, which never consults this table
        ///         after creation).
        /// @param marketType Market-type id.
        /// @param module The module that was bound until now.
        event ModuleDeregistered(bytes4 indexed marketType, address indexed module);
    
        /*//////////////////////////////////////////////////////////////
                                      ERRORS
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Thrown when an enabled operator would end up with a zero fee recipient
        ///         (fees would be burned — see design finding F10).
        error FeeRecipientRequired();
        /// @notice Thrown when a non-owner attempts an operator-owner-gated call.
        error NotOwner();
        /// @notice Thrown when `venueForCreate` is called by anything other than the
        ///         module governance has bound (`registerModule`) for the venue's
        ///         market type (or when no module is bound yet).
        error NotModule();
        /// @notice Thrown when `acceptOperatorOwnership` is called by anyone other than
        ///         the staged pending owner.
        error PendingOwnerOnly();
        /// @notice Thrown when the supplied operatorId has never been registered.
        error UnknownOperator();
        /// @notice Thrown when the supplied (operatorId, venueId) has never been created.
        error UnknownVenue();
        /// @notice Thrown when a create-path view rejects a disabled operator.
        error OperatorDisabled();
        /// @notice Thrown when a create-path view rejects a venue whose
        ///         `creationEnabled` flag is false.
        error VenueCreationDisabled();
        /// @notice Thrown when a venue's bound market type does not match the market type
        ///         the caller is operating on (design finding F1 — e.g. a SPOT venue must
        ///         never originate a BINARY market).
        error MarketTypeMismatch(bytes4 expected, bytes4 actual);
        /// @notice Thrown when `initialize` is given a zero owner address.
        error ZeroOwner();
        /// @notice Thrown when callers reference operatorId 0 — the reserved "no operator" sentinel.
        error OperatorIdReserved();
        /// @notice Thrown when `createVenue` is given `marketType == bytes4(0)` (reserved;
        ///         zero doubles as the venue-existence sentinel). Also thrown by
        ///         `registerModule` for the same reserved sentinel.
        error MarketTypeReserved();
        /// @notice Thrown when a policy contract fails the IVenuePolicy ERC-165 probe.
        error InvalidPolicy();
        /// @notice Thrown when `feeParams` exceeds the sanity byte-length ceiling.
        error FeeParamsTooLong();
        /// @notice Thrown when an operator/venue `context` blob exceeds `_MAX_CONTEXT_BYTES`.
        error ContextTooLong();
        /// @notice Thrown when `registerModule` is given the zero address as module.
        error ZeroModule();
        /// @notice Thrown when the module being registered reports a different
        ///         `marketType()` than the id it is being bound under — a
        ///         governance fat-finger guard.
        error ModuleTypeMismatch(bytes4 expected, bytes4 actual);
        /// @notice Thrown when no module is registered for the requested market type.
        error UnknownMarketType(bytes4 marketType);
    
        /*//////////////////////////////////////////////////////////////
                              Constructor / Initializer
        //////////////////////////////////////////////////////////////*/
    
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
    
        /// @notice Initialise the proxy. Called once immediately after the ERC1967Proxy is created.
        /// @param owner_ Initial contract owner; receives upgrade authority.
        function initialize(address owner_) external initializer {
            if (owner_ == address(0)) revert ZeroOwner();
            __Ownable_init(owner_);
            // No __UUPSUpgradeable_init() since OZ 5.5: UUPSUpgradeable is no longer
            // transpiled (stateless; the old transpiled init was an empty no-op).
            _getMarketsCoreStorage().nextOperatorId = 1;
        }
    
        /*//////////////////////////////////////////////////////////////
                               Operator management
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Register a new operator. Permissionless — anyone can claim an identity;
        ///         economic weight comes from the venues and markets built under it.
        ///         Pass `enabled = false` to stage the configuration before going live.
        /// @param feeRecipient Default fee recipient for the operator's venues; required
        ///        non-zero when `enabled` is true.
        /// @param enabled Brand-level kill switch: when false the operator can back no
        ///        NEW markets under any of its venues (`venueForCreate` reverts). Existing
        ///        markets keep running — users can always exit. Stage config with
        ///        `enabled = false`, flip on to go live.
        /// @param policy Operator-wide IVenuePolicy consulted in addition to per-venue
        ///        policies (AND-composition); pass address(0) for none.
        /// @param context Opaque metadata bytes attached to the operator; empty for none.
        ///        The registry never decodes it — purely off-chain data, indexed from the
        ///        event. Size-capped by `_MAX_CONTEXT_BYTES`.
        /// @return operatorId Auto-incrementing id assigned to the new operator (always >= 1).
        /// @dev Reverts with `FeeRecipientRequired` if `enabled` and `feeRecipient == 0`.
        /// @dev Reverts with `InvalidPolicy` if `policy` fails the ERC-165 probe.
        /// @dev Reverts with `ContextTooLong` if `context` exceeds `_MAX_CONTEXT_BYTES`.
        function registerOperator(address feeRecipient, bool enabled, IVenuePolicy policy, bytes calldata context)
            external
            returns (uint32 operatorId)
        {
            _validateOperatorConfig(feeRecipient, enabled);
            _validatePolicy(policy);
            if (context.length > _MAX_CONTEXT_BYTES) revert ContextTooLong();
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            // Must return the pre-increment value as operatorId, so post-increment is required.
            // solhint-disable-next-line gas-increment-by-one
            operatorId = $.nextOperatorId++;
            $._operators[operatorId] = Operator({
                owner: msg.sender, feeRecipient: feeRecipient, enabled: enabled, policy: policy, context: context
            });
            emit OperatorRegistered(operatorId, msg.sender, feeRecipient, enabled, address(policy), context);
        }
    
        /// @notice Update all mutable operator fields in one call. Only the operator
        ///         owner may call.
        /// @param operatorId Target operator; caller must be its owner.
        /// @param feeRecipient New default fee recipient; required non-zero when `enabled`.
        /// @param enabled New enabled flag.
        /// @param policy New operator-wide policy; address(0) removes it.
        /// @param context New opaque metadata bytes (replaces the prior value; empty clears it).
        /// @dev Reverts with `FeeRecipientRequired` / `InvalidPolicy` / `ContextTooLong` as in `registerOperator`.
        function updateOperator(
            uint32 operatorId,
            address feeRecipient,
            bool enabled,
            IVenuePolicy policy,
            bytes calldata context
        ) external {
            Operator storage op = _onlyOperatorOwner(operatorId);
            _validateOperatorConfig(feeRecipient, enabled);
            _validatePolicy(policy);
            if (context.length > _MAX_CONTEXT_BYTES) revert ContextTooLong();
            op.feeRecipient = feeRecipient;
            op.enabled = enabled;
            op.policy = policy;
            op.context = context;
            emit OperatorUpdated(operatorId, feeRecipient, enabled, address(policy), context);
        }
    
        /// @notice Flip the operator's enabled flag without touching any other field.
        /// @param operatorId Target operator; caller must be its owner.
        /// @param enabled New enabled state.
        /// @dev Reverts with `FeeRecipientRequired` when enabling an operator whose
        ///      `feeRecipient` is zero (F10 invariant: enabled → recipient != 0).
        function setOperatorEnabled(uint32 operatorId, bool enabled) external {
            Operator storage op = _onlyOperatorOwner(operatorId);
            if (enabled && op.feeRecipient == address(0)) revert FeeRecipientRequired();
            op.enabled = enabled;
            emit OperatorEnabledChanged(operatorId, enabled);
        }
    
        /// @notice Stage a two-step operator ownership transfer. The pending new owner
        ///         must call `acceptOperatorOwnership` to complete it. Staging the
        ///         current owner cancels any pending transfer.
        /// @param operatorId Target operator; caller must be its current owner.
        /// @param newOwner Address of the proposed new owner; must be non-zero.
        function transferOperatorOwnership(uint32 operatorId, address newOwner) external {
            if (newOwner == address(0)) revert ZeroOwner();
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            Operator storage op = _onlyOperatorOwner(operatorId);
            if (newOwner == op.owner) {
                address staged = $.pendingOwner[operatorId];
                if (staged != address(0)) {
                    delete $.pendingOwner[operatorId];
                    emit OperatorOwnershipTransferCancelled(operatorId, staged);
                }
                return;
            }
            $.pendingOwner[operatorId] = newOwner;
            emit OperatorOwnershipTransferStarted(operatorId, op.owner, newOwner);
        }
    
        /// @notice Complete a pending operator ownership transfer. Must be called by the
        ///         address staged via `transferOperatorOwnership`.
        /// @param operatorId Target operator; `msg.sender` must be the pending new owner.
        function acceptOperatorOwnership(uint32 operatorId) external {
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            address pending = $.pendingOwner[operatorId];
            if (msg.sender != pending) revert PendingOwnerOnly();
            Operator storage op = $._operators[operatorId];
            address old = op.owner;
            op.owner = msg.sender;
            delete $.pendingOwner[operatorId];
            emit OperatorOwnershipTransferred(operatorId, old, msg.sender);
        }
    
        /*//////////////////////////////////////////////////////////////
                                 Venue management
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Create a venue under an operator. `marketType` is fixed for this
        ///         (operatorId, venueId) tuple FOREVER — changing it would orphan
        ///         markets whose pool params were decoded under the old type (design
        ///         finding F2). Everything else is mutable via `updateVenue`.
        /// @param operatorId Parent operator; caller must be its owner.
        /// @param marketType Market-type id (see `MarketTypeIds`); non-zero.
        /// @param config Initial mutable configuration; see `VenueConfig`.
        /// @return venueId The CONTRACT-GENERATED venue id: an opaque, globally
        ///         unique `bytes32` = `keccak256(operatorId, seq)`. Callers do NOT
        ///         supply it (an id is not a caller-chosen label) — this mirrors the
        ///         auto-assigned `operatorId`, just wider so it stays globally
        ///         unique and never a human tag. Collision is impossible, so there
        ///         is no `VenueAlreadyExists`.
        /// @dev Reverts with `MarketTypeReserved` on a zero market type.
        /// @dev Reverts with `InvalidPolicy` / `FeeParamsTooLong` on invalid config
        ///      (see `_validateVenueConfig`).
        function createVenue(uint32 operatorId, bytes4 marketType, VenueConfig calldata config)
            external
            returns (bytes32 venueId)
        {
            if (marketType == bytes4(0)) revert MarketTypeReserved();
            _onlyOperatorOwner(operatorId);
            _validateVenueConfig(config);
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            // Contract-generated id: keccak over (operatorId, monotonic seq). The
            // seq makes it unique even for the same operator; folding operatorId in
            // keeps ids from different operators disjoint by construction.
            venueId = keccak256(abi.encode(operatorId, ++$.nextVenueSeq));
            Venue storage v = $._venues[operatorId][venueId];
            v.marketType = marketType;
            v.config = config;
            $._venueIdsOf[operatorId].push(venueId);
            _emitVenueCreated(operatorId, venueId, marketType, config);
        }
    
        /// @notice Update a venue's mutable configuration. `marketType` is not part of
        ///         `VenueConfig` and therefore structurally cannot change here.
        /// @param operatorId Parent operator; caller must be its owner.
        /// @param venueId Target venue.
        /// @param config New configuration; see `VenueConfig`.
        /// @dev Reverts with `UnknownVenue` if the venue was never created.
        /// @dev Same config validation reverts as `createVenue`.
        function updateVenue(uint32 operatorId, bytes32 venueId, VenueConfig calldata config) external {
            _onlyOperatorOwner(operatorId);
            _validateVenueConfig(config);
            Venue storage v = _getMarketsCoreStorage()._venues[operatorId][venueId];
            if (v.marketType == bytes4(0)) revert UnknownVenue();
            v.config = config;
            emit VenueUpdated(
                operatorId,
                venueId,
                config.feeParams,
                config.feeRecipientOverride,
                address(config.policy),
                config.signer,
                config.creationEnabled,
                config.context
            );
        }
    
        /// @notice Flip a venue's creation flag without touching any other field.
        /// @param operatorId Parent operator; caller must be its owner.
        /// @param venueId Target venue.
        /// @param creationEnabled New origin-side enable flag.
        function setVenueEnabled(uint32 operatorId, bytes32 venueId, bool creationEnabled) external {
            _onlyOperatorOwner(operatorId);
            Venue storage v = _getMarketsCoreStorage()._venues[operatorId][venueId];
            if (v.marketType == bytes4(0)) revert UnknownVenue();
            v.config.creationEnabled = creationEnabled;
            emit VenueEnabledChanged(operatorId, venueId, creationEnabled);
        }
    
        /*//////////////////////////////////////////////////////////////
                                Module binding (governance)
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Bind the canonical module contract for a market type. Governance-
        ///         only. Only the bound module may consume a venue of that type
        ///         (the `venueForCreate` gate reads this table), making market
        ///         attribution a chain fact.
        /// @param marketType Market-type id (see `MarketTypeIds`); non-zero.
        /// @param module Module authorised to originate markets of this type;
        ///        non-zero, must report `marketType() == marketType`.
        function registerModule(bytes4 marketType, address module) external onlyOwner {
            if (marketType == bytes4(0)) revert MarketTypeReserved();
            if (module == address(0)) revert ZeroModule();
            bytes4 reported = IMarketsModule(module).marketType();
            if (reported != marketType) revert ModuleTypeMismatch(marketType, reported);
            _getMarketsCoreStorage().moduleOf[marketType] = module;
            emit ModuleRegistered(marketType, module);
        }
    
        /// @notice Retire a market type SAFELY: clears the binding so no NEW markets
        ///         of this type can be originated (the module's typed create path
        ///         calls `venueForCreate`, which no longer admits it). Every
        ///         EXISTING market keeps running untouched: mint/merge/redeem,
        ///         trading, and oracle resolution all live on the module and never
        ///         consult this table after creation. Re-`registerModule` to resume.
        /// @param marketType Market-type id to retire; must currently be bound.
        function deregisterModule(bytes4 marketType) external onlyOwner {
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            address module = $.moduleOf[marketType];
            if (module == address(0)) revert UnknownMarketType(marketType);
            delete $.moduleOf[marketType];
            emit ModuleDeregistered(marketType, module);
        }
    
        /// @notice The canonical module bound for a market type, or address(0) if unset.
        /// @param marketType Market-type id to query.
        /// @return The bound module address.
        function moduleOf(bytes4 marketType) external view returns (address) {
            return _getMarketsCoreStorage().moduleOf[marketType];
        }
    
        /*//////////////////////////////////////////////////////////////
                                  View Functions
        //////////////////////////////////////////////////////////////*/
    
        /// @notice Auto-incrementing. Returns the id that will be assigned on the next
        ///         `registerOperator` call. Starts at 1 so 0 stays reserved.
        function nextOperatorId() external view returns (uint32) {
            return _getMarketsCoreStorage().nextOperatorId;
        }
    
        /// @notice Staged new owner for a pending two-step operator transfer. Zero means
        ///         no transfer is in progress for that operatorId.
        /// @param operatorId Target operator.
        /// @return The staged pending owner, or address(0) if none.
        function pendingOperatorOwner(uint32 operatorId) external view returns (address) {
            return _getMarketsCoreStorage().pendingOwner[operatorId];
        }
    
        /// @notice Return the full operator record. Reverts for the reserved id 0.
        /// @param operatorId Target operator.
        /// @return Full Operator struct; `owner` is zero for unregistered ids.
        function getOperator(uint32 operatorId) external view returns (Operator memory) {
            if (operatorId == 0) revert OperatorIdReserved();
            return _getMarketsCoreStorage()._operators[operatorId];
        }
    
        /// @notice Return the full venue record (marketType + config).
        /// @param operatorId Parent operator.
        /// @param venueId Target venue.
        /// @return Full Venue struct; `marketType` is zero for venues never created.
        function getVenue(uint32 operatorId, bytes32 venueId) external view returns (Venue memory) {
            if (operatorId == 0) revert OperatorIdReserved();
            return _getMarketsCoreStorage()._venues[operatorId][venueId];
        }
    
        /// @notice Enumerate all venue ids ever created under `operatorId`.
        /// @param operatorId Parent operator.
        /// @return List of venue ids in creation order (soft-disabled venues included —
        ///         venues are never hard-deleted, so ids never dangle).
        function venueIdsOf(uint32 operatorId) external view returns (bytes32[] memory) {
            return _getMarketsCoreStorage()._venueIdsOf[operatorId];
        }
    
        /// @notice Resolve the effective fee recipient for a venue: the per-venue
        ///         override if set, otherwise the operator's default recipient.
        /// @param operatorId Parent operator.
        /// @param venueId Target venue.
        /// @return Effective recipient; may be zero only while the operator is disabled
        ///         (the F10 invariant guarantees enabled operators have a recipient).
        function resolvedFeeRecipient(uint32 operatorId, bytes32 venueId) public view returns (address) {
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            address override_ = $._venues[operatorId][venueId].config.feeRecipientOverride;
            return override_ != address(0) ? override_ : $._operators[operatorId].feeRecipient;
        }
    
        /// @notice Everything a market-type contract needs to admit a market-creation
        ///         under (operatorId, venueId), gathered in one call with all liveness
        ///         checks applied. This is the create-path hot view BinaryMarketsModule uses.
        /// @param operatorId Origin operator.
        /// @param venueId Origin venue.
        /// @param marketType Market type the CALLER operates on; must match the venue's
        ///        bound type (design finding F1 — cross-type attribution is rejected here,
        ///        not left to the caller).
        /// @return signer Per-venue EIP-712 signer; non-zero means the caller MUST verify a
        ///         venue signature (the signer's existence IS the requirement — no flag).
        /// @return operatorPolicy Operator-wide policy (AND'd with the venue policy); zero for none.
        /// @return venuePolicy Per-venue policy; zero for none.
        /// @return feeParams Type-specific fee parameters for the caller to decode + cap.
        /// @return feeRecipient Effective fee recipient (venue override or operator default).
        /// @dev Caller MUST be the module governance bound for `marketType` — reverts
        ///      `NotModule` otherwise, so only the canonical module can originate a
        ///      market under a venue and attribution can never be spoofed.
        /// @dev Reverts with `UnknownOperator` for unregistered operators.
        /// @dev Reverts with `OperatorDisabled` when the operator is soft-disabled.
        /// @dev Reverts with `UnknownVenue` for venues never created.
        /// @dev Reverts with `MarketTypeMismatch` when the venue serves a different type.
        /// @dev Reverts with `VenueCreationDisabled` when the venue's origin side is off.
        function venueForCreate(uint32 operatorId, bytes32 venueId, bytes4 marketType)
            external
            view
            returns (
                address signer,
                IVenuePolicy operatorPolicy,
                IVenuePolicy venuePolicy,
                bytes memory feeParams,
                address feeRecipient
            )
        {
            MarketsCoreStorage storage $ = _getMarketsCoreStorage();
            if (msg.sender != $.moduleOf[marketType]) revert NotModule();
            Operator storage op = $._operators[operatorId];
            if (op.owner == address(0)) revert UnknownOperator();
            if (!op.enabled) revert OperatorDisabled();
            Venue storage v = $._venues[operatorId][venueId];
            if (v.marketType == bytes4(0)) revert UnknownVenue();
            if (v.marketType != marketType) revert MarketTypeMismatch(marketType, v.marketType);
            VenueConfig storage cfg = v.config;
            if (!cfg.creationEnabled) revert VenueCreationDisabled();
            address recipient =
                cfg.feeRecipientOverride != address(0) ? cfg.feeRecipientOverride : op.feeRecipient;
            return (cfg.signer, op.policy, cfg.policy, cfg.feeParams, recipient);
        }
    
        /*//////////////////////////////////////////////////////////////
                                Internal helpers
        //////////////////////////////////////////////////////////////*/
    
        /// @dev UUPS upgrade authorization hook — restricted to the contract owner.
        function _authorizeUpgrade(address) internal override onlyOwner {}
    
        /// @dev F10 invariant: an enabled operator must have a fee recipient, otherwise
        ///      every venue falling back to the operator default would burn fees.
        function _validateOperatorConfig(address feeRecipient, bool enabled) internal pure {
            if (enabled && feeRecipient == address(0)) revert FeeRecipientRequired();
        }
    
        /// @dev Shared config validation for `createVenue` / `updateVenue` — factored so
        ///      the two paths can never drift.
        function _validateVenueConfig(VenueConfig calldata config) internal view {
            _validatePolicy(config.policy);
    
            // Common-sense bound: rejects obvious garbage before it is committed to
            // storage; not a security boundary (see `_MAX_FEE_PARAMS_BYTES`). The fee
            // rates themselves are opaque here — the bound module decodes and caps
            // them in its own units at market creation.
            if (config.feeParams.length > _MAX_FEE_PARAMS_BYTES) revert FeeParamsTooLong();
            if (config.context.length > _MAX_CONTEXT_BYTES) revert ContextTooLong();
        }
    
        /// @dev Revert if `policy` is non-zero but does not declare ERC-165 support for
        ///      IVenuePolicy. Catches both revert and false-return cases.
        function _validatePolicy(IVenuePolicy policy) internal view {
            if (address(policy) == address(0)) return;
            try policy.supportsInterface(type(IVenuePolicy).interfaceId) returns (bool ok) {
                if (!ok) revert InvalidPolicy();
            } catch {
                revert InvalidPolicy();
            }
        }
    
        /// @dev Load the operator and assert the caller is its owner. Returns the storage
        ///      pointer so callers can mutate fields without a second SLOAD.
        function _onlyOperatorOwner(uint32 operatorId) internal view returns (Operator storage op) {
            op = _getMarketsCoreStorage()._operators[operatorId];
            if (op.owner == address(0)) revert UnknownOperator();
            if (msg.sender != op.owner) revert NotOwner();
        }
    
        /// @dev Emit `VenueCreated` from its own frame to keep `createVenue` clear of the
        ///      event's argument fan-out.
        function _emitVenueCreated(
            uint32 operatorId,
            bytes32 venueId,
            bytes4 marketType,
            VenueConfig calldata config
        ) private {
            emit VenueCreated(
                operatorId,
                venueId,
                marketType,
                config.feeParams,
                config.feeRecipientOverride,
                address(config.policy),
                config.signer,
                config.creationEnabled,
                config.context
            );
        }
    
        /// @dev ERC-7201 namespaced storage accessor.
        function _getMarketsCoreStorage() private pure returns (MarketsCoreStorage storage $) {
            assembly {
                $.slot := _MarketsCoreStorageLocation
            }
        }
    }
    
  • src/interfaces/IMarketsModule.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    /// @title IMarketsModule
    /// @notice The MINIMAL surface every market-type module implements so
    ///         `MarketsCore.registerModule` can sanity-check a governance
    ///         binding before committing it: the module must self-report the
    ///         SAME market-type id it is being bound under. This is the only
    ///         requirement the registry places on a module — everything else
    ///         (creation, mint/merge/redeem, trading, resolution) is type-specific
    ///         and lives on the module's own typed surface, which users call
    ///         directly.
    ///
    ///         This is the extension seam: adding a market type = deploy a module
    ///         implementing this interface + `MarketsCore.registerModule
    ///         (marketType, module)`, with ZERO changes to the registry or
    ///         sibling modules.
    interface IMarketsModule {
        /// @notice The market-type id this module is the canonical module for
        ///         (matches its `MarketsCore.moduleOf` binding; checked at
        ///         registration).
        function marketType() external view returns (bytes4);
    }
    
  • src/interfaces/IOracleAdapter.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    import {QuestionDefinition} from "../types/OracleTypes.sol";
    import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    
    /// @title IOracleAdapter
    /// @notice Interface that oracle adapters must implement. Each adapter normalises a
    ///         specific oracle's interface into a standard surface the market-type
    ///         module (e.g. BinaryMarketsModule) can call.
    ///         The adapter also owns the Somnia reactivity subscription for its oracle
    ///         and calls `IOracleAnswerReceiver.onOracleAnswers` (payout-vector wire)
    ///         when an answer is posted.
    ///
    ///         Oracle v2 (DESIGN §8e) adds two lifecycle hooks: `onBind` (the ONE gate
    ///         every market creation passes) EARMARKS the resolution reserve — attached
    ///         with the market-creation value and locked per-market so resolution funds
    ///         are always present — while attributing the market to its operator and
    ///         counting the bind (cap-split input); `onResolved` (fired at the module's
    ///         irreversible resolve-commit) releases that earmark. The resolution callback
    ///         charges the earmark by the EXACT measured resolve cost and credits any
    ///         surplus back to the operator's withdrawable balance.
    ///
    ///         Implements ERC-165. The module validates `supportsInterface` at market
    ///         creation, so passing a non-conforming address reverts.
    interface IOracleAdapter is IERC165 {
        /// @notice Schedules a new oracle question. Optional — adapters that do not
        ///         support scheduling should revert on this call.
        /// @dev Oracle v2 hubs make this content-addressed (DESIGN §8c): the same
        ///      template definition returns the SAME question id and is charged only
        ///      marginal cost. Callers quote via `getSchedulingCost` and attach exactly
        ///      that; any excess is refunded.
        /// @param def Full question definition to schedule with the underlying oracle.
        /// @return oracleQuestionId Adapter-scoped id assigned to the (possibly deduplicated) question.
        function scheduleQuestion(QuestionDefinition calldata def)
            external
            payable
            returns (uint256 oracleQuestionId);
    
        /// @notice Notify the adapter that `marketId` (of `operatorId`) has been bound to
        ///         `oracleQuestionId`. Called by the answer receiver (the module) from
        ///         `_createMarket` — the one enforcement point every market passes.
        /// @dev Payable (Oracle v2, DESIGN §8e — EARMARK-AT-CREATION): the module forwards
        ///      exactly `resolveReserve()` as the attached value, which the hub LOCKS to the
        ///      operator for this market (revert if the value is not exactly the reserve). The
        ///      hub also records the market→operator attribution + the qid→markets fan-out
        ///      list and bumps the bind count (cap-split input). The earmark is released at
        ///      resolution: the hub charges the exact measured resolve cost against it and
        ///      credits the surplus. Adapters without metering return `resolveReserve() == 0`
        ///      and accept the (zero-value) call as a no-op.
        /// @param marketId Module-scoped market id being bound (the settlement-attribution key).
        /// @param operatorId Origin operator id whose earmark funds this market's resolve.
        /// @param oracleQuestionId Adapter-scoped question id the market was bound to.
        function onBind(bytes32 marketId, uint32 operatorId, uint256 oracleQuestionId) external payable;
    
        /// @notice Payer-aware bind (A1). As above, but records the `payer` that fronted the
        ///      reserve so the resolution SURPLUS is refunded to THAT account (not the
        ///      operator) — the fairness fix for open venues. Adapters that don't track a
        ///      payer may treat this identically to the 3-arg form.
        /// @param marketId Module-scoped market id being bound.
        /// @param operatorId Origin operator id whose earmark funds this market's resolve.
        /// @param oracleQuestionId Adapter-scoped question id the market was bound to.
        /// @param payer Account that attached the reserve — receives the resolution surplus.
        function onBind(bytes32 marketId, uint32 operatorId, uint256 oracleQuestionId, address payer)
            external
            payable;
    
        /// @notice Notify the adapter that `marketId` has irreversibly resolved (or voided).
        ///         Called by the answer receiver (the module) from `_resolve` at the
        ///         resolve-commit point — the ONE gate every resolution passes, on both the
        ///         hub drain (`resolveOne`) and pull-fallback (`pokeOracle`) paths, exactly
        ///         once per market (the module's `if (Resolved||Voided) return` guard).
        /// @dev Lets the adapter release the market's earmark on the BACKSTOP (pokeOracle)
        ///      path, which does not flow through the hub's metered drain. The hub
        ///      distinguishes the two paths internally (a transient drain flag) so the
        ///      earmark is settled exactly once and never double-charged. Adapters without
        ///      metering accept the call as a no-op.
        /// @param marketId Module-scoped market id whose resolution just committed.
        function onResolved(bytes32 marketId) external;
    
        /// @notice Returns the number of outcome slots for a given oracle question.
        ///         Called by the module at market creation to determine outcomeSlotCount.
        /// @param oracleQuestionId Adapter-scoped question id to query.
        /// @return slotCount Number of outcome slots the question resolves across.
        function getSlotCount(uint256 oracleQuestionId) external view returns (uint8 slotCount);
    
        /// @notice Pulls the current answer from the oracle. Used as the pull-fallback
        ///         path (the module's `pokeOracle` pulls through the adapter instead of
        ///         reading the oracle directly).
        ///         Must revert if no answer is available yet.
        /// @param oracleQuestionId Adapter-scoped question id whose answer to fetch.
        /// @return outcomeIdx Index of the resolved outcome; ignored when `voided` is true.
        /// @return voided True if the oracle voided the question rather than resolving it.
        function pullAnswer(uint256 oracleQuestionId) external view returns (uint8 outcomeIdx, bool voided);
    
        /// @notice Pulls the raw numeric answer for a resolved question. Backs
        ///         reference-resolved markets: the module compares two questions'
        ///         numeric answers on-chain instead of consuming the oracle's
        ///         bucket outcome. Must revert if no answer is available yet.
        /// @param oracleQuestionId Adapter-scoped question id whose value to fetch.
        /// @return numericValue Aggregated numeric answer in the question's native scale.
        /// @return voided True if the oracle voided the question rather than resolving it.
        function pullNumericAnswer(uint256 oracleQuestionId)
            external
            view
            returns (int256 numericValue, bool voided);
    
        /// @notice Returns the cost in native token to schedule a question.
        ///         Returns 0 if scheduling is not supported by this adapter.
        /// @dev Oracle v2 hubs quote the MARGINAL cost: the full oracle submission cost
        ///      for a definition that would schedule a NEW question, and 0 for one that
        ///      deduplicates onto an already-scheduled question (DESIGN §8c) — existing
        ///      callers that read the quote and forward exactly that value keep working
        ///      unchanged.
        /// @param def Full question definition whose scheduling cost to quote.
        /// @return cost Native amount that must be forwarded to `scheduleQuestion`.
        function getSchedulingCost(QuestionDefinition calldata def) external view returns (uint256 cost);
    
        /// @notice The resolution reserve that must be ATTACHED to each `onBind` call (the
        ///         market-creation value) — earmarked and locked per-market so resolution
        ///         funds are always present (DESIGN §8e, EARMARK-AT-CREATION). Sized as one
        ///         worst-case resolve (`perMarketResolveGas × maxFeePerGas`).
        /// @dev `onBind` reverts unless `msg.value` equals this exactly. The reserve is
        ///      collected per market (not a standing balance) and released at resolution
        ///      (metered charge + surplus credit). Adapters without metering return 0.
        /// @return reserve Native amount (wei) to attach to `onBind` per market.
        function resolveReserve() external view returns (uint256 reserve);
    }
    
  • src/interfaces/IOracleAnswerReceiver.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    
    /// @dev Canonical payout-vector denominator (Oracle v2, DESIGN §7.7): 100% in
    ///      basis-points × 1000 — the SAME unit the dex prediction family's fee math
    ///      uses (`ONE_HUNDRED_PERCENT_BPS * 1_000`), pinned here so the wire, the
    ///      market storage, and the settlement math all divide by one constant.
    ///      Every delivered payout vector satisfies `Σ numerators == 10_000_000` raw;
    ///      a binary win is `[D, 0]` / `[0, D]`, a void is uniform `[D/2, D/2]`.
    uint32 constant PAYOUT_VECTOR_DENOMINATOR = 10_000_000;
    
    /// @title IOracleAnswerReceiver
    /// @notice Implemented by the market-type module (e.g. BinaryMarketsModule).
    ///         Called by the oracle adapter (the hub) to resolve ONE market at a time.
    ///         Only the adapter registered for a given market may resolve it (the module
    ///         skips markets bound to a different adapter).
    ///
    ///         Oracle v2 wire (DESIGN §2B/§8b/§8e): resolution is delivered as a CTF-style
    ///         PAYOUT VECTOR instead of a single winning index — one redemption rule for
    ///         win / void / (future) scalar. The adapter translates the raw oracle answer
    ///         (discrete winner → one-hot, reference comparison → one-hot, every
    ///         `VoidReason` → uniform vector) and drives the per-market fan-out itself: the
    ///         hub's bounded-drain loop calls `resolveOne` per market so it can meter each
    ///         market's gas and debit the operator exactly (no unbounded module-side loop).
    ///
    ///         Implements ERC-165 so adapters can verify the receiver at construction
    ///         time rather than discovering a misconfiguration at resolution time.
    interface IOracleAnswerReceiver is IERC165 {
        /// @notice Resolve ONE market to the delivered payout vector. Called by the
        ///         registered adapter (the hub) from its bounded-drain loop, once per bound
        ///         market, so the hub can meter each call's gas and debit the operator.
        /// @dev `voided` is the EXPLICIT void discriminator (DESIGN §7.7: the fee waiver
        ///      is flag-driven, never inferred from the vector shape). When `voided` is
        ///      true the numerators are the uniform vector and the receiver applies its
        ///      void path (uniform payout, settlement fee waived). Reference-mode markets
        ///      ignore the delivered vector and recompute a one-hot from the market's own
        ///      numeric answer vs its reference (pulled from the adapter=hub). Non-reverting
        ///      per market: a per-market obstruction is skipped (retryable via `pokeOracle`)
        ///      rather than reverting the hub's callback.
        /// @param marketId Module-scoped market id to resolve.
        /// @param payoutDenominator Denominator the numerators are scaled against
        ///        (always `PAYOUT_VECTOR_DENOMINATOR`; self-describing for future families).
        /// @param payoutNumerators Per-outcome payout numerators; `Σ == payoutDenominator`.
        /// @param voided True if the oracle voided the question rather than resolving it.
        function resolveOne(
            bytes32 marketId,
            uint32 payoutDenominator,
            uint256[] calldata payoutNumerators,
            bool voided
        ) external;
    }
    
  • src/interfaces/IProphecyOracle.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    import {Answer, QuestionDefinition, QuestionStatus, VoidReason} from "../types/OracleTypes.sol";
    
    /// @title IProphecyOracle
    /// @notice Subset of the OracleEntryPoint interface that the adapter depends on.
    ///         - submitQuestion / getSubmissionCost: route through the entry point.
    ///         - getAnswer / getOutcomeLabels: view aggregators that delegate to the
    ///           active OracleCore via the entry point's oracleCoreFor mapping.
    ///         - AnswerPosted: emitted by OracleCore (not the entry point). The
    ///           adapter subscribes to the OracleCore address for this event and
    ///           passes that address as ORACLE_CORE in the constructor.
    interface IProphecyOracle {
        /// @notice Emitted by OracleCore when a question has been resolved (or voided).
        /// @param questionId Oracle-scoped id of the resolved question.
        /// @param outcomeIdx Index of the resolved discrete outcome, or the matched numeric interval.
        /// @param outcomeLabel Human-readable label for the resolved outcome.
        /// @param numericValue Aggregated numeric value (for numeric questions); 0 for discrete.
        /// @param voided True if the question was voided rather than resolved.
        /// @param reason VoidReason discriminator; `None` for normal resolutions, otherwise the void cause.
        /// @param receiptIds Ids of the aggregated committee receipts backing this answer.
        event AnswerPosted(
            uint256 indexed questionId,
            uint8 outcomeIdx,
            string outcomeLabel,
            int256 numericValue,
            bool voided,
            VoidReason reason,
            uint256[] receiptIds
        );
    
        /// @notice Submit a new question to the oracle for scheduling.
        /// @param def Full question definition, including sources and valid answers.
        /// @return questionId Oracle-scoped id assigned to the newly scheduled question.
        function submitQuestion(QuestionDefinition calldata def) external payable returns (uint256 questionId);
    
        /// @notice Quote the native cost required to schedule `def`.
        /// @param def Full question definition whose scheduling cost to quote.
        /// @return cost Native amount that must be forwarded with `submitQuestion`.
        function getSubmissionCost(QuestionDefinition calldata def) external view returns (uint256 cost);
    
        /// @notice Returns the lifecycle status of a question.
        /// @param questionId Oracle-scoped question id to inspect.
        /// @return status Current `QuestionStatus` for the question.
        function getStatus(uint256 questionId) external view returns (QuestionStatus status);
    
        /// @notice Returns the aggregated answer struct for a question, if any.
        /// @param questionId Oracle-scoped question id whose answer to fetch.
        /// @return answer Aggregated `Answer` record. Fields are zeroed until resolved.
        function getAnswer(uint256 questionId) external view returns (Answer memory answer);
    
        /// @notice Returns the human-readable outcome labels for a question.
        /// @param questionId Oracle-scoped question id to inspect.
        /// @return labels String labels, one per outcome slot / numeric interval.
        function getOutcomeLabels(uint256 questionId) external view returns (string[] memory labels);
    }
    
  • src/interfaces/ISomniaReactivityPrecompile.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    /// @notice Interface a contract must implement to receive reactivity callbacks.
    interface ISomniaEventHandler {
        /// @notice Callback invoked by the reactivity precompile when a subscribed event fires.
        /// @param emitter Address of the contract that emitted the observed event.
        /// @param eventTopics Log topics for the observed event (topic0 + up to 3 indexed topics).
        /// @param data ABI-encoded non-indexed event payload.
        function onEvent(address emitter, bytes32[] calldata eventTopics, bytes calldata data) external;
    }
    
    /// @notice Somnia Reactivity precompile, sourced from the chain spec.
    interface ISomniaReactivityPrecompile {
        /// @notice Parameters describing a single reactivity subscription.
        struct SubscriptionData {
            bytes32[4] eventTopics;
            address origin;
            address caller;
            address emitter;
            address handlerContractAddress;
            bytes4 handlerFunctionSelector;
            uint64 priorityFeePerGas;
            uint64 maxFeePerGas;
            uint64 gasLimit;
            bool isGuaranteed;
            bool isCoalesced;
        }
    
        /// @notice Emitted once per block by the precompile; consumed by block-tick subscribers.
        /// @param blockNumber Number of the block the tick corresponds to.
        event BlockTick(uint64 indexed blockNumber);
    
        /// @notice Emitted by the precompile at a scheduled wall-clock timestamp.
        /// @param timestampMillis Scheduled trigger time, in milliseconds since the Unix epoch.
        event Schedule(uint256 indexed timestampMillis);
    
        /// @notice Register a new reactivity subscription.
        /// @param data Subscription parameters (emitter, topics, handler, gas).
        /// @return subscriptionId Precompile-assigned id for the newly registered subscription.
        function subscribe(SubscriptionData calldata data) external returns (uint256 subscriptionId);
    
        /// @notice Cancel a previously registered subscription.
        /// @param subscriptionId Id returned by `subscribe`.
        function unsubscribe(uint256 subscriptionId) external;
    
        /// @notice Fetch the stored subscription parameters and owner for a subscription id.
        /// @param subscriptionId Id returned by `subscribe`.
        /// @return data Stored subscription parameters.
        /// @return owner Address that registered the subscription.
        function getSubscriptionInfo(uint256 subscriptionId)
            external
            view
            returns (SubscriptionData memory data, address owner);
    }
    
    /// @notice Helper constants and subscription builders for interacting with the Somnia
    ///         reactivity precompile.
    library SomniaExtensions {
        /// @notice Fixed address of the Somnia reactivity precompile.
        address internal constant SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS = address(0x0100);
    
        // Protocol subscription-option constants. Names and values mirror the upstream
        // @somnia-chain/reactivity library's `SomniaExtensions` (its fixed 0.8.30 pragma
        // is what keeps this a copy rather than an import — see that repo's
        // on-chain/smart-contracts/contracts/interfaces/SomniaExtensions.sol) and the
        // TS SDK's `DEFAULT_SUBSCRIPTION_OPTIONS` (packages/sdk/src/reactivity/index.ts).
        // The precompile's own rules: `gasLimit` must be in `(0, MAXIMUM_HANDLER_GAS_LIMIT]`,
        // and a non-zero `maxFeePerGas` must sit at least 6 gwei above `priorityFeePerGas`
        // (pass `0` to skip that check).
        /// @notice Default miner tip (wei) for reactivity callbacks.
        uint64 internal constant DEFAULT_PRIORITY_FEE_PER_GAS = 0;
        /// @notice Default EIP-1559 fee cap (wei) for reactivity callbacks.
        uint64 internal constant DEFAULT_MAX_FEE_PER_GAS = 20 gwei;
        /// @notice Default gas budget per reactivity callback invocation.
        uint64 internal constant DEFAULT_HANDLER_GAS_LIMIT = 10_000_000;
        /// @notice The precompile's ceiling on a subscription's callback gas budget.
        uint64 internal constant MAXIMUM_HANDLER_GAS_LIMIT = 200_000_000;
    
        /// @notice Build a one-shot `Schedule` subscription firing at `firesAtMillis`.
        /// @dev `emitter` is deliberately NOT set — each caller assigns it after building
        ///      (the canonical precompile address, or an injected stand-in such as the
        ///      anvil LiveReactivityStub). origin + caller stay wildcards (address(0)):
        ///      system-emitted Schedule events carry no useful tx-context, so non-zero
        ///      filters match nothing. isGuaranteed / isCoalesced are RESERVED per the
        ///      reactivity docs and must be false for every subscription.
        /// @param firesAtMillis Fire time, in milliseconds since the Unix epoch.
        /// @param handler Contract whose `onEvent` receives the callback.
        /// @param priorityFeePerGas Miner tip (wei) for the callback.
        /// @param maxFeePerGas EIP-1559 fee cap (wei) for the callback.
        /// @param gasLimit Gas budget for the callback invocation.
        /// @return data Subscription parameters, ready for `subscribe` once the caller
        ///         sets `emitter`.
        function scheduleSubAt(
            uint256 firesAtMillis,
            address handler,
            uint64 priorityFeePerGas,
            uint64 maxFeePerGas,
            uint64 gasLimit
        ) internal pure returns (ISomniaReactivityPrecompile.SubscriptionData memory data) {
            data.eventTopics[0] = ISomniaReactivityPrecompile.Schedule.selector;
            data.eventTopics[1] = bytes32(firesAtMillis);
            data.origin = address(0);
            data.caller = address(0);
            data.handlerContractAddress = handler;
            data.handlerFunctionSelector = ISomniaEventHandler.onEvent.selector;
            data.priorityFeePerGas = priorityFeePerGas;
            data.maxFeePerGas = maxFeePerGas;
            data.gasLimit = gasLimit;
            data.isGuaranteed = false;
            data.isCoalesced = false;
        }
    
        /// @notice Build a `BlockTick` subscription for `targetBlock`.
        /// @dev Same shape (and same caller-assigns-`emitter` contract) as {scheduleSubAt};
        ///      only the topic pair differs.
        /// @param targetBlock Block to fire at (one-shot); 0 fires EVERY block (recurring).
        /// @param handler Contract whose `onEvent` receives the callback.
        /// @param priorityFeePerGas Miner tip (wei) for the callback.
        /// @param maxFeePerGas EIP-1559 fee cap (wei) for the callback.
        /// @param gasLimit Gas budget for the callback invocation.
        /// @return data Subscription parameters, ready for `subscribe` once the caller
        ///         sets `emitter`.
        function blockTickSubAt(
            uint256 targetBlock,
            address handler,
            uint64 priorityFeePerGas,
            uint64 maxFeePerGas,
            uint64 gasLimit
        ) internal pure returns (ISomniaReactivityPrecompile.SubscriptionData memory data) {
            data.eventTopics[0] = ISomniaReactivityPrecompile.BlockTick.selector;
            data.eventTopics[1] = bytes32(targetBlock);
            data.origin = address(0);
            data.caller = address(0);
            data.handlerContractAddress = handler;
            data.handlerFunctionSelector = ISomniaEventHandler.onEvent.selector;
            data.priorityFeePerGas = priorityFeePerGas;
            data.maxFeePerGas = maxFeePerGas;
            data.gasLimit = gasLimit;
            data.isGuaranteed = false;
            data.isCoalesced = false;
        }
    
        /// @notice Build a persistent subscription on a contract-emitted event.
        /// @dev Unlike {scheduleSubAt} this DOES set `emitter` — contract-emitted events
        ///      filter on their real emitter (no precompile-emitter special case).
        ///      origin + caller stay wildcards (address(0)).
        /// @param topic0 topic0 (event signature hash) of the observed event.
        /// @param emitter Contract whose logs the subscription filters on.
        /// @param handler Contract whose `onEvent` receives the callback.
        /// @param priorityFeePerGas Miner tip (wei) for the callback.
        /// @param maxFeePerGas EIP-1559 fee cap (wei) for the callback.
        /// @param gasLimit Gas budget for the callback invocation.
        /// @return data Subscription parameters, ready for `subscribe`.
        function eventSub(
            bytes32 topic0,
            address emitter,
            address handler,
            uint64 priorityFeePerGas,
            uint64 maxFeePerGas,
            uint64 gasLimit
        ) internal pure returns (ISomniaReactivityPrecompile.SubscriptionData memory data) {
            data.eventTopics[0] = topic0;
            data.origin = address(0);
            data.caller = address(0);
            data.emitter = emitter;
            data.handlerContractAddress = handler;
            data.handlerFunctionSelector = ISomniaEventHandler.onEvent.selector;
            data.priorityFeePerGas = priorityFeePerGas;
            data.maxFeePerGas = maxFeePerGas;
            data.gasLimit = gasLimit;
            data.isGuaranteed = false;
            data.isCoalesced = false;
        }
    }
    
  • src/interfaces/IVenuePolicy.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
    
    /// @title IVenuePolicy
    /// @notice Pluggable access-control hook a venue can attach to its `Venue`
    ///         record in the registry. The market-type module consults a venue's
    ///         policy at three decision points; each method returns `false` to deny.
    ///
    ///         A venue's policy contract serves all three callbacks. Implementations
    ///         pick which dimensions they actually gate; the others can return `true`
    ///         unconditionally. A policy is OPTIONAL: a venue (and operator) with no
    ///         create-side policy is open — a zero policy means "no restriction", so
    ///         anyone may create markets. Attach a policy only to gate creation.
    ///
    ///         Implements ERC-165. The registry validates `supportsInterface` before
    ///         accepting a policy address, so passing a non-conforming address reverts.
    interface IVenuePolicy is IERC165 {
        /// @notice Called when `user` attempts to create a market through this
        ///         venue (i.e. tagging the create-market call with this venue's id).
        ///         Use this for KYC / allowlist gates on market creators.
        /// @param user Account attempting to create the market.
        /// @return allowed True if `user` is permitted to create markets under this venue.
        function canCreateMarket(address user) external view returns (bool allowed);
    
        /// @notice Called when `user` attempts to buy/sell through this venue.
        ///         `conditionId` lets the policy gate on a per-market basis (e.g.
        ///         "this venue only allows verified accounts to trade on real-money
        ///         markets; demo markets are open").
        /// @param user Account attempting to trade.
        /// @param conditionId Identifier of the market being traded.
        /// @return allowed True if `user` is permitted to trade `conditionId` under this venue.
        function canTrade(address user, bytes32 conditionId) external view returns (bool allowed);
    
        /// @notice Called when a *different* venue (`routingVenueId`) attempts to
        ///         route a trade against a market that *this* venue originated.
        ///         Lets a market-creating venue gate which counterparties may
        ///         facilitate trades on its markets. Returning `false` blocks the
        ///         trade.
        ///
        ///         Skipped entirely when the routing venue and the origin venue
        ///         are the same (a venue never gates itself). Skipped when the
        ///         origin venue is 0 (the market was created without a venue —
        ///         there is no origin policy to consult).
        ///
        ///         A `routingVenueId` of 0 represents a venueless ("direct") trade
        ///         path; an origin venue can still refuse it by returning `false`,
        ///         which is the lever for "all trades on my markets must go through
        ///         an approved routing venue".
        /// @param routingVenueId Venue id attempting to route the trade (0 for a direct path).
        /// @param conditionId Identifier of the market being routed against.
        /// @return allowed True if `routingVenueId` is permitted to route trades on `conditionId`.
        function canRoute(uint32 routingVenueId, bytes32 conditionId) external view returns (bool allowed);
    }
    
  • src/types/OracleTypes.sol
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.34;
    
    /// @notice Minimal stubs for the ProphecyOracle's external types. Matches the
    ///         oracle's OracleTypes.sol exactly so ABI encoding is compatible.
    
    /// @notice Enum discriminating the answer shape for a question.
    enum AnswerType {
        NumericAnswer,
        DiscreteAnswer
    }
    
    /// @notice Enum discriminating the source type used to fetch a question's answer.
    enum QuestionSourceType {
        Website,
        JSON,
        Contract
    }
    
    /// @notice Enum tracking the lifecycle status of a question inside the oracle.
    enum QuestionStatus {
        None,
        Scheduled,
        AwaitingAnswer,
        Resolved,
        Voided
    }
    
    /// @notice Carried on AnswerPosted. `None` for normal resolutions; otherwise
    ///         identifies why the question was voided. Must match the layout of
    ///         `VoidReason` in prophecy-oracle's OracleTypes.sol — the enum value
    ///         is ABI-encoded as `uint8` in the event payload.
    enum VoidReason {
        None,
        InsufficientSources,
        NumericOverflow,
        NoBucketMatch,
        NoDiscreteWinner,
        Administrative,
        Expired
    }
    
    /// @notice Inclusive numeric range. `low <= aggregatedValue <= high` matches the bucket.
    struct Interval {
        int256 low;
        int256 high;
    }
    
    /// @notice Set of valid answers a question may resolve to.
    struct ValidAnswers {
        AnswerType answerType;
        string[] discreteOutcomes;
        Interval[] numericIntervals;
        uint64 numericDecimals;
    }
    
    /// @notice A single answer source (website URL, JSON URL, on-chain contract) with encoded params.
    struct QuestionSource {
        QuestionSourceType sourceType;
        bytes params;
    }
    
    /// @notice Full definition of a question to be scheduled with the oracle.
    struct QuestionDefinition {
        string questionText;
        QuestionSource[] sources;
        ValidAnswers validAnswers;
        uint256 resolutionTime;
        uint256 minAgreement;
        uint256 subcommitteeSize;
        uint256 subcommitteeThreshold;
    }
    
    /// @notice The oracle's final answer for a question.
    struct Answer {
        uint64 timestamp;
        bool voided;
        uint8 outcomeIdx;
        int256 numericValue;
        uint256[] receiptIds;
    }
    
    /// @notice Full on-oracle question record, exposed via the oracle's storage getters.
    /// @dev Field order mirrors the oracle contract's storage layout exactly, so packing
    ///      cannot be reordered without breaking ABI/getter compatibility.
    // solhint-disable-next-line gas-struct-packing
    struct Question {
        address creator;
        AnswerType answerType;
        string[] discreteOutcomes;
        Interval[] numericIntervals;
        uint64 numericDecimals;
        uint256[] sourceIds;
        uint256 asyncSourceCount;
        uint256 resolutionTime;
        uint256 minAgreement;
        uint256 subcommitteeSize;
        uint256 subcommitteeThreshold;
        QuestionStatus status;
        bool answerPosted;
        bool voided;
        string questionText;
    }
    
ABI · 104 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "oracle_",
        "type": "address",
        "internalType": "contract IProphecyOracle"
      },
      {
        "name": "oracleCore_",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "reactivity_",
        "type": "address",
        "internalType": "contract ISomniaReactivityPrecompile"
      },
      {
        "name": "receiver_",
        "type": "address",
        "internalType": "contract IOracleAnswerReceiver"
      },
      {
        "name": "marketsCore_",
        "type": "address",
        "internalType": "contract MarketsCore"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "AddressEmptyCode",
    "type": "error",
    "inputs": [
      {
        "name": "target",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "AlreadySubscribed",
    "type": "error",
    "inputs": []
  },
  {
    "name": "ERC1967InvalidImplementation",
    "type": "error",
    "inputs": [
      {
        "name": "implementation",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "ERC1967NonPayable",
    "type": "error",
    "inputs": []
  },
  {
    "name": "FailedCall",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InsufficientCredit",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidInitialization",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidOperator",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidReceiver",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NoPrecompile",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotInitializing",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotOperatorOwner",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotReactivity",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotReceiver",
    "type": "error",
    "inputs": []
  },
  {
    "name": "OwnableInvalidOwner",
    "type": "error",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "OwnableUnauthorizedAccount",
    "type": "error",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "QuestionNotFinal",
    "type": "error",
    "inputs": []
  },
  {
    "name": "ReentrancyGuardReentrantCall",
    "type": "error",
    "inputs": []
  },
  {
    "name": "RefundFailed",
    "type": "error",
    "inputs": []
  },
  {
    "name": "UUPSUnauthorizedCallContext",
    "type": "error",
    "inputs": []
  },
  {
    "name": "UUPSUnsupportedProxiableUUID",
    "type": "error",
    "inputs": [
      {
        "name": "slot",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ]
  },
  {
    "name": "UnderpaidSchedulingCost",
    "type": "error",
    "inputs": []
  },
  {
    "name": "WithdrawFailed",
    "type": "error",
    "inputs": []
  },
  {
    "name": "WrongEmitter",
    "type": "error",
    "inputs": []
  },
  {
    "name": "WrongEvent",
    "type": "error",
    "inputs": []
  },
  {
    "name": "WrongReserveAttached",
    "type": "error",
    "inputs": []
  },
  {
    "name": "AnswerDelivered",
    "type": "event",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "payoutDenominator",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "payoutNumerators",
        "type": "uint256[]",
        "indexed": false,
        "internalType": "uint256[]"
      },
      {
        "name": "voided",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "CallbackAccounted",
    "type": "event",
    "inputs": [
      {
        "name": "marketsResolved",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "gasPrice",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "measuredGas",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "overheadGasAttributed",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "totalCost",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "totalCharged",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "subsidy",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "pendingRemaining",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "CreditWithdrawn",
    "type": "event",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "indexed": true,
        "internalType": "uint32"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Deposited",
    "type": "event",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "DrainContinuation",
    "type": "event",
    "inputs": [
      {
        "name": "subscriptionId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "pendingRemaining",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "DrainParamsUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "perMarketResolveGas",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "callbackBaseGas",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      },
      {
        "name": "maxResolvesPerCallback",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      },
      {
        "name": "resolveGasReserve",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "name": "GasParamsUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "priorityFeePerGas",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "maxFeePerGas",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      },
      {
        "name": "gasLimit",
        "type": "uint64",
        "indexed": true,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Initialized",
    "type": "event",
    "inputs": [
      {
        "name": "version",
        "type": "uint64",
        "indexed": false,
        "internalType": "uint64"
      }
    ],
    "anonymous": false
  },
  {
    "name": "MarketBound",
    "type": "event",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "operatorId",
        "type": "uint32",
        "indexed": true,
        "internalType": "uint32"
      },
      {
        "name": "bindCount",
        "type": "uint32",
        "indexed": false,
        "internalType": "uint32"
      }
    ],
    "anonymous": false
  },
  {
    "name": "MarketResolveCharged",
    "type": "event",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "operatorId",
        "type": "uint32",
        "indexed": true,
        "internalType": "uint32"
      },
      {
        "name": "measuredGas",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "overheadShare",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "cost",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "charged",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferStarted",
    "type": "event",
    "inputs": [
      {
        "name": "previousOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "newOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferred",
    "type": "event",
    "inputs": [
      {
        "name": "previousOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "newOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "PayerCreditWithdrawn",
    "type": "event",
    "inputs": [
      {
        "name": "payer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "PayerSurplusCredited",
    "type": "event",
    "inputs": [
      {
        "name": "payer",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "QuestionCapSplit",
    "type": "event",
    "inputs": [
      {
        "name": "questionKey",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "supersededQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "newQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "QuestionReused",
    "type": "event",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "questionKey",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "scheduler",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "QuestionScheduled",
    "type": "event",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "questionKey",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "scheduler",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "oracleCost",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ReserveEarmarked",
    "type": "event",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "indexed": true,
        "internalType": "uint32"
      },
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "SurplusCredited",
    "type": "event",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "indexed": true,
        "internalType": "uint32"
      },
      {
        "name": "marketId",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Upgraded",
    "type": "event",
    "inputs": [
      {
        "name": "implementation",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ANSWER_POSTED_TOPIC",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MARKETS_CORE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract MarketsCore"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAX_BINDS_PER_QUESTION",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "ORACLE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract IProphecyOracle"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "ORACLE_CORE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "REACTIVITY",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract ISomniaReactivityPrecompile"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "RECEIVER",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "contract IOracleAnswerReceiver"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "SCHEDULE_TOPIC",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "UPGRADE_INTERFACE_VERSION",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "acceptOwnership",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "bindCount",
    "type": "function",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "count",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "callbackBaseGas",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "continuationSubId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "creditOf",
    "type": "function",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "outputs": [
      {
        "name": "balance",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "earmarkedOf",
    "type": "function",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "outputs": [
      {
        "name": "locked",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "enableReactivity",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "subId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "gasLimit",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getSchedulingCost",
    "type": "function",
    "inputs": [
      {
        "name": "def",
        "type": "tuple",
        "components": [
          {
            "name": "questionText",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "sources",
            "type": "tuple[]",
            "components": [
              {
                "name": "sourceType",
                "type": "uint8",
                "internalType": "enum QuestionSourceType"
              },
              {
                "name": "params",
                "type": "bytes",
                "internalType": "bytes"
              }
            ],
            "internalType": "struct QuestionSource[]"
          },
          {
            "name": "validAnswers",
            "type": "tuple",
            "components": [
              {
                "name": "answerType",
                "type": "uint8",
                "internalType": "enum AnswerType"
              },
              {
                "name": "discreteOutcomes",
                "type": "string[]",
                "internalType": "string[]"
              },
              {
                "name": "numericIntervals",
                "type": "tuple[]",
                "components": [
                  {
                    "name": "low",
                    "type": "int256",
                    "internalType": "int256"
                  },
                  {
                    "name": "high",
                    "type": "int256",
                    "internalType": "int256"
                  }
                ],
                "internalType": "struct Interval[]"
              },
              {
                "name": "numericDecimals",
                "type": "uint64",
                "internalType": "uint64"
              }
            ],
            "internalType": "struct ValidAnswers"
          },
          {
            "name": "resolutionTime",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "minAgreement",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeSize",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeThreshold",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct QuestionDefinition"
      }
    ],
    "outputs": [
      {
        "name": "cost",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getSlotCount",
    "type": "function",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "slotCount",
        "type": "uint8",
        "internalType": "uint8"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "initialize",
    "type": "function",
    "inputs": [
      {
        "name": "admin_",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "priorityFeePerGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "maxFeePerGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "gasLimit_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "perMarketResolveGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "callbackBaseGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "maxResolvesPerCallback_",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "resolveGasReserve_",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "marketsForQuestion",
    "type": "function",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "markets",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "maxFeePerGas",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "maxResolvesPerCallback",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "migrateSubscription",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "subId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "onBind",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "payer",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "onBind",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "onEvent",
    "type": "function",
    "inputs": [
      {
        "name": "emitter",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "eventTopics",
        "type": "bytes32[]",
        "internalType": "bytes32[]"
      },
      {
        "name": "data",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "onResolved",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "operatorOf",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "outstandingOf",
    "type": "function",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "outputs": [
      {
        "name": "count",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "payerCreditOf",
    "type": "function",
    "inputs": [
      {
        "name": "payer",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "payerOf",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "payer",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pendingOwner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pendingResolves",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "remaining",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "perMarketResolveGas",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "priorityFeePerGas",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "proxiableUUID",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pullAnswer",
    "type": "function",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "outcomeIdx",
        "type": "uint8",
        "internalType": "uint8"
      },
      {
        "name": "voided",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pullNumericAnswer",
    "type": "function",
    "inputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "numericValue",
        "type": "int256",
        "internalType": "int256"
      },
      {
        "name": "voided",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "questionIdByKey",
    "type": "function",
    "inputs": [
      {
        "name": "questionKey",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "questionKeyOf",
    "type": "function",
    "inputs": [
      {
        "name": "def",
        "type": "tuple",
        "components": [
          {
            "name": "questionText",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "sources",
            "type": "tuple[]",
            "components": [
              {
                "name": "sourceType",
                "type": "uint8",
                "internalType": "enum QuestionSourceType"
              },
              {
                "name": "params",
                "type": "bytes",
                "internalType": "bytes"
              }
            ],
            "internalType": "struct QuestionSource[]"
          },
          {
            "name": "validAnswers",
            "type": "tuple",
            "components": [
              {
                "name": "answerType",
                "type": "uint8",
                "internalType": "enum AnswerType"
              },
              {
                "name": "discreteOutcomes",
                "type": "string[]",
                "internalType": "string[]"
              },
              {
                "name": "numericIntervals",
                "type": "tuple[]",
                "components": [
                  {
                    "name": "low",
                    "type": "int256",
                    "internalType": "int256"
                  },
                  {
                    "name": "high",
                    "type": "int256",
                    "internalType": "int256"
                  }
                ],
                "internalType": "struct Interval[]"
              },
              {
                "name": "numericDecimals",
                "type": "uint64",
                "internalType": "uint64"
              }
            ],
            "internalType": "struct ValidAnswers"
          },
          {
            "name": "resolutionTime",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "minAgreement",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeSize",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeThreshold",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct QuestionDefinition"
      }
    ],
    "outputs": [
      {
        "name": "key",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "name": "renounceOwnership",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "reservedFor",
    "type": "function",
    "inputs": [
      {
        "name": "marketId",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "reserve",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "resolveGasReserve",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "resolveReserve",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "reserve",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "scheduleQuestion",
    "type": "function",
    "inputs": [
      {
        "name": "def",
        "type": "tuple",
        "components": [
          {
            "name": "questionText",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "sources",
            "type": "tuple[]",
            "components": [
              {
                "name": "sourceType",
                "type": "uint8",
                "internalType": "enum QuestionSourceType"
              },
              {
                "name": "params",
                "type": "bytes",
                "internalType": "bytes"
              }
            ],
            "internalType": "struct QuestionSource[]"
          },
          {
            "name": "validAnswers",
            "type": "tuple",
            "components": [
              {
                "name": "answerType",
                "type": "uint8",
                "internalType": "enum AnswerType"
              },
              {
                "name": "discreteOutcomes",
                "type": "string[]",
                "internalType": "string[]"
              },
              {
                "name": "numericIntervals",
                "type": "tuple[]",
                "components": [
                  {
                    "name": "low",
                    "type": "int256",
                    "internalType": "int256"
                  },
                  {
                    "name": "high",
                    "type": "int256",
                    "internalType": "int256"
                  }
                ],
                "internalType": "struct Interval[]"
              },
              {
                "name": "numericDecimals",
                "type": "uint64",
                "internalType": "uint64"
              }
            ],
            "internalType": "struct ValidAnswers"
          },
          {
            "name": "resolutionTime",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "minAgreement",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeSize",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "subcommitteeThreshold",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct QuestionDefinition"
      }
    ],
    "outputs": [
      {
        "name": "oracleQuestionId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "setDrainParams",
    "type": "function",
    "inputs": [
      {
        "name": "perMarketResolveGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "callbackBaseGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "maxResolvesPerCallback_",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "resolveGasReserve_",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setGasParams",
    "type": "function",
    "inputs": [
      {
        "name": "priorityFeePerGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "maxFeePerGas_",
        "type": "uint64",
        "internalType": "uint64"
      },
      {
        "name": "gasLimit_",
        "type": "uint64",
        "internalType": "uint64"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "subscriptionId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "supportsInterface",
    "type": "function",
    "inputs": [
      {
        "name": "interfaceId",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "outputs": [
      {
        "name": "supported",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "upgradeToAndCall",
    "type": "function",
    "inputs": [
      {
        "name": "newImplementation",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "data",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "withdraw",
    "type": "function",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address payable"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "withdrawMyCredit",
    "type": "function",
    "inputs": [
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address payable"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "withdrawableOf",
    "type": "function",
    "inputs": [
      {
        "name": "operatorId",
        "type": "uint32",
        "internalType": "uint32"
      }
    ],
    "outputs": [
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "receive",
    "stateMutability": "payable"
  }
]