Reference

Subsystem guardrails

What a security pool, share migration, Escalation Game, fork migration, Truth Auction, or REP/ETH price coordinator will and will not accept, one subsystem at a time. Every rule names the contract source that enforces it, so operators, indexers, and interface maintainers can check it against the exact Solidity.

The invariant catalog connects these local guardrails to cross-contract conservation, liveness, and the stated economic assumptions; the security model is normative for external assumptions. Release procedure lives in Launch a protocol release, assumption monitoring in Monitor security assumptions, and the remaining support contracts in the contract inventory.

Security pool guardrails

The following pool-creation, minting, withdrawal, and direct-ETH rules are enforced by contracts. Participant-controlled payout and share-receiving addresses must satisfy A22 asset-recipient compatibility. Deterministic identity depends on A27 cryptographic security, while vault and token authority depends on A28 account authority.

Area Implementation behavior Source
Origin pool shape Origin pools require an existing question, an unforked universe, a present universe REP token, and exactly two categorical labels in this order: Yes, then No. Statoblast adds Invalid as the third trading and resolution outcome. At construction, the effective escalation deposit is exactly max(1 REP, live universe theoretical REP supply / 10,000,000); a new origin's non-decision threshold must exceed that effective value. A zero configured vault REP floor selects the default live universe theoretical supply / 100,000; a nonzero constructor value is the exact override. The independently configured security-bond debt floor defaults to 1 ETH. SecurityPoolFactory.sol, ShareToken.sol, BinaryOutcomes.sol
Deployment history The factory records security-pool deployments and exposes paged deployment-history reads for indexers and UIs. SecurityPoolFactory.sol
Deterministic addresses SecurityPoolFactory derives securityPoolSalt = keccak256(abi.encode(parent, universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas)), using a zero parent for an origin. The coordinator and child truth-auction factories each hash that value again with their caller (SecurityPoolFactory) for their CREATE2 salt. The pool deployment worker instead uses literal CREATE2 salt zero; its address varies through the full constructor init-code hash, which contains the pool wiring. An origin share token uses originId = keccak256(abi.encode(questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas, originUniverseId)) directly as its CREATE2 salt, while factory ownership, Zoltar, and question ID remain in its init code; children reuse that lineage token and inherit its priority fee. SecurityPoolFactory.sol, SecurityPoolDeployer.sol, PriceOracleManagerAndOperatorQueuerFactory.sol, UniformPriceDualCapBatchAuctionFactory.sol, ShareTokenFactory.sol
Share-token salt squatting Direct ShareTokenFactory callers cannot reserve the canonical origin-pool share-token address. CREATE2 includes constructor arguments in the init-code hash, and the share token owner is msg.sender, so a direct caller using the canonical salt deploys a caller-owned token at a different address than the SecurityPoolFactory deployment. ShareTokenFactory.sol, ShareToken.sol
Complete-set capacity Complete-set minting checks the next collateral amount against live ETH minting capacity and both actual REP-backing constraints. Dispute-staked REP counts only toward associated backing, not migration-safe pool-held backing. It also requires any explicit unassigned auction position to remain healthy after the mint. Sold truth-auction ownership enters both total ownership and fee eligibility at finalization, so it accrues fees before the bidder claims it; its health affects minting, not fee accrual. Claiming later assigns that already-counted ownership and its fees to the bidder vault without adding it to either pool aggregate again. The settlement delegate updates accounting before the share-token mint call. Fork finalization instead installs inherited collateral when actual ETH covers that collateral and accrued fees, even if the accepted REP price makes the position unhealthy. Complete-set holders can redeem at the funded collateral-per-share rate; fixed-outcome winners can redeem their shares. For unresolved children, REP deposits can restore backing and capacity, and liquidation can transfer debt to a healthy receiver or record bad debt under the existing rules. No market-price recovery is required to activate the child or redeem its funded ETH. SecurityPool.createCompleteSet, SecurityPoolSettlementDelegate.createCompleteSet, SecurityPool.depositRepToVault
Fee accrual clamp While the initial pool's universe remains unforked, its provisional fee cutoff is the question end time. A universe fork replaces that cutoff with the fork timestamp, whether the fork is before or after the question end. A child with a fixed outcome and no continuation game stops accruing fees at finalization. Other children open a new epoch at finalization, ending at local escalation-game resolution or a subsequent universe fork. SecurityPool.sol
Fee accrual and rounding Fee-eligible capacity includes live vault-assigned ownership and sold truth-auction ownership awaiting claim. Settlement-collateral decay first enters an unallocated reserve; vault checkpoints and auction claims preserve fractional carry and move only whole, redeemable attoETH into totalClaimableVaultFeesAttoEth. totalAccruedFeesAttoEth() combines reserve and assigned claimable vault fees for settlement-collateral reconciliation. Global fee-index and sub-attoETH carries preserve value between accruals. Capacity-ownership-scoped index carry is cleared when pool financials are installed, ordinary vault capacity ownership changes, or liquidation transfers capacity ownership, so prior attribution dust is not reassigned. Assigning finalized-auction ownership to its winning vault is the exception: it preserves that carry because the sold ownership was already fee-eligible and already part of the denominator. After a fork permanently ends accrual, the pool tracks capacity ownership still awaiting the final index. Auction claims preserve aggregate fees; once every eligible vault and auction allocation is reconciled, a subsequent permissionless vault fee checkpoint returns any whole reserve attoETH that no vault can individually claim to settlement collateral. SecurityPool.updateSettlementCollateral, SecurityPool.updateVaultFees, SecurityPool._clearFeeIndexRemainder
Retention-rate updates Retention-rate updates no-op when the pool is not Operational; otherwise utilization divides tracked collateral by live ETH minting capacity derived from total capacity ownership. Unclaimed sold auction ownership therefore already affects utilization and fee accrual. A later claim changes only its vault assignment, not fee eligibility, total ownership, live capacity, or retention solely because of that claim. SecurityPool.sol
Multiplier-preserving REP outflows withdrawRepFromVault rejects withdrawal while the vault still has REP escrowed in an escalation game. Withdrawals and new escalation deposits independently preserve multiplier-adjusted REP backing for the affected vault and aggregate pool at the latest valid REP/ETH price. SecurityPool.sol
External fork withdrawal lock If the universe forked before the local escalation game ended and non-decision was not reached, parent-pool escalation withdrawal reverts. The child continuation already has the canonical snapshot and aggregate backing: winning inherited deposits settle there by proof, inherited losers require no transaction, and clearing the vault's unresolved parent escalation-deposit accounting is optional. SecurityPool.sol, EscalationGameSettlement.withdrawDeposit, EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep
REP-to-backing-unit round-up The pool’s internal REP-to-backing-unit conversion uses ceiling division when it removes vault REP backing units for an escalation deposit. That intentionally removes enough REP backing units to cover the requested REP even when the conversion is fractional. SecurityPool._attoRepToBackingUnitsRoundUp, SecurityPool.depositToEscalationGame
Escalation deposit wrapper depositToEscalationGame rejects pools with an inherited fixed outcome because they cannot enter another fork or safely unwind a later local non-decision. Otherwise it deploys the game on the first valid post-end deposit, previews the accepted amount, removes vault REP backing units with round-up accounting, checks local and global multiplier-adjusted backing, transfers REP into the game, and records the deposit. The game factory normally preserves the configured bond; if later REP burns reduce the live threshold to that bond or below, it uses nonDecisionThresholdAttoRep - 1 so long as the threshold exceeds one attoREP. SecurityPool.depositToEscalationGame, EscalationGameFactory.deployEscalationGame, EscalationGame.recordDepositFromSecurityPool
Direct ETH receiver Ordinary calls to receive() accept ETH only from the forker, its Truth Auction, or its parent pool. Forced ETH can bypass receive(); it remains raw, unaccounted surplus and is not collateral or accrued fees. SecurityPool.sol
Vault enumeration getVaults(startIndex, count) pages the append-only vault registry in newest-registered-first order. Registration requires only a nonzero address and can occur without economic state. Consumers must read current REP backing, capacity ownership, claimable fees, escalation stake, and bad debt when deciding whether to display an entry. The call returns an empty array when count == 0 or the start index is out of range; getVaultCount() reports the registry length. SecurityPool.getVaults

Share migration

Share migration after a fork is user-facing asset migration, not vault REP migration. Canonical rules live in FORK-10 for persistent per-child materialization, SHARE-04 for the economic-claim denominator, and FORK-11 for unequal materialized supplies.

Area Implementation behavior Source
Persistent entitlement ShareToken.migrate preserves the caller's parent token balance as a branch-independent entitlement. After its first use that source balance is transfer-locked, preventing a seller and buyer from materializing the same claim. ShareToken.sol
Target list The target outcome list must be non-empty, valid for the fork question, and strictly increasing. ShareToken.sol
Canonical source and fork transition Migration requires a canonical source pool for the token's universe. If that pool is still Operational, migrate first asks its forker to initiate the pool fork; the call proceeds only after the source is PoolForked. That conditional transition can freeze the source pool and emit the ordinary pool-fork and snapshot events. ShareToken.migrate, SecurityPoolForker.initiateSecurityPoolFork
Canonical destinations Every destination must be a canonical direct child of the source pool. A single-target migration may lazily create a missing child while the branch-creation window is open; a multi-target migration requires every canonical child pool to exist before the call. Lazy creation can also emit child-link, REP-migration, and continuation events. ShareToken.migrate, SecurityPoolForker.createChildUniverse
Independent materialization Each selected child token id is minted up to the current source balance. A later call can select another existing child, and source shares added through an ancestor migration materialize only their previously unminted delta. ShareToken.sol
Economic denominator Child setup copies the frozen parent's remaining economic claim supply rather than its currently materialized ERC-1155 supply. Complete-set minting and redemption update that denominator; share migration does not because its claims were reserved at fork time. SecurityPoolForker.sol, SecurityPool.sol
Timing The eight-week window bounds Statoblast child-pool creation and pool-local vault/REP migration. Shares can materialize indefinitely in an already-created child. Raw Zoltar child-universe deployment and migration-REP splitting follow Zoltar's fork and balance guards, not this pool timestamp. ShareToken.sol, SecurityPoolForkerVaultMigrationBase.sol, Zoltar.sol
Malformed outcomes Malformed fork outcomes are rejected using Zoltar question-data validation. ZoltarQuestionData.sol

Escalation resolution and deposits

This section specifies accepted deposits, edge-case resolution results, and the consumption of carry proofs or residual REP.

Area Implementation behavior Source
Deposit preview The preview path expects a proposed amount of at least the current startBondAttoRep. EscalationGame.previewDepositOnOutcome
Recorded deposit amount The recorded deposit must be positive, and the accepted amount must be at least startBondAttoRep unless it exactly fills the selected outcome to nonDecisionThresholdAttoRep. EscalationGame.recordDepositFromSecurityPool, EscalationGameCalculations._getAcceptedDepositAmount
Outcome room Accepted deposit amount is capped to the selected outcome's remaining room under nonDecisionThresholdAttoRep. EscalationGameCalculations._getAcceptedDepositAmount
Tie adjustment If the accepted amount would create a tie with the current maximum balance while still below non-decision, the contract reduces the accepted amount by 1 attoREP; if that breaks the accepted-amount rule, the deposit is rejected. EscalationGameCalculations._getAcceptedDepositAmount
Unresolved resolution state If two or more outcomes meet the current running cost, getQuestionResolution() returns None. EscalationGameCalculations.getQuestionResolution
Matching-fork continuation resolution After the continuation deadline, a game with a fixed child outcome settles deposits against that outcome. Payout settlement rejects any pool/game outcome mismatch. See Matching-question child outcome for the pool-level finality rule. EscalationGameCalculations.getQuestionResolution, EscalationGameSettlement._getPayoutQuestionResolution, SecurityPoolForker.getQuestionOutcome
Empty-game fallback If all outcome balances are zero after the running cost is non-zero, getQuestionResolution() returns Invalid; if the running cost is still zero, the unresolved check returns None first. EscalationGameCalculations.getQuestionResolution
Strict leading resolution After the unresolved-cost check and the all-zero Invalid fallback, a strict Invalid, Yes, or No lead returns that outcome. Valid local deposits prevent tied maxima below non-decision by reducing the accepted amount by 1 attoREP, or reverting if that adjusted amount becomes invalid. Continuation snapshots preserve the parent balances exactly, including ties, so every selected branch starts from the same unresolved game state. EscalationGameCalculations.getQuestionResolution, EscalationGameCalculations._getStrictLeaderOrNone, EscalationGameCalculations._getAcceptedDepositAmount, EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances
Structural non-decision predicate hasReachedNonDecision() becomes true when two or more outcomes reach nonDecisionThresholdAttoRep; nonDecisionState separately records how that balance condition entered the lifecycle. New games use ⌈forkThresholdAttoRep / 2⌉, so two threshold balances always contain at least the REP required to fund an own fork even when the fork threshold is odd. Zoltar.getNonDecisionThresholdAttoRep, EscalationGameCalculations.hasReachedNonDecision
nonDecisionState = None No explicit non-decision state is active. Deposits may remain available subject to the ordinary activation, continuation, timing, and amount guards. EscalationGame.previewDepositOnOutcome, EscalationGameDepositDelegate.recordDepositFromSecurityPool
nonDecisionState = Local A local deposit brought a second outcome to the threshold. The game stores the real nonDecisionTimestamp, closes further deposits, and canTriggerOwnFork() returns true. That predicate is game-local: a pool with an inherited fixed outcome still rejects the fork transition in activateForkMode(). EscalationGameDepositDelegate.recordDepositFromSecurityPool, EscalationGameCalculations.canTriggerOwnFork, SecurityPool.activateForkMode
nonDecisionState = InheritedThresholdTie Snapshot initialization preserved two or more threshold-full balances without fabricating a local timestamp. Deposits stay closed while this state persists. With a fixed child outcome, the game follows the continuation clock and cannot trigger its own fork. Otherwise, an auction haircut that removes the threshold tie clears this state and emits InheritedThresholdTieReopened: ordinary pool-mediated deposit checks apply after the pool resumes the game, fork eligibility clears, and the clock rebases to the reduced binding capital with a fresh response period on resume. An unfixed tie that remains threshold-full retains fork eligibility; a genuine local non-decision is unchanged. EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances, EscalationGameCalculations.getEscalationGameEndDate, EscalationGameCalculations.canTriggerOwnFork, EscalationGameDepositDelegate.applyTruthAuctionHaircut
Carry proofs Inherited carry uses Merkle Mountain Range peaks and nullifier roots so child games can consume proofs without replaying already-spent parent deposits. EscalationGameSettlement.withdrawDeposit, EscalationGameCarry._verifyAndConsumeCarriedDepositProof
Continuation withdrawal Anyone may relay a batch of winning carried proofs for one depositor after a child continuation resolves. A proof authenticates the original deposit and consumes its stable index once; the committed depositor remains the payout address. Each ancestor checkpoint maps the two cumulative endpoints of a deposit through its exact auction ratio; their difference determines retained principal consumption. Payout calculation also uses the separate reward interval, binding capital, winning balance, and fork-threshold scaling. Exported principal intervals remove consumed prefixes before a later haircut. Reward intervals keep their original positions so prior direct claims cannot grant later deposits a second reward. Within one game, allocations stay fixed and the retained total falls by the exact allocated amount, so claim order cannot change payouts or strand a rounding remainder. Each endpoint loses less than one attoREP to flooring per haircut; adjacent endpoints telescope to the aggregate retained total. Allocation reads traverse the source-game lineage, so their gas cost grows with fork depth. Local deposits start after their own game’s haircut. Inherited losing outcomes retire in constant-size work when the result is final and require no proof transaction; locally created losing deposits retain ordinary settlement. Child initialization fixes the carry root/count and source-game link and performs no claim or owner import. The 64 Merkle Mountain Range (MMR) peaks are a logarithmic frontier, not a participant cap. Continuation resumes in one bounded call once aggregate backing is complete, and liquidation never processes or moves claims. SecurityPool.withdrawForkedEscalationDeposits, EscalationGameSettlement.withdrawDeposit, EscalationGameDepositDelegate.consumeCarriedDeposit
Optional unresolved parent escalation-deposit accounting cleanup The public SecurityPoolForker.migrateVaultWithUnresolvedEscalation wrapper first runs ordinary migration for that same vault: the parent REP backing-unit claim is converted to REP and credited as child-local backing units, capacity ownership moves to the child, claimable fees are checkpointed and retained in the parent vault, and proportional settlement collateral routes separately at pool level. Its fixed-size escalation cleanup then exports one Invalid/Yes/No principal tuple without another token transfer. The cleanup preserves proof leaves and neither funds dispute-staked REP backing nor authorizes inherited claims. SecurityPoolForker.migrateVaultWithUnresolvedEscalation, EscalationGameEscrow.exportVaultUnresolvedTotalsWithoutTransfer, EscalationGameForker.migrateVaultWithUnresolvedEscalation
Residual sweep Once a game is final and unresolved principal is cleared, its residual can be settled. A continuation sweep first retires its inherited aggregate carry; after that, no local dispute stake or vault escrow may remain. An ordinary game's residual returns to the security pool. A fork continuation burns its entire residual, which can include inherited capital that must not accrue to child-local owners. This rule prevents capture by a late depositor and stranding in an ownerless fixed child. EscalationGameSettlement.sweepResidualRepToSecurityPool

Fork migration

This section specifies pool-level migration after a universe fork: proxies, child-pool creation, pool-proxy REP splitting, and child outcome selection.

Area Implementation behavior Source
Pool-specific migration identity The forker lazily deploys one deterministic SecurityPoolMigrationProxy per parent pool. The proxy is the stable msg.sender for Zoltar migration accounting. REP sent to its predictable address before deployment remains isolated surplus: fork accounting uses newly routed REP and the Zoltar migration ledger, not the proxy’s preexisting ERC-20 balance. SecurityPoolForker.sol, SecurityPoolMigrationProxy.sol
Proxy authority The migration proxy is owner-controlled by the forker and wraps lockRep, forkUniverse, splitToChild, and child REP sweeping. SecurityPoolMigrationProxy.sol
Child REP backing During vault migration, the forker ensures the child pool is backed by enough child-universe REP for cumulative migrated REP credited to that child. SecurityPoolForkerVaultMigrationBase.sol
Vault fees during migration Migration uses one fixed, fee-exclusive snapshot and a cumulative REP ceiling. The parent vault is checkpointed before its capacity ownership is cleared, so earned fees remain redeemable, and parent-to-child ETH transfers cannot consume the balance reserved by totalAccruedFeesAttoEth(). The invariant catalog specifies BAL-07 fee-reserve protection, FORK-09 cumulative child collateral, and FORK-08 continuation backing. SecurityPoolForker.sol, SecurityPoolForkerVaultMigrationBase.sol, SecurityPool.sol
Split shortfall tracking The forker tracks how much REP has already been split for each parent-pool/outcome pair and only splits the shortfall before sweeping child REP into the child pool. SecurityPoolForkerVaultMigrationBase.sol
Canonical continuation snapshot Fork initialization stores the complete parent Invalid/Yes/No balances, carry totals, peaks, leaf counts, and nullifier roots once. Every lazily created child reads that same stored snapshot, even if an allowed parent claim occurs before a later child is created. SecurityPoolForker._snapshotEscalationAtFork, SecurityPoolForkerBase._initializeChildForkedEscalationGameIfNeeded, EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances
Aggregate escalation backing An external unrelated fork reproduces the drained game's dispute-staked REP one-for-one in each selected child. The escalation's own fork transfers post-haircut aggregate backing instead; ordinary pool-held REP remains one-to-one. Each selected child receives its applicable aggregate backing once, and resumeFromFork remains paused until that backing is present after accounting for child REP already exported by valid direct pre-resume claims. See FORK-08 for the raw and effective principal definitions and exact funding bound. Neither path scales or releases per-vault child escrow. SecurityPoolForker.initiateSecurityPoolFork, SecurityPoolForker.forkZoltarWithOwnEscalationGame, SecurityPoolForkerVaultMigrationBase._ensureChildEscalationBacking, EscalationGame.resumeFromFork
Direct-claim replay protection A successful direct own-fork claim records both the stable parent deposit identity and cumulative claimed principal by outcome. Every current, late, or recursively inherited child rejects a second payout; effective inherited principal subtracts immediate-parent direct claims so those leaves cannot strand the residual sweep. EscalationGameForker.claimForkedEscalationDeposits, SecurityPoolForker.isEscalationDepositClaimedDirectly, EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep
Optional vault cleanup See Optional unresolved parent escalation-deposit accounting cleanup for wrapper ordering and proof independence. In migration terms, the call records cleanup for one selected child and never processes another vault. SecurityPoolForker.migrateVaultWithUnresolvedEscalation, EscalationGameForker.migrateVaultWithUnresolvedEscalation
Independent continuation liveness Child creation installs the canonical carry commitment, source-game link, and aggregate backing without waiting for vault transactions or copying claims and owners. The fixed 64-peak MMR frontier is logarithmic commitment storage for up to 2^64 - 1 leaves, not a participant cap; see Merkle Mountain Range carry-proof peaks. Once the child is operational and fully funded, resumeForkedEscalationGame resumes it in one bounded permissionless call. Authenticated winning proofs can then be relayed permissionlessly to pay their committed depositors, inherited losers retire without proofs, and optional parent cleanup may happen independently. SecurityPoolForkerBase._finalizeAwaitingForkContinuationIfReady, EscalationGameSettlement.withdrawDeposit, EscalationGameCarry._getEffectiveInheritedUnresolvedTotalAttoRep
Own-fork REP buckets When escalation triggers its own fork, escalationChildRepAtForkAttoRep equals disputeStakedRepToForkAttoRep - ⌊forkThresholdAttoRep / forkBurnDivisor⌋. vaultRepAtForkAttoRep preserves ordinary pool-held REP one-for-one. Creating one selected child does not reduce the post-haircut escalation backing available to another selected child. SecurityPoolForker.forkZoltarWithOwnEscalationGame, SecurityPoolForker.getOwnForkRepBuckets, SecurityPoolForkerBase._initializeOwnForkRepBuckets
Child-pool deployment window Child pools are created lazily for selected fork outcomes, but only while the parent pool is PoolForked and the eight-week migration window is still open. SecurityPoolForkerVaultMigrationBase.sol, SecurityPoolUtils.sol
Matching-question child outcome When the parent universe forks on the pool's question, the child stores its selected branch as a fixed result. depositToEscalationGame rejects new local deposits, and activateForkMode rejects every later pool fork transition. See Child Outcome Resolution for the collateral and REP-liveness rationale. SecurityPool.sol, SecurityPoolForkerVaultMigrationBase.sol, SecurityPoolForker.sol
Unrelated-fork child outcome A child created by an unrelated fork without an inherited fixed result uses a local escalation result only if that escalation ended before the universe forked; otherwise continuation or later state must produce the outcome. SecurityPoolForker.sol

Truth Auction operations

Collateral-repair auctions have three operator-critical boundaries: forker ownership, a one-week bidding window, and paged settlement into vault accounting. Bids close at auctionStarted + AUCTION_TIME; direct auction finalization is allowed at >= that boundary, but the public forker wrapper requires the boundary to have passed. The canonical clearing rules and examples are in Truth Auction.

Area Implementation behavior Source
Owner Child-pool Truth Auctions are owned by SecurityPoolForker. The direct auction startAuction and finalize calls are owner-only, while anyone can reach them through startTruthAuction and finalizeTruthAuction. UniformPriceDualCapBatchAuction.sol, SecurityPoolForker.sol
Finalized settlement After finalization, only the auction owner withdraws bid outcomes from the auction; SecurityPoolForker wraps that call so anyone can settle a vault's bid pages. UniformPriceDualCapBatchAuction.sol, SecurityPoolForker.sol
Child fee activation Completed migration or truth-auction settlement starts a new child fee epoch. Migrated ownership and sold truth-auction ownership enter the fee denominator at finalization. Each later auction claim assigns the bidder vault its ownership and fees accrued from the saved finalization index through the current index without changing the pool’s live eligible total; intervening capacity changes and liquidations remain preserved. SecurityPool.sol, SecurityPoolForker.sol

REP/ETH oracle operations

This section covers coordinator staging, callback recovery, stale-operation handling, and liquidation boundaries. Report sizing, request cost, and current OpenOracle parameters are canonical in the OpenOracle integration parameters.

Area Implementation behavior Source
Atomic initial report The sponsor commits an explicit ETH bounty of at least the dynamic request cost and funds the WETH/REP position; the coordinator retains the whole bounty and submits that position atomically as the initial reporter. A caller-selected WETH amount above the dynamic minimum is allowed. See OpenOracle sizing and funding for derivation, reporter withdrawals, application buffering, and escalation effects. OpenOraclePriceCoordinator.sol, openOracle.ts
Immediate execution If a price is still valid, the operation executes immediately and positive unused ETH is refunded. A contract caller must accept the refund callback or the execution rolls back; the canonical refund warning also covers newly opened reports. OpenOraclePriceCoordinator.sol
Fresh-price request guard requestPrice reverts while the cached coordinator price is still valid, so callers cannot open a redundant pending report on top of a fresh cache. OpenOraclePriceCoordinator.sol
Staging guardrails Withdrawal and liquidation amounts must be non-zero. Validity must be positive and no more than five minutes. Withdrawals target the operator's own vault. Liquidation uses explicit operator, receiver, and target roles; receiver and target must differ. A delegated receiver requires a valid bounded approval and queue-time reservation, while the self-receiving operator path requires no signature. OpenOraclePriceCoordinator.sol, OpenOraclePriceCoordinatorTypes.sol
Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and Ethereum Improvement Proposal 712 (EIP-712) permits from externally owned accounts (EOAs) or ERC-1271 contract wallets. An approval binds pool, receiver, exact operator, exact or wildcard target, cumulative and per-operation ETH debt limits, minimum post-health factor, validity window, and nonce. Revocation blocks new reservations without disturbing pending ones; nonce invalidation blocks stale approvals. LiquidationApprovalRegistry.sol, openOracle.ts
Approval reservation lifecycle Staging reserves at most requested debt, snapshotted target debt, per-operation allowance, and available cumulative allowance. Success consumes exactly moved debt and releases the remainder; bad-debt-only execution consumes zero. Every terminal failure and permissionless expiration releases once. Approval validity must cover the latest legal execution time. OpenOraclePriceCoordinator.sol, LiquidationApprovalRegistry.sol
Pending report bound At most four operations are attached to one settlement callback. Operations can still be tracked as active even when they do not fit into the pending callback batch. OpenOraclePriceCoordinator.sol
Sponsor exclusivity Once the cached price is stale and a caller funds a fresh coordinator report, only that pendingReportSponsor can append more staged operations until settlement. Valid disputes reset the settlement clock, so this paid exclusive lane can be extended indefinitely; the coordinator makes no bounded-availability guarantee. The economic tradeoffs, attack model, and parameter table own dispute funding, escalation, and rounding details. OpenOraclePriceCoordinator.sol, OpenOracle.sol
High settlement basefee The callback clears the pending report and terminally fails every operation attached to that report if settlement basefee is above the stored maximum from request time. Each delegated-liquidation reservation is released. OpenOraclePriceCoordinator.sol
Uneconomic or saturated final report Coordinator reports always enable dispute history. The callback reads storedGame(reportId).numReports. It rejects a saturated uint24 counter with Counter saturated; otherwise it requires the final history record's WETH amount to cover the configured dispute-security formula at its recorded base fee plus the configured priority fee and rejects it with Report uneconomic. OpenOraclePriceCoordinator.sol, OpenOracle.sol
Late settlement OpenOracle allows settlement at any time after settlement eligibility. The coordinator measures freshness from settlement eligibility, the final storedGame(reportId).reportTimestamp plus settlementTime, and rejects a report settled after its freshness window has closed with Report stale. The rejection clears the pending report so a new price can be requested. OpenOraclePriceCoordinator.sol, OpenOracle.sol
Zero report values A callback with zero amount, zero denominator, or a computed zero price does not update lastPrice. OpenOraclePriceCoordinator.sol
Recovery path Coordinator reports opt into OpenOracle's STORE_ALL and TRACK_DISPUTES flags. Recovery requires a pending report whose stored OpenOracle settlement timestamp is nonzero. It returns withdrawable reporter balances to the sponsor, clears the report, consumes all associated pending operations, and releases their liquidation reservations. An expired operation can also be cleaned permissionlessly before any valid-price requirement. OpenOraclePriceCoordinator.sol
Consumed failures Expired operations, stale liquidations, zero-effect withdrawals, and liquidations too close to threshold are consumed and emitted as failed executions rather than retried forever. OpenOraclePriceCoordinator.sol
Liquidation snapshot A staged liquidation becomes stale if target REP backing units or capacity ownership changes. The queue-time open-interest snapshot bounds the reservation and records context but is not itself a staleness key. Execution re-evaluates target and receiver health from live balances, live obligations, and the settled price. ETH debt moves only up to the amount whose complete 5% REP award is funded; REP-denominated capacity ownership moves proportionally. An unsuitable below-minimum receiver reverts instead of creating avoidable bad debt. See Queued Execution. OpenOraclePriceCoordinator.sol, SecurityPool.sol
Liquidation distance A staged liquidation must remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold when it executes. OpenOraclePriceCoordinator.sol, SecurityPoolOperationsDelegate.sol