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"
}
]