diff --git a/docs/assets/css/docsShell.css b/docs/assets/css/docsShell.css index 94602e0d0..9a1aa0f85 100644 --- a/docs/assets/css/docsShell.css +++ b/docs/assets/css/docsShell.css @@ -269,6 +269,7 @@ body.docs-shell-page:has(.docs-search[open]) { color: var(--docs-shell-muted); font-size: 0.86rem; line-height: 1.35; + overflow-wrap: anywhere; text-decoration: none; } diff --git a/docs/assets/js/docsSearchData.js b/docs/assets/js/docsSearchData.js index 9659a393d..4e5bc39eb 100644 --- a/docs/assets/js/docsSearchData.js +++ b/docs/assets/js/docsSearchData.js @@ -1,2 +1,2 @@ // Generated by scripts/build-docs-index.mts. Loaded on demand; do not edit directly. -window.statoblastDocsSearch = [{"fragment":"","heading":"","keywords":["ABI","transactions","callers","events"],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"For developers and operators: find the contract that handles each state change, who can call it, and what the call does. Quick index ZoltarQuestionData Zoltar ReputationToken SecurityPoolFactory SecurityPool SecurityPoolForker EscalationGame LiquidationApprovalRegistry OpenOraclePriceCoordinator ShareToken UniformPriceDualCapBatchAuction","title":"Contract interactions","topic":"Contracts","weight":1},{"fragment":"zoltarquestiondata","heading":"ZoltarQuestionData","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata. Source Read surface: Use getQuestionId before submission; questionCreatedTimestamp and questions for direct lookup; getQuestionCount and getQuestions for indexed or paged discovery; and getQuestionEndDate , getOutcomeLabels , splitUint256IntoTwoWithInvalid , hasNonZeroScalarReservedBits , isMalformedAnswerOption , and getAnswerOptionName when validating or displaying answers. In the QuestionData tuple, startTime and endTime are uint48 , while numTicks is uint120 ; clients must use these exact widths because they determine the getQuestionId and createQuestion selectors. Transaction Caller Main prerequisites State or asset effect Primary signals createQuestion(questionData, outcomeOptions) Anyone Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose keccak256(abi.encode(label)) values are strictly descending. Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied. QuestionCreated","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"zoltar","heading":"Zoltar","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP. Source Read surface: Use universes , forkThresholdDivisor , forkBurnDivisor , zoltarQuestionData , genesisReputationToken , getForkTime , forkQuestionMatches , getRepToken , getForkThresholdAttoRep , getNonDecisionThresholdAttoRep , getUniverseTheoreticalSupplyAttoRep , getChildUniverseId , getDeployedChildUniverses , and getMigrationRepBalanceAttoRep to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and forkBurnDivisor >= 5 , which caps the uncredited fork haircut at 20% of the threshold. Security boundaries for these calls are A15 intended question selection and A25 safe immutable parameters . Transaction Caller Main prerequisites State or asset effect Primary signals forkUniverse(universeId, questionId) Any address able to fund the current fork threshold Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut. UniverseForked burnRep(universeId, amountAttoRep) Any REP holder; the caller can burn only its own balance Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance. Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork. RepBurned and the token burn or transfer event deployChild(universeId, outcomeIndex) Anyone Parent forked; outcome is well formed; child is not already deployed. Deploys the deterministic child REP token and initializes the child universe. DeployChild addRepToMigrationBalance(universeId, amountAttoRep) Parent REP holder Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Burns or sinks additional parent REP and increases the caller's reusable migration balance. MigrationRepAdded splitMigrationRep(universeId, amountAttoRep, outcomeIndexes) Migration-balance holder Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance. Mints amount of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch. TheoreticalSupplySet and DeployChild when needed; child REP Transfer and Mint , then MigrationRepSplit , per selected branch, including at zero amount; no event for an empty list","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"reputationtoken","heading":"ReputationToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar. Source Read surface: Use getTotalTheoreticalSupplyAttoRep , zoltar , and the standard ERC-20 name , symbol , decimals , totalSupply , balanceOf , and allowance reads. Transaction Caller Main prerequisites State or asset effect Primary signals setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep) Zoltar only Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP. Sets the child token theoretical-supply ceiling used to bound subsequent migration mints. TheoreticalSupplySet mint(account, valueAttoRep) Zoltar only account is nonzero; resulting ERC-20 supply does not exceed theoretical supply. Mints branch REP to an account. Mint and ERC-20 Transfer burn(account, valueAttoRep) Zoltar only account is nonzero and has sufficient REP; theoretical supply covers the burn. Burns account REP and reduces both actual and theoretical supply by the same amount. Burn and ERC-20 Transfer transfer(to, value) REP holder Destination is nonzero; caller has sufficient balance. Moves REP from the caller without changing actual or theoretical supply. Transfer approve(spender, value) Any REP account setting its own allowance Spender is nonzero. Replaces the named spender allowance without moving REP. Approval transferFrom(from, to, value) A spender with sufficient allowance from from Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source. Moves REP from from ; a finite allowance decreases by value , while an infinite allowance remains unchanged. Neither allowance path emits Approval . Transfer only","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolfactory","heading":"SecurityPoolFactory","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction. Source Read surface: Use initialEscalationGameDepositAttoRep , minimumSecurityBondDebtAttoEth , and minimumVaultRepDepositAttoRep for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly max(1 REP, theoretical REP supply / 10,000,000) . A zero configured vault REP floor selects the default theoretical REP supply / 100,000 ; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use securityPoolDeploymentCount with the strict securityPoolDeploymentsRange(startIndex, count) pager, which reverts rather than truncating when the requested range exceeds the array. Use getOriginId , getPoolId , getSecurityPool , getSecurityPoolOriginId , and getSecurityPoolHasInheritedForkOutcome for canonical lookup. Transaction Caller Main prerequisites State or asset effect Primary signals deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas) Anyone statoblastSecurityMultiplierBps > 10_001 , which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the liquidation design . initialReportPriorityFeeAttoEthPerGas > 0 and remains within the coordinator-computed OpenOracle uint128 report/escalation-halt capacity bound; question exists and has exactly the categorical labels Yes , then No ; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit max(1 REP, theoretical REP supply / 10,000,000) ; the origin/universe/priority-fee slot has not already been claimed. Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically. SecurityPoolRegistered , then DeploySecurityPool deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth) SecurityPoolForker only Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring. Creates and registers a canonical child pool with a coordinator that inherits initialReportPriorityFeeAttoEthPerGas from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token. SecurityPoolRegistered , then DeploySecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypool","heading":"SecurityPool","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation. Source Read surface: Immutable relationship and configuration getters are questionId , universeId , initialEscalationGameDepositAttoRep , zoltar , parent , shareToken , repToken , priceOracleManagerAndOperatorQueuer , openOracle , escalationGameFactory , questionData , securityPoolForker , truthAuction , securityPoolFactory , and statoblastSecurityMultiplierBps ; the current game is escalationGame . Accounting getters include totalCapacityOwnershipAttoRep , settlementCollateralAttoEth , totalRepBackingUnits , shareTokenSupplyAttoShares , securityVaults , minimumSecurityBondDebtAttoEth , minimumVaultRepDepositAttoRep , vaultTargetHealthFactorBps , totalBadDebtAttoEth , and vaultBadDebtAttoEth . Use getCurrentMintingCapacityAttoEth for price-converted aggregate capacity and getVaultOpenInterestAttoEth for a vault’s live proportional obligation. Other derived and paged reads are getVaultCount , getVaults , attoSharesToAttoEth , attoEthToAttoShares , attoRepToBackingUnits , backingUnitsToAttoRep , getTotalPoolHeldAttoRep , totalAccruedFeesAttoEth , getPoolAccountingSnapshot , getVaultFeeRemainder , and isEscalationResolved . The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from securityVaults , escalation stake, and bad debt. isEscalationResolved() is true only when a local escalation game is configured and the forker routes a non- None outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are totalClaimableVaultFeesAttoEth , lastUpdatedFeeAccumulator , feeIndex , currentRetentionRate , awaitingForkContinuation , and systemState . Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on A16 timely inclusion , A21 genesis REP and WETH behavior , A19 observable correctable price , and A06 lifecycle executors . User-initiated pool calls additionally depend on A28 account authority . Transaction Caller Main prerequisites State or asset effect Primary signals burnEscalationWinnerHaircut(amountAttoRep) This pool's EscalationGame only Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool. Burns the winning-deposit haircut from REP already escrowed in the game. RepBurned and ERC-20 Transfer ; child REP also emits Burn depositRepToVault(attoRepAmount, targetHealthFactorBps) Vault owner Operational and unforked; isEscalationResolved() is false; target health factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum. Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from the deposit and selected target health factor. RepDepositedToVault , the vault target-health-factor event, and accounting checkpoints redeemFees(vault) Anyone; any nonzero ETH payment is always sent to vault A nonzero payment path requires vault to accept ETH. First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount. Accrual checkpoints only when accrual state changes; both VaultAccountingCheckpoint and PoolAccountingCheckpoint for a nonzero redemption; no event when fees and accrual state are unchanged createCompleteSet() with ETH Trader Operational and unforked; isEscalationResolved() is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; under A22 asset-recipient compatibility , a contract trader accepts onERC1155BatchReceived . Adds collateral and mints one Invalid , Yes , and No share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint. CompleteSetCreated , PoolAccountingCheckpoint , then ERC-1155 TransferBatch on a successful callback redeemCompleteSet(amountAttoShares) Anyone; positive redemption requires the caller to hold the complete set Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance. Burns equal balances of all three outcomes and pays amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. CompleteSetRedeemed and PoolAccountingCheckpoint redeemShares() Anyone; a positive payout requires the caller to hold winning shares Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value. Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. SharesRedeemed and PoolAccountingCheckpoint redeemRepFromVault(vault) Anyone; REP is always sent to vault Operational pool with a final outcome; the specified vault has no escalation escrow and has redeemable REP. Burns the vault's REP backing units and returns its proportional vault REP backing. RepRedeemedFromVault depositToEscalationGame(outcome, maxAmount) Vault owner Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero. Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses nonDecisionThresholdAttoRep - 1 instead. Repeat deposits use the existing game's stored startBondAttoRep and nonDecisionThresholdAttoRep . Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome. EscalationGameSet on first deposit; DepositToEscalationGame withdrawFromEscalationGame(outcome, depositIndexes) Anyone; a nonempty list must select deposits belonging to one original depositor Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor. A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event. Per processed deposit, escalation-game CarryDepositConsumed ; additionally ClaimDeposit for a winning payout. No event for an empty list withdrawForkedEscalationDeposits(outcome, proofs) Anyone; a nonempty list must name one original depositor across all proofs Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor. A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event. Per processed proof, escalation-game CarryDepositConsumed and ClaimDeposit . No event for an empty list updateSettlementCollateral() Anyone No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp. Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees. PoolAccountingCheckpoint whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp updateRetentionRate() Anyone No caller restriction. It returns unchanged when the pool is not Operational or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate. Recalculates the retention rate from current collateral and live oracle-priced minting capacity. PoolAccountingCheckpoint only when the stored retention rate changes; no event for a no-op updateVaultFees(vault) Anyone for any address No caller, nonzero-vault, or lifecycle restriction. First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership. Accrual PoolAccountingCheckpoint when due; VaultAccountingCheckpoint when the vault index, remainder, or claimable fee balance changes; an additional PoolAccountingCheckpoint when pool accounting changes; no event when neither accrual nor vault or pool accounting changes withdrawRepFromVault(vault, attoRepAmount) This pool's OpenOraclePriceCoordinator only Fresh coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy. Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to vault . VaultTargetHealthFactorSet ; REP Transfer ; RepWithdrawnFromVault ; VaultAccountingCheckpoint ; and applicable fee-accrual or retention PoolAccountingCheckpoint events, including a zero-value transfer/event path if the trusted coordinator supplies zero performLiquidation(operationId, operator, receiverVault, targetVault, requestedDebtAttoEth, snapshotTargetBackingUnits, snapshotTargetCapacityOwnershipAttoRep, snapshotTotalPoolHeldAttoRep, snapshotTotalRepBackingUnits, minimumReceiverHealthFactorBps, minLiquidationPriceDistanceBps) This pool's OpenOraclePriceCoordinator only Fresh settled coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; receiver differs from target. Target snapshots must match. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor. Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt. Fee-accrual and target or receiver VaultAccountingCheckpoint events as needed; VaultLiquidated identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; VaultBadDebtRecorded records residual target debt on a full-target request; final pool accounting checkpoint setStartingParams(...) SecurityPoolFactory only Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring. Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization. Coordinator RepEthPriceSet and CoordinatorStateCheckpoint , then pool PoolAccountingCheckpoint , even for zero or repeated values if the factory were to call again activateForkMode() SecurityPoolForker only The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data. Sets PoolForked , accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints. Pool-held REP Transfer always, including at zero; configured-game REP Transfer only for a positive game balance; accrual checkpoint when due; always PoolForkModeActivated and fork-activation PoolAccountingCheckpoint initializeForkedEscalationGame(...) SecurityPoolForker only No game is configured; downstream startFromFork parameters are valid. Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome. Escalation GameContinuedFromFork , then pool EscalationGameSet initializeForkCarrySnapshotWithResolutionBalances(...) SecurityPoolForker only A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data. Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots. ForkCarryCheckpoint resumeForkedEscalationGame() Anyone Pool is operational, awaiting a configured fork continuation, and the game has not resumed. Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call. ForkContinuationResumed and AwaitingForkContinuationSet(false) setAwaitingForkContinuation(shouldAwait) SecurityPoolForker only No lifecycle or value-change guard. Stores whether complete-set minting must wait for continuation initialization. AwaitingForkContinuationSet , including for a repeated value setSystemState(newState) SecurityPoolForker only No transition or value-change guard. Replaces the pool lifecycle state directly. SystemStateSet , including for a repeated state configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, targetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; no lifecycle or value-change guard. Replaces the vault REP backing units, price-independent capacity ownership, fee index, target health factor, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state setTotalRepBackingUnits(newDenominator) SecurityPoolForker only No lifecycle or value-change guard. Replaces the REP backing units denominator. TotalRepBackingUnitsSet , including for zero or a repeated value setTotalSharesAttoShares(newTotalSharesAttoShares) SecurityPoolForker only No lifecycle or value-change guard. Replaces stored shareTokenSupplyAttoShares , the denominator used by attoSharesToAttoEth and complete-set redemption. ShareTokenSupplySet , including for zero or a repeated value setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth) SecurityPoolForker only Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard. Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry. PoolAccountingCheckpoint , including for repeated financial values authorizeChildPool(pool) SecurityPoolForker only This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard. Asks the lineage share token to establish pool as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op. AuthorizationUpdated only on first authorization; no event when already authorized transferEth(receiver, amountAttoEth) SecurityPoolForker only Fee liabilities are covered; amount fits both unreserved pool ETH and tracked settlement collateral; receiver accepts the ETH call, including zero value. Reduces tracked settlement collateral by amount , checkpoints the reconciliation, and calls receiver with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint. PoolAccountingCheckpoint , including at zero amount; no dedicated ETH-transfer event addFeeEligibleCapacityOwnershipAttoRep(vault, amountAttoRep) SecurityPoolForker only The resulting fee-eligible capacity ownership cannot exceed total capacity ownership; no lifecycle, vault, positive-amount, or value-change guard. Adds newly auction-claimed capacity ownership to the live fee denominator, clears the pooled fee-index rounding remainder, then checkpoints elapsed fees and recalculates retention from collateral and unchanged total capacity ownership. The assignment itself does not change live minting capacity. Retention-rate PoolAccountingCheckpoint first when the rate changes, then VaultAccountingCheckpoint and auction-claim PoolAccountingCheckpoint , including the latter two at zero amount; the calling forker emits ClaimAuctionProceeds only after the broader credit workflow completes Direct ETH transfer to receive() Forker, this pool's truth auction, or parent pool only Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard. Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees. No dedicated receive event; the calling protocol step emits its own event","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolforker","heading":"SecurityPoolForker","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions. Source Read surface: Use zoltar , forkData , getMigratedAttoRep , getForkActivationTime , isEscalationDepositClaimedDirectly , getEscalationDepositId , getDirectlyClaimedEscalationPrincipal , isEscalationWinnerHaircutPaidByFork , getEscalationMigrationEntitlementStatus , getOwnForkRepBuckets , getOwnForkMigrationStatus , getMigrationProxyAddress , getQuestionOutcome , attoRepToBackingUnits , and backingUnitsToAttoRep to reconstruct fork progress and preview migration conversions.","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"child-game-trust-boundary","heading":"Child-game trust boundary","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured SecurityPoolFactory registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from securityPool() when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed EscalationGame instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do. Transaction Caller Main prerequisites State or asset effect Primary signals initiateSecurityPoolFork(securityPool) Anyone Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from securityPool() when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the child-game trust boundary . Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot and ParentRepLocked ; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists forkZoltarWithOwnEscalationGame(securityPool) Anyone Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from securityPool() when validated and canTriggerOwnFork() is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the child-game trust boundary . Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot , ParentRepLocked , and Zoltar fork events; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists migrateRepToZoltar(securityPool, outcomeIndices) Anyone Migration proxy exists and the pool is PoolForked . Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child ForkMigration state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied. For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events. MigrationRepSplit and ChildRepSplit when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch createChildUniverse(securityPool, outcomeIndex) Anyone Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the child-game trust boundary . These relationship checks do not independently prove configured-factory registration. Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game. DeployChild only when child REP was absent; always SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , and TotalRepBackingUnitsSet ; AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as continuation and backing state requires migrateVault(securityPool, outcomeIndex) Vault owner for their non-escrowed position Migration window open; the selected child's reported nonzero escalation game passes the child-game trust boundary . The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state. Transfers the caller's REP backing units, REP-denominated capacity ownership, target health factor, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move. VaultBadDebtMigrated and VaultMigrationCheckpoint migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex) The named vault owner Migration window open; caller equals vault ; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the child-game trust boundary . First runs ordinary migration for the same vault, which may transfer REP backing units, capacity ownership, target health factor, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs. Vault migration events, including VaultBadDebtMigrated , plus EscalationMigrationEntitlementInitialized on first export and EscalationMigrationEntitlementMaterialized for the selected child claimForkedEscalationDeposits(...) The named vault owner Caller equals vault ; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies canTriggerOwnFork() by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in ForkMigration , has a continuation game that passes the child-game trust boundary , and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit vault as its immutable depositor. First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary. DeployChild , SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , TotalRepBackingUnitsSet , AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as setup requires; per claimed deposit, CarryDepositConsumed and ClaimDeposit ; escrow record/export events when REP is paid; always ClaimForkedEscalationDepositsToWallet , including for an empty list startTruthAuction(securityPool) Anyone Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the child-game trust boundary . Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction. ShareTokenSupplySet and TruthAuctionStarted ; immediate no-auction completion also emits TruthAuctionFinalized , pool accounting checkpoints, and ForkContinuationResumed for an unresolved continuation finalizeTruthAuction(securityPool) Anyone Truth auction started, its one-week window has passed, msg.value is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the child-game trust boundary . Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, activates the child at that settlement-collateral level, and fixes bidder REP-backing-unit and capacity-ownership rates. A nonzero repair contribution is rejected. TruthAuctionFinalized , auction AuctionFinalized , and pool accounting checkpoints; TruthAuctionHaircutApplied when purchased REP removes a positive escalation allocation; ForkContinuationResumed for an unresolved continuation settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices) Anyone on behalf of the named bidder vault At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled. Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and credits each fixed-position REP backing and capacity-ownership result. It also assigns the bidder vault its cumulative share of auctioned bad debt: intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion. Underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited claimAuctionProceeds(securityPool, vault, tickIndices) Anyone on behalf of the named bidder vault Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled. For a nonempty list, withdraws finalized bid settlements, converts purchased REP into child REP backing units, independently credits the bid positional capacity-ownership allocation, and assigns the bidder vault its cumulative share of auctioned bad debt. Intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns three zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events. For processed bids, underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame) This SecurityPoolForker contract only, through its migration delegate callback External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the child-game trust boundary . Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use. ChildDisputeStakedRepMaterialized and escalation-continuation events when initialization is required Direct ETH transfer to receive() A child-pool truth auction trusted by this forker during ChildPoolLinked trustedAuctionAddresses[msg.sender] was set when the forker linked the child and emitted ChildPoolLinked ; configured-factory registration determines whether that lineage is canonical. Accepts auction ETH during forker-controlled auction finalization. No dedicated receive event; auction AuctionFinalized is followed by forker TruthAuctionFinalized and pool accounting checkpoints","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"escalationgame","heading":"EscalationGame","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits. Source Read surface: Base getters are securityPool , repToken , activationTime , nonDecisionThresholdAttoRep , startBondAttoRep , nonDecisionTimestamp , nonDecisionState , forkContinuation , forkElapsedAtStart , forkResumedAt , fixedQuestionOutcome , nodes , disputeStakedRepByVaultAttoRep , totalDisputeStakedAttoRep , truthAuctionRepBeforeAttoRep , truthAuctionRepRemainingAttoRep , cumulativeClaimRetention , and cumulativeClaimRetentionExponent . The claim delegate fallback exposes rootClaimSourceGame , applyInheritedClaimRetention , and applyInheritedSourceStorageBasis . The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. disputeStakedRepByVaultAttoRep is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use previewDepositOnOutcome , computeIterativeAttritionCostAttoRep , computeTimeSinceStartFromAttritionCostAttoRep , totalCostAttoRep , getEscalationGameEndDate , getQuestionResolution , getFinalQuestionResolution , hasReachedNonDecision , canTriggerOwnFork , getBindingCapitalAttoRep , getOutcomeBalancesAttoRep , getDepositsByOutcome , getDepositsByOutcomeLength , forkCarrySnapshotInitialized , getOutcomeState , getForkCarrySnapshot , getForkCarryRoots , isForkCarryFundingComplete , getCarryLeafPageByOutcome , getProofConsumedCarriedDepositIndexesByOutcome , getLocalUnresolvedPrincipalByVaultAndOutcome , and getForkedEscrowByVaultAndOutcome for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through SecurityPool . Transaction Caller Main prerequisites State or asset effect Primary signals start(startBondAttoRep, nonDecisionThresholdAttoRep) EscalationGameFactory contract during atomic deployment Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid. Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to nonDecisionThresholdAttoRep - 1 before this call. GameStarted startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep) Immutable owner ( EscalationGameFactory ) during atomic continuation deployment Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid. Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until resumeFromFork . GameContinuedFromFork resumeFromFork() Owning SecurityPool only Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , where sourcePrincipalAtForkAttoRep is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims. Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is max(rebasedCurveEnd, forkResumedAt + 3 days) , so even an exhausted inherited clock receives a fresh response period. After that deadline, getFinalQuestionResolution returns the fixed outcome when the continuation has one. ForkContinuationResumed applyTruthAuctionHaircut(repToRemove) The child pool's SecurityPoolForker only Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance. Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it. TruthAuctionHaircutApplied and REP Transfer recordDepositFromSecurityPool(...) Owning SecurityPool only Explicit non-decision state is None ; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold. Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf. LocalDepositAppended , DepositOnOutcome , optionally NonDecisionReached withdrawDeposit(uint256 depositIndex, outcome) Owning SecurityPool only Explicit non-decision state is None ; non- None supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index. Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting. CarryDepositConsumed and VaultEscrowUpdated ; for a winner, ClaimDeposit , positive REP payout Transfer , and haircut burn signals when nonzero initializeForkCarrySnapshotWithResolutionBalances(...) Owning SecurityPool only Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data. Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set nonDecisionState to InheritedThresholdTie without creating a local timestamp. ForkCarryCheckpoint ; additionally InheritedThresholdTie when the installed balances meet the non-decision threshold claimDepositForWinning(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path. Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor. CarryDepositConsumed , VaultEscrowUpdated , ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when their amounts are positive claimDepositForWinningWithoutTransfer(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non- None guard; neither form checks final resolution or that the outcome won. Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or ⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉ after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut. CarryDepositConsumed , VaultEscrowUpdated , and ClaimDeposit with transferredRep = false ; no REP transfer or haircut burn exportUnresolvedDeposit(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None outcome and a valid unsettled local deposit. Final resolution is not required. Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP. CarryDepositConsumed and VaultEscrowUpdated ; no ClaimDeposit or REP transfer withdrawDeposit(CarriedDepositProof proof, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof. Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it. CarryDepositConsumed and ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when positive exportVaultUnresolvedTotals(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to repReceiver . Always VaultUnresolvedTotalsExported , including when every amount is zero; VaultEscrowUpdated and REP Transfer only for a positive total exportVaultUnresolvedTotalsWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller. Always VaultUnresolvedTotalsExported with transferredRep = false , including when every amount is zero; VaultEscrowUpdated only for a positive total; no REP transfer drainAllRep(receiver) Owning SecurityPool only receiver is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after activateForkMode enters PoolForked . Transfers the game's full REP balance to receiver . A zero balance returns zero without a transfer or event. REP Transfer for a positive balance; no event at zero balance recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep) Owning SecurityPool or its SecurityPoolForker Outcome is not None ; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op. Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event. ForkedEscrowRecorded for a nonzero record; no event when both amounts are zero exportForkedEscrowByOutcome(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault and repReceiver are nonzero. Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event. ForkedEscrowExported when any source principal or child REP remains; REP Transfer when positive child REP is transferred; no event for an already-empty export exportForkedEscrowByOutcomeWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero. Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event. ForkedEscrowExported with transferredRep = false when any source principal or child REP remains; no REP transfer; no event for an already-empty export sweepResidualRepToSecurityPool() Anyone Final outcome; no unresolved principal; no vault escrow; positive residual balance. Returns otherwise stranded residual REP to the owning pool. ResidualRepSweptToSecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"liquidationapprovalregistry","heading":"LiquidationApprovalRegistry","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator. Source Read surface: Use coordinator to identify the validating coordinator and implied security pool. LIQUIDATION_APPROVAL_TYPEHASH , DOMAIN_SEPARATOR , and liquidationApprovalDigest define the chain- and registry-bound EIP-712 message. getLiquidationApproval reports parameters plus available, reserved, consumed, and revoked state; minimumLiquidationApprovalNonce reports receiver invalidation state; liquidationReservations and minimumHealthFactorBps expose operation reservation state and its execution-time health floor. Transaction Caller Main prerequisites State or asset effect Primary signals initialize(coordinator) Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically Coordinator is nonzero and the registry has not been initialized. Binds this registry clone to one coordinator and therefore one security pool. No event; the public coordinator getter records the binding. setLiquidationApproval(params) The receiver vault named by params Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce. Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce. LiquidationApprovalSet permitLiquidationApproval(params, signature) Anyone relaying the receiver vault signature Signature is valid for params.receiverVault ; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass. Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce. LiquidationApprovalSet revokeLiquidationApproval(approvalId) Approval receiver vault only Approval exists and is not already revoked. Prevents new reservations while leaving reservations already attached to staged operations intact. LiquidationApprovalRevoked with available, reserved, and consumed totals invalidateLiquidationApprovalNonce(newNonce) Receiver vault invalidating its own older nonce range New nonce is greater than the receiver current minimum. Raises the minimum nonce accepted for new approval installation or reservation. LiquidationApprovalNonceInvalidated reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp) Bound coordinator only Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota. Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota. LiquidationApprovalReserved release(operationId) Bound coordinator only Coordinator terminal cleanup path. Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op. LiquidationApprovalReleased when quota is returned consume(operationId, debtMovedAttoEth) Bound coordinator only For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op. Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once. LiquidationApprovalConsumed","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"openoraclepricecoordinator","heading":"OpenOraclePriceCoordinator","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup. Source Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS , OPEN_INTEREST_DIVIDER , reputationToken , securityPool , openOracle , weth , liquidationApprovalRegistry , gasConsumedOpenOracleReportPrice , gasConsumedSettlement , gasUnitsForOneDispute , initialReportPriorityFeeAttoEthPerGas , targetPriceErrorForDispute , openOracleSecurityMultiplierBps , settlementTime , disputeDelay , protocolFee , feePercentage , multiplier , timeType , trackDisputes , protocolFeeRecipient , escalationHaltMultiplierBps , maxSettlementBaseFeeMultiplierBps , and minLiquidationPriceDistanceBps . Current report and operation getters are pendingReportId , pendingReportSponsor , pendingOperationSlotId , lastSettlementTimestamp , lastPrice , pendingReportMaxSettlementBaseFeeAttoEthPerGas , stagedOperationCounter , and stagedOperations . Use isPriceValid , minimumToken1ReportAttoEth , getRequestPriceCostAttoEth , getQueuedOperationCostAttoEth , getSettlementCallbackGasLimit , getPendingOperationSlot , getActiveStagedOperationCount , getActiveStagedOperations , getPendingSettlementOperationCount , and getPendingSettlementOperationIds for derived or paged state. Report and staged-operation liveness depends on A16 timely inclusion , A17 corrector capability , A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . When lastPrice is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path. Transaction Caller Main prerequisites State or asset effect Primary signals requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...) Liquidation operator; a delegated receiver must have approved this exact operator Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution. Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID. LiquidationRouteStaged ; LiquidationApprovalReserved on a delegated route; staged-operation lifecycle events requestPriceIfNeededAndStageOperation(...) with funding when stale Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations. securityPool.isEscalationResolved() is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund. Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report. StagedOperationQueued , possibly PriceRequested , then ExecutedStagedOperation ; authoritative CoordinatorStateCheckpoint records requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding Anyone when no fresh price or report is pending Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund. Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position. PriceRequested and CoordinatorStateCheckpoint executeStagedOperation(operationId) Anyone Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried. Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds. ExecutedStagedOperation , either LiquidationApprovalConsumed or LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint expireStagedOperation(operationId) Anyone Operation exists and its settlement-plus-validity window has elapsed. Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price. ExecutedStagedOperation , LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint recoverSettledPendingReport() Anyone A pending report ID exists and its stored OpenOracle storedGame(reportId).settlementTimestamp is nonzero. Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active. PendingReportRecovered , failed ExecutedStagedOperation for each live attached operation, LiquidationApprovalReleased for each attached delegated liquidation, and CoordinatorStateCheckpoint openOracleCallback(...) Configured OpenOracle only Callback report matches the pending report; excessive settlement basefee, a saturated uint24 report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state. A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation. PriceReported or PriceReportRejected ; operation execution events; authoritative CoordinatorStateCheckpoint records setLiquidationApprovalRegistry(registry) Coordinator deployment factory only Registry is nonzero and no registry was previously installed. Binds the coordinator-local approval registry once. No event; deterministic factory deployment and the public getter identify the registry. setSecurityPool(pool) Anyone while securityPool remains zero; normal factory deployment calls atomically Current securityPool is zero; the argument itself is not required to be nonzero. A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator. SecurityPoolSet and CoordinatorStateCheckpoint setRepEthPrice(price) Configured nonzero SecurityPool only Caller equals the configured pool. Seeds the coordinator's price value, including zero, for inherited child state. RepEthPriceSet and CoordinatorStateCheckpoint","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"sharetoken","heading":"ShareToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches. Source Read surface: Base and relationship getters are name , symbol , zoltar , canonicalPoolByUniverse , _balances , _supplies , and _operatorApprovals . Standard ERC-1155 reads are supportsInterface , balanceOf , totalSupply , balanceOfBatch , and isApprovedForAll ; protocol-specific reads are isAuthorized , totalSupplyForOutcome , maximumOutcomeSupply , balanceOfOutcome , balanceOfShares , getMigratedShareAmountAttoShares , getTokenId , getTokenIds , and unpackTokenId . Transaction Caller Main prerequisites State or asset effect Primary signals setApprovalForAll(operator, approved) Any token account setting its own operator approval The operator differs from the caller. Sets or clears the operator's authority over all of the caller's outcome-token balances. ApprovalForAll Both safeTransferFrom(...) overloads Share holder or approved ERC-1155 operator Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under A22 asset-recipient compatibility , a contract recipient accepts the ERC-1155 callback. Transfers one outcome-token balance without changing supply. TransferSingle Both safeBatchTransferFrom(...) overloads Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under A22 asset-recipient compatibility , an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks. A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event. TransferBatch for a nonempty batch; no event for an empty batch migrate(fromId, targetOutcomeIndexes) Holder of the source token ID Source universe forked; canonical source pool is Operational or PoolForked , and an Operational source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under A22 asset-recipient compatibility , a contract holder accepts onERC1155Received for every target mint. If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup. PoolForkModeActivated , PoolAccountingCheckpoint , SecurityPoolForkSnapshot , ParentRepLocked , and optionally DisputeStakedRepDrainedAtFork when auto-forking; SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , and ChildPoolLinked when lazily deploying, plus DeployChild , ChildRepSplit , PoolHeldRepSweptToChild , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , and ChildDisputeStakedRepMaterialized as applicable; then one ERC-1155 mint TransferSingle and Migrate per materialized target on successful callbacks authorize(securityPoolCandidate) Initially authorized SecurityPoolFactory for an origin pool; an authorized parent SecurityPool for a child pool Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool. Establishes the candidate as canonicalPoolByUniverse for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op. AuthorizationUpdated on first authorization; no event when the same candidate is already authorized mintCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero; amount is positive; under A22 asset-recipient compatibility , a contract account accepts onERC1155BatchReceived . Mints amount each of Invalid, Yes, and No to account , then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction. TransferBatch on a successful callback burnCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero and has at least amount of every outcome. Burns amount each of Invalid, Yes, and No from account ; global outcome supplies may differ. TransferBatch burnTokenIdAndGetRemainingSupply(tokenId, account) An authorized SecurityPool account is nonzero; caller is authorized. Burns account 's full balance of tokenId and returns the burned amount and that token ID's remaining supply. TransferSingle , including when the burned balance is zero","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"uniformpricedualcapbatchauction","heading":"UniformPriceDualCapBatchAuction","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. Source Read surface: Auction summary getters are maxAttoRepBeingSold , attoEthRaiseCap , finalized , clearingTick , ethFilledAtClearingAttoEth , attoEthRaised , totalAttoRepPurchased , auctionStarted , minBidSizeAttoEth , owner , underfunded , underfundedThreshold , underfundedWinningAttoEth , and activeTickCount . pendingEthRefundsAttoEth reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use computeClearing , previewFinalization , tickToPrice , getTickSummary , getTickCount , getTickPage , getActiveTickPage , getBidCountAtTick , getBidPageAtTick , getBidderBidCount , and getBidderBidPage before finalizing or submitting settlement indexes. Transaction Caller Main prerequisites State or asset effect Primary signals startAuction(attoEthRaiseCap, maxAttoRepBeingSold) Auction owner ( SecurityPoolForker ) only Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in uint128 ; the block timestamp fits in uint48 . Starts the one-week auction and fixes its two caps and minimum bid. AuctionStarted submitBid(tick) with ETH Any bidder Auction active and unfinalized; before one-week deadline; bid meets minBidSizeAttoEth ; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in uint128 . Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again. BidSubmitted refundLosingBids(tickIndices) Bidder for its own bids Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded. A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails refundLosingBidsFor(bidder, tickIndices) Auction owner ( SecurityPoolForker ) only; public callers use settleAuctionBids Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded. A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails finalize() Auction owner ( SecurityPoolForker ) only; users reach it through finalizeTruthAuction Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value. Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event. AuctionFinalized withdrawBids(withdrawFor, tickIndices, proRataTotal) Auction owner only Auction finalized; caller is owner. Nonempty indexes belong to withdrawFor and remain unsettled. For a nonempty list, returns refunds, purchased REP, and a companion pro-rata allocation for the selected beneficiary bids so the forker can credit REP backing units and capacity ownership. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP and capacity-ownership settlement. An empty list returns three zeros without changing bids, emitting events, or calling the beneficiary. BidSettled per processed bid; EthRefundDeferred when a positive push fails withdrawPendingEthRefund() Bidder with deferred ETH Caller has a positive pendingEthRefundsAttoEth balance and currently accepts ETH. Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event. PendingEthRefundWithdrawn","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"","heading":"","keywords":["invariants","safety","liveness","properties"],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"An invariant is a statement that must remain true at every successful external-call boundary, across every legal lifecycle transition, or under the economic assumptions named beside it. Reverts may interrupt a transition, but they must roll back all of its state and asset effects. The catalog separates local contract guards from cross-contract properties and economic assumptions. It also records launch-critical properties that the current contracts do not preserve. A documented implementation choice is not automatically a safe invariant. See the Protocol Security Model for assumptions A01–A28. Funded and underfunded clearing are explained in the Truth Auction clearing guide ; oracle notional and residual-loss analysis are explained in OpenOracle Integration . Four connected layers run from local authority guards through cross-contract conservation and lifecycle liveness to economic security. A failure in an earlier layer invalidates the safety claims built above it. Invariant Dependency Correct callers are necessary but insufficient. Their actions must conserve assets, every mandatory state transition must remain reachable, and the named participation and inclusion assumptions must hold where the protocol deliberately relies on economic incentives. Search Type Any type Enforcement status Any status Subsystem Any subsystem Expand visible Collapse all Reset filters Loading invariants… No invariants match the selected filters.","title":"Protocol invariants","topic":"Safety","weight":1},{"fragment":"standing","heading":"Classification and Scope","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Each entry separates the kind of property from its current enforcement status. A status describes the strongest claim supported by the local implementation and tests; it is not a formal proof. Changes to any contract named in the evidence column must preserve the full statement, not only the check performed in one function. Type Meaning Safety A forbidden caller, state, transition, or replay cannot succeed. Conservation Assets, liabilities, supplies, and claims reconcile without duplication or loss. Liveness A required permissionless action remains executable under the stated conditions. External assumption The property depends on participant behavior, inclusion, liquidity, or token behavior outside the contracts. Enforcement status Meaning Required response Contract guard A direct authorization, bound, or state check enforces the property locally. Keep the guard and test its negative boundary. Reviewed preservation Several contracts cooperate to preserve the property in reviewed flows and tests. Protect it with cross-contract invariant and lifecycle tests. Open violation A concrete sequence reaches a state that contradicts the required property. Fix before deployment and retain a regression test. Economic or external assumption Solidity cannot guarantee the property without honest participation, liquidity, or transaction inclusion. State and monitor the assumption, and document both its failure mode and the guarantees deliberately excluded from the protocol model. Proposed guard The property closes a missing safety or verification boundary not currently represented as one assertion. Add a contract check, model assertion, or invariant harness where practical. Invariant identifiers are stable review labels. They are grouped by authority, universe, question, asset, share, vault, fork, escalation, oracle, auction, lifecycle, and observability boundaries rather than by Solidity file.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"standing","heading":"Terms used in the catalog","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Pool question The question whose outcome determines the pool's winning shares and escalation payouts. Fork question The ended question recorded by the universe fork. It may differ from the pool question. Child branch A well-formed answer encoding for the fork question, used to derive one child universe from its parent. SecurityPool's binary pool-question paths specifically use Invalid, Yes, and No. Continuation game A child escalation game initialized from an unresolved parent game's fork snapshot. Fixed payout outcome The pool-question outcome a continuation game must use after its deadline, regardless of its copied balance leader. Direct fork A caller invokes Zoltar's universe-fork entrypoint directly rather than through a pool's own non-decision path. Recursive fork A fork of a child universe created by an earlier fork. Tracked balance An amount recorded by protocol accounting; it excludes unsolicited assets unless a named transition explicitly credits them. Consumed claim A deposit, bid, or proof that has already been settled, refunded, exported, or otherwise made unusable.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"zoltar-invariants","heading":"Zoltar Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern core universe creation, REP supply, universe forks, deterministic child identities, and branch-specific REP migration.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"universes","heading":"Universes, REP Supply, and Migration","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"UNI-01 One fork per universe Required property An initialized universe records at most one fork time and one fork question. Example: After a universe forks on question 17, a second call attempting to fork it on question 22 reverts. Type Safety Enforcement status Contract guard Primary evidence Zoltar.forkUniverse UNI-02 Valid fork questions Required property forkUniverse accepts only a question that exists in ZoltarQuestionData and whose end time has been reached ( block.timestamp >= endTime ). A pool may separately require the fork question to equal its pool question. Zoltar intentionally imposes no question-creation age, universe registration, or pool-relevance condition. Example: At exactly a known question's end time, a caller may fork a universe even when that question came from another universe or was created with an already-past end time; an unknown or still-open question is rejected. Requiring a universe-relevant or sufficiently aged question is not a Zoltar invariant. Type Safety Enforcement status Contract guard Primary evidence forkUniverse , question model Related external boundaries A15 intended question selection UNI-03 Fork threshold burn and credit Required property The fork threshold is ⌊parent theoretical supply / forkThresholdDivisor⌋ . Starting a fork burns that amount of the initiator's parent REP, reduces the parent theoretical supply by the same amount, and credits the threshold minus ⌊threshold / forkBurnDivisor⌋ for migration. The configured forkBurnDivisor is at least 5 , so the uncredited haircut cannot exceed 20%. Example: With a 1,000 REP theoretical supply, threshold divisor 20, and burn divisor 5, the initiator commits 50 parent REP, pays a 10 REP haircut, and receives 40 REP of migration credit. Type Conservation Enforcement status Contract guard Primary evidence getForkThresholdAttoRep and forkUniverse UNI-04 Deterministic child universe IDs Required property A child universe identity is exactly the truncated hash of its parent universe and fork outcome; one parent-outcome pair maps to one child. Example: Repeatedly deriving the Yes child of the same parent returns the same universe ID, while deriving its No child returns a different ID. Type Safety Enforcement status Contract guard Primary evidence getChildUniverseId UNI-05 Well-formed child outcomes Required property Only well-formed outcomes of the recorded fork question can deploy child universes or receive migrated REP. Example: For a binary categorical fork question whose valid indexes are 0, 1, and 2, passing index 3 cannot deploy a child or receive child REP. Type Safety Enforcement status Contract guard Primary evidence deployChild and splitRepInternal UNI-06 Per-child migration mint limit Required property For one migrator and one child, cumulative child REP minted never exceeds that migrator's parent migration balance. Example: A migrator with 40 REP of migration credit cannot mint 25 REP and later mint another 20 REP into the same child. Type Safety Enforcement status Contract guard Primary evidence childMigrationRepAmounts UNI-07 Branch-specific child REP Required property One burned parent migration balance may mint the same credited amount into several selected children. Each minted balance belongs to a different child REP token, and migration never restores the burned parent REP. Example: A 40 REP migration credit may mint 40 Yes-child REP and 40 No-child REP, but neither balance is parent REP and the burned parent balance remains zero. Type Conservation Enforcement status Reviewed preservation Primary evidence splitRepInternal , ReputationToken UNI-08 Child theoretical supply snapshot Required property The child theoretical-supply snapshot equals the parent's theoretical supply after the threshold burn plus the initiator's post-haircut migration credit. Each child therefore excludes the uncredited fork haircut. Example: If a fork removes 50 parent REP and credits 40 migration REP, each child's theoretical snapshot is 10 REP below the pre-fork supply. Type Conservation Enforcement status Reviewed preservation Primary evidence childUniverseTheoreticalSupplySnapshotsAttoRep UNI-09 Child REP supply coherence Required property For each child REP token, total minted supply equals the sum of holder balances, never exceeds the child's theoretical-supply snapshot, and uses the same theoretical maximum recorded by the child universe. Example: If two migrators mint 12 and 7 child REP, the child token supply is 19 REP, both balances sum to 19 REP, and minting does not change the child's theoretical maximum. Type Conservation Enforcement status Reviewed preservation Primary evidence splitMigrationRep and child universe accounting , ReputationToken supply , aggregate child-supply invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"questions","heading":"Questions and Answer Classification","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"QST-01 Question registry and classifier coherence Required property Every successful question creation appends its deterministic ID exactly once and leaves prior question data and labels immutable. For every stored question and answer, the malformed classifier is true exactly when the public answer name is Malformed . Example: Appending a scalar question after two categorical questions preserves the first two records and their order; a scalar answer with reserved bits is both classified and named malformed. Type Safety Enforcement status Reviewed preservation Primary evidence createQuestion , getQuestions , and answer classification , registry and classifier invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"statoblast-invariants","heading":"Augur Statoblast Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern Statoblast deployment, SecurityPool accounting, fork migration, escalation, oracle operations, truth auctions, and the cross-contract lifecycle built on Zoltar universes.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"authority","heading":"Authority and Deployment Identity","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A23 verified deployments and A27 cryptographic identity . AUTH-01 Coordinator and forker-only operations Required property Only the immutable coordinator may execute price-sensitive pool operations, and only the immutable forker may mutate fork accounting or transfer migration assets. Example: A wallet calling withdrawRepFromVault directly is rejected because only the coordinator may call it. Type Safety Enforcement status Contract guard Primary evidence SecurityPool.onlyValidOracle and onlyForker AUTH-02 Atomic pool and coordinator binding Required property The pool and its coordinator bind once during atomic factory deployment; no external caller gets a transaction boundary in which it can install a substitute pool. Example: A caller cannot front-run factory deployment and make the coordinator point to an attacker-controlled pool. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , setSecurityPool AUTH-03 Immutable protocol logic Required property No mutable owner, governance address, upgrade implementation, or emergency operator can replace protocol logic after deployment. Example: After deployment, no administrator can point a pool proxy at different bytecode because the pool has no upgrade slot. Type Safety Enforcement status Reviewed preservation Primary evidence Immutable release posture AUTH-04 Deterministic deployment identity Required property Every canonical CREATE2 address commits to the correct factory, salt, constructor arguments, parent identity, universe, question, and multiplier. Example: Changing a child's question ID changes its derived address instead of deploying different semantics at the expected address. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , addressDerivation.ts AUTH-05 Delegate storage compatibility Required property Delegatecall targets are constructor-installed protocol modules, and every target interprets the forker's storage with the same layout. Example: The vault-migration delegate reads forkDataByPool from the same slot used by the forker rather than corrupting adjacent state. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolForker , interface and storage-layout regression tests AUTH-06 Permissionless settlement beneficiaries Required property Anyone may trigger permissionless settlement, but the caller cannot choose its beneficiary. Each transfer or REP-backing-unit credit goes only to the vault, bidder, depositor, or share holder recorded for that claim. Example: Alice may settle Bob's auction bid, but the purchased REP remains pool-held while the corresponding REP backing units and capacity ownership are credited to Bob's vault. Type Safety Enforcement status Contract guard Primary evidence Contract interaction reference AUTH-07 Published deployment verification Required property For every published deployment address, the runtime-bytecode hash and constructor-installed dependencies must match the reviewed release manifest. Example: A manifest check fails when the published coordinator address contains code built with a different OpenOracle dependency. Type Safety Enforcement status Proposed guard Primary evidence Deployment Status Oracle limits , mainnet manifest , Sepolia manifest AUTH-08 Factory registry and lineage bijection Required property Every factory deployment record names one unique pool whose runtime parent, share token, universe, and question match the record. Its pool-to-origin reverse lookup and origin/universe forward lookup are mutual inverses, and its recorded share token authorizes that pool. Example: Looking up a child pool's origin and then resolving that origin with the child's universe returns the same child, not a pool from an independent origin lineage in that universe. Type Safety Enforcement status Reviewed preservation Primary evidence factory registry and canonical lookups , share-token authorization , stateful registry-bijection test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"pool-accounting","heading":"Pool Assets, Shares, and Vaults","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"The raw ETH balance needs an explicit surplus term. Without it, forced ETH is indistinguishable from collateral and can change an exchange rate or lifecycle precondition. rawEthBalanceAttoEth = settlementCollateralAttoEth + unallocatedAccruedFeesAttoEth + totalClaimableVaultFeesAttoEth + explicitSurplusAttoEth Every successful external call should reconcile raw ETH into named liabilities or an explicit surplus bucket. All equation terms are denominated in attoETH; raw balance alone must not define collateral. BAL-01 ETH conservation Required property At every successful external-call boundary, raw ETH = tracked collateral + unallocated fee reserve + total claimable vault fees + unaccounted surplus . Surplus is the derived remainder and is never a protocol liability. Example: If the pool holds 12 ETH, owes 2 ETH in fees, and tracks 9 ETH of collateral, the remaining 1 ETH is surplus rather than additional collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence Pool ETH accounting , stateful exact-accounting invariant tests BAL-02 Unsolicited ETH isolation Required property Unsolicited ETH cannot change collateral, initialize an exchange rate, increase liabilities, or block a required transition. Example: ETH forced into an empty pool does not let the first complete-set mint use that ETH as its collateral base. Type Conservation Enforcement status Reviewed preservation Primary evidence receive , tracked collateral, and fee redemption , auction finalization BAL-03 Collateral capacity Required property At a complete-set mint boundary, the resulting tracked collateral must fit the current ETH minting capacity derived from total REP-denominated capacity ownership, the live REP-per-ETH price, and the pool security multiplier. Total ownership includes any unclaimed truth-auction allocation, so that allocation can provide minting headroom before it becomes fee-eligible. Existing open interest is not deleted when later repricing lowers live capacity, so this is not a continuous collateral-below-capacity invariant. Fee checkpointing and redemption cannot reclassify unsolicited ETH as collateral, and a price change reprices capacity without iterating through vaults. Example: If current oracle-priced capacity is 10 ETH, a mint that would raise tracked collateral from 9 ETH to 11 ETH reverts even if the raw balance contains surplus ETH. A later REP price change can lower capacity below 9 ETH without changing the existing open interest or ownership records. Type Conservation Enforcement status Reviewed preservation Primary evidence _requireCapacityNotExceeded , setPoolFinancials , and fee redemption , fork finalization BAL-04 REP coverage boundaries Required property A REP withdrawal or escalation deposit must leave the affected vault and aggregate pool totals independently passing both live open-interest health branches. A delegated liquidation receiver must pass both branches after accepting debt; liquidation does not require the unhealthy target to become healthy. Subsequent repricing may make an existing position unhealthy without deleting its open interest, enabling liquidation rather than retroactively reverting the price change. The associated-REP branch may count locally dispute-staked REP; the free-REP branch counts only pool-held vault REP and uses at least the 10,500-BPS liquidation-award reserve. Required backing rounds upward. A delegated receiver's factor of 10,000 is exactly the protocol minimum, and any higher approved factor multiplies both branches at execution. Example: A receiver approved at 12,000 BPS must retain 1.2× the upward-rounded associated-REP requirement and 1.2× the upward-rounded free-REP requirement after accepting live debt. Passing only one branch is insufficient. Type Safety Enforcement status Contract guard Primary evidence Capacity ownership and withdrawal checks BAL-05 REP backing units conversion consistency Required property After deposits, withdrawals, liquidations, escalation transfers, migration, and auction claims, the same current pool-held REP balance and total REP backing units govern both REP-to-backing-unit and backing-units-to-REP conversion. Example: Depositing REP and immediately converting the resulting REP backing units back to REP uses the same denominator rather than a stale pre-deposit balance. Type Conservation Enforcement status Reviewed preservation Primary evidence attoRepToBackingUnits and backingUnitsToAttoRep BAL-06 Fee liability accounting Required property Every attoETH of accrued fees is represented once as unallocated reserve or claimable vault fees, and redemption clears the claimable fee balance before ETH is sent. Example: Assigning 3 attoETH of reserve to a vault decreases unallocated reserve by 3 and increases that vault's claimable fees by 3, without creating a second claim. Type Conservation Enforcement status Reviewed preservation Primary evidence Fee accumulator and redeemFees BAL-07 Fee reserve protection Required property Fork transfers and redemptions cannot spend ETH reserved for unpaid or unallocated fees. Example: A fork transfer may move 8 ETH of collateral from a 10 ETH balance that also contains 2 ETH of fee liabilities, but may not move all 10 ETH. Type Conservation Enforcement status Contract guard Primary evidence transferEth BAL-08 Aggregate pool-ledger coherence Required property totalClaimableVaultFeesAttoEth equals summed unpaid vault fees. In Operational , fee-eligible capacity ownership equals summed live vault capacity ownership, and uncheckpointed capacity ownership equals capacity ownership whose vault fee index trails the global index. During ForkMigration , configured migrated capacity ownerships remain pending outside those pool aggregates. A PoolForked parent retains its fork-time total and fee-eligible snapshots as individual vaults migrate. After a positive-purchase truth-auction activation, total capacity ownership equals fee-eligible capacity ownership plus the outstanding unclaimed auction allocation. If the auction purchases zero REP, no auction capacity ownership exists: the child still records the parent's fork-time total, while the unmigrated remainder stays outside fee eligibility. The auction settlement specification defines that zero-purchase allocation rule. Example: Migrating one of two vaults that each own 3 REP of capacity leaves the parent's frozen 6 REP total unchanged and records 3 REP as pending in the child. With purchased REP, claims move the remaining 3 REP from auction allocation into fee eligibility. With zero purchased REP, auction allocation is zero and that remainder stays unassigned and ineligible. Type Conservation Enforcement status Reviewed preservation Primary evidence getPoolAccountingSnapshot and vault checkpointing , truth-auction claim accounting , stateful ledger and claim-order invariant tests SHARE-01 Complete-set symmetry Required property A complete-set mint or burn changes Invalid, Yes, and No balances by the same amount in the same universe. Example: Minting five complete sets creates five Invalid, five Yes, and five No shares for that universe. Type Conservation Enforcement status Contract guard Primary evidence mintCompleteSets and burnCompleteSets SHARE-02 Complete-set redemption Required property Before outcome finalization, burning B complete sets returns ⌊B × tracked collateral / complete-set supply⌋ after the fee checkpoint, then reduces supply by B and collateral by the ETH returned. Example: With 100 sets and 10 ETH of tracked collateral, burning 10 sets returns 1 ETH before integer-rounding residue. Type Conservation Enforcement status Reviewed preservation Primary evidence redeemCompleteSet SHARE-03 Positive-output minting Required property A successful positive-value complete-set mint returns a positive share amount. Example: A 1 attoETH mint that rounds to zero shares reverts instead of accepting the ETH. Type Conservation Enforcement status Contract guard Primary evidence attoEthToAttoShares and createCompleteSet SHARE-04 Child supply denominators Required property Child setup copies the frozen parent's remaining economic claim supply as its collateral denominator. That supply includes both materialized child ERC-1155 balances and source entitlements that can materialize later. Complete-set minting adds claims, while complete-set and winning-share redemption consume them. Example: If a parent freezes with 10 claims per outcome and a child has no materialized shares yet, setup still records 10. A new minter receives only the shares purchased at that established rate, while all 10 source claims retain their reserved collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence attoEthToAttoShares and redeemShares and ShareToken.migrate SHARE-05 Winning-share payout cap Required property Total ETH paid to winning shares never exceeds the collateral available when redemption begins; rounding residue remains for later winning holders. Example: When two winners redeem sequentially, the second receives its fraction of the collateral left after the first rather than a fraction of the original balance paid twice. Type Conservation Enforcement status Contract guard Primary evidence redeemShares SHARE-06 Share supply conservation Required property For every ERC-1155 share ID, total supply equals the sum of holder balances through mint, transfer, and burn operations. Per-child materialization and duplicate prevention are owned by FORK-10 . Example: Moving two Yes shares between wallets leaves Yes supply unchanged; burning one Yes share reduces both the holder-balance sum and Yes supply by one. Type Conservation Enforcement status Reviewed preservation Primary evidence ERC-1155 supply accounting , modeled holder-supply invariant test VAULT-01 Dispute-staked REP isolation Required property REP escrowed in an escalation game cannot also be withdrawn, liquidated as pool-held vault REP backing, or migrated through the ordinary non-escrowed vault path. Example: A vault initially attributed 20 REP that dispute-stakes 5 REP is left with 15 REP of vault backing plus a separate 5 REP claim. It cannot make an ordinary withdrawal while that escrow remains; ordinary vault migration moves only the 15 REP backing, while the dispute-staked REP claim follows its separate migration path. Type Conservation Enforcement status Reviewed preservation Primary evidence Escrow checks , EscalationGame VAULT-02 Liquidation conservation and freshness Required property A liquidation transfers rather than creates aggregate REP backing units and capacity ownership, and total capacity ownership across target and receiver is conserved exactly. A nominal debt quote is bounded by the request, target open interest, and the amount whose complete REP award the target can fund. Proportional ownership is rounded downward; moved debt is the receiver's exact live open-interest increase and is bounded by that quote. A delegated route additionally bounds moved debt by its staged approval reservation; the self-receiving route has no approval reservation. On a full-target request, target-local bad debt is target open interest minus exact moved debt, so it may include an award-unfunded slice and integer-allocation residue. Because vault open interest is independently derived with upward rounding, exact before-and-after vault debt deltas are not a conservation identity. Unmatched ownership, escalation claims, accrued fees, and pool-held REP surplus remain with the target. Execution rejects changed target snapshots, enforces the receiver's live post-transfer health factor, and every delegated terminal path consumes or releases its reservation exactly once. Example: If the target or receiver changes after staging, execution may fail safely and release the full reservation. The operator receives no ownership merely for submitting the transaction. Type Conservation Enforcement status Reviewed preservation Primary evidence Liquidation design , performLiquidation , executeStagedOperation VAULT-04 Escalation claims are non-transferable and migration-neutral Required property An escalation claim is permanently bound to the depositor committed in its carry leaf and has no transfer path or parent-OI migration power. Liquidation cannot read, move, or acquire it. Final settlement pays the committed depositor after proof verification and replay protection. Example: Moving half a vault's capacity ownership leaves every escalation claim unchanged, and locking that claim cannot increase the OI routed to any child. Type Safety Enforcement status Reviewed preservation Primary evidence withdrawDeposit , liquidation design VAULT-03 Vault registry coherence Required property The vault registry is append-only and contains each nonzero address at most once. Registration does not require economic state: any vault path that calls _registerVault , including a public fee checkpoint for an empty address, can append it. Pagination is newest-registered first; later activity and full exit do not reorder or remove an entry. Consumers read current vault and escalation state to decide which registered vaults to display. Example: After a vault has fully exited and its REP backing, capacity ownership, claimable fees, escalation stake, bad debt, and open interest are all zero, its address remains in registry pagination. A UI can filter the empty position without losing historical discovery. Type Safety Enforcement status Reviewed preservation Primary evidence _registerVault , registry pagination and direct-claim boundary tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"forks","heading":"Forks and Child Isolation","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"FORK-01 Parent pool finality Required property Once the parent universe forks, parent operational flows freeze and the parent pool never re-enters Operational . Example: After fork activation, the parent cannot accept another REP deposit or later return from PoolForked to Operational . Type Safety Enforcement status Contract guard Primary evidence isOperational FORK-02 Immutable fork snapshot Required property When the parent first enters fork mode, it captures fork time, collateral, REP buckets, total REP backing units, escalation state, and capacity ownership exactly once. Those immutable values are the common basis for every child of that fork. Example: REP sent unsolicited to the parent after fork mode begins cannot change the REP snapshot used to initialize a later-created No child. Type Safety Enforcement status Reviewed preservation Primary evidence Fork snapshot preparation FORK-03 Isolated migration proxy Required property Each parent pool maps to exactly one deterministic migration-proxy address, and that address is the pool's isolated identity in the Zoltar migration ledger. Example: Two parent pools using the same forker derive different proxy addresses and therefore cannot spend each other's migration credit. Type Safety Enforcement status Reviewed preservation Primary evidence getMigrationProxyAddress FORK-04 Prefunded proxy isolation Required property Preexisting or unsolicited REP at the deterministic proxy cannot block initiation or become unclassified migration principal. Example: An attacker sending 1 REP to the future proxy address cannot make fork initiation revert or count that REP as pool migration principal. Type Liveness Enforcement status Reviewed preservation Primary evidence initiateSecurityPoolFork FORK-05 Eight-week migration boundary Required property Statoblast child-pool creation, pool-local vault migration, pool-proxy REP splitting, and own-fork claims are allowed through forkActivationTime + 8 weeks , inclusive, and are closed after that timestamp. forkActivationTime is recorded when the parent pool enters PoolForked ; post-migration activation begins only after its window ends. Share materialization has no expiry, but after the deadline it can target only an already-created child. This pool-local boundary does not limit Zoltar.deployChild , addRepToMigrationBalance , or splitMigrationRep ; those Zoltar operations follow their own fork identity and balance guards without this timestamp check. Example: Vault migration succeeds exactly at the eight-week deadline and reverts one second later. Type Safety Enforcement status Reviewed preservation Primary evidence forkActivationTime recording and child guards , MIGRATION_TIME , deployChild , addRepToMigrationBalance , and splitMigrationRep , migration deadline tests FORK-06 Child deployment identity Required property A deployed child must name the requesting pool as its parent and must use the child universe derived from that parent's selected branch. Its factory, forker, auction, share token, pool question, and security multiplier must equal the values supplied or inherited by the parent factory path. External-universe fork initiation requires the source pool to be authorized by its declared share token; own-game initiation does not. That relationship is not proof of configured-factory registration. The auction must have deployed code and must not already be trusted for another child. Example: On the external-universe path, the forker first requires the source to be authorized by its declared share token without treating that check as factory registration. The configured canonical factory installs the parent's share token, pool question, and multiplier in the child; the post-deployment guard then checks deployed and unique auction assignment plus parent, universe, factory, forker, and auction relationships before linking it. Type Safety Enforcement status Reviewed preservation Primary evidence _prepareForkState external-fork authorization guard , _validateChildPoolDeployment and deployChildSecurityPool FORK-07 Single-use non-escrowed vault migration Required property Vault migration credits one child and clears the corresponding parent REP backing units and capacity ownership before reuse. It checkpoints and retains claimable fees in the parent vault while routing proportional settlement collateral separately at pool level. Example: After Alice migrates her non-escrowed vault accounting to the Yes child, a second migration call cannot credit the same parent REP backing units to the No child. Type Conservation Enforcement status Reviewed preservation Primary evidence _migrateNonEscrowedVaultAccounting FORK-08 One-time aggregate continuation backing Required property Each selected child initializes the canonical carry snapshot and receives the complete aggregate continuation backing at most once. Optional vault cleanup only clears unresolved parent escalation-deposit accounting; it creates no child escrow and cannot change carried-proof eligibility. The continuation cannot resume until its game balance covers all effective unrelated-fork principal. For an own fork, initial backing must cover sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , the rounded-up 80% minimum, and the live balance must cover the unexported remainder after valid direct pre-resume claims. sourcePrincipalAtForkAttoRep is the raw aggregate stored by the fork snapshot before effective direct-claim deductions. Example: Creating the Yes child installs its snapshot and aggregate backing before Alice acts. Alice may later clear her unresolved parent escalation-deposit accounting, but that cleanup neither funds the child nor enables or disables her winning proof. Type Conservation Enforcement status Contract guard Primary evidence _ensureChildEscalationBacking , resumeFromFork , isForkCarryFundingComplete , _exportForkedEscrowByOutcome , and fork-backing regression tests FORK-09 Cumulative child collateral Required property Cumulative parent ETH transferred to children never exceeds the fork collateral snapshot and is derived from cumulative migrated REP, not per-call rounding. Example: If several calls collectively migrate R child REP, their collateral target is ⌈fork collateral × R / denominator⌉ , independent of how that same R is partitioned. The denominator is vaultRepAtForkAttoRep for an own fork and auctionableAttoRepAtFork otherwise. Type Conservation Enforcement status Contract guard Primary evidence _transferForkMigratedCollateralToChild FORK-10 Share migration integrity Required property Share migration preserves the holder's parent token-ID balance as a persistent entitlement, rejects duplicate or malformed target outcomes, and mints only the unmaterialized balance into each selected child. A used source balance cannot transfer. Example: Materializing seven parent Yes shares in one child leaves all seven parent entitlements visible and locked. A later call can mint seven into another existing child, but cannot mint the first child twice. Type Conservation Enforcement status Contract guard Primary evidence ShareToken.migrate FORK-11 Unequal child share supplies Required property Child pricing uses the fork-time economic claim supply rather than currently materialized outcome supplies. Unequal ERC-1155 supplies therefore do not block complete-set minting or proportional complete-set redemption. The denominator lifecycle and payout mechanics are owned by SHARE-04 . Example: If the economic supply is 12 while materialized supplies are 10, 8, and 6, a new complete set is priced against 12 and adds the same newly purchased balance to all three materialized supplies. Type Conservation Enforcement status Reviewed preservation Primary evidence setTotalSharesAttoShares during auction preparation , attoSharesToAttoEth , redeemCompleteSet , and redeemShares , mintCompleteSets and persistent migration accounting FORK-12 Child activation after settlement Required property Value-free truth-auction settlement activates a child with its tracked migrated collateral plus retained auction ETH, which cannot exceed the parent fork snapshot. Fork-time economic claim accounting keeps later minting and proportional burns independent from materialization order. Example: A child with 9 ETH against a 10 ETH fork snapshot activates after value-free finalization with 9 ETH of tracked collateral; its later operations remain subject to the ordinary share-supply and minting guards. Type Safety Enforcement status Reviewed preservation Primary evidence Truth-auction start and finalization","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"escalation","heading":"Escalation Games and Carried Claims","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"ESC-01 Total escrow reconciliation Required property totalDisputeStakedAttoRep equals effective live vault-denominated escrow plus effective aggregate continuation backing in forkCarryDisputeStakedAttoRep . Continuation backing is not assigned to child-vault health or migration power, but it must enter the aggregate before the truth auction so physically held REP cannot escape the repair haircut. Auction retention floors the aggregate total and the continuation bucket independently; do not reconstruct either aggregate by summing independently rounded vault views. Example: If vault-denominated escrow contributes 4 and 6 REP and an external-fork continuation carries 12 REP of aggregate proof backing, the total is 22 REP, represented as 22 × 10^18 attoREP in totalDisputeStakedAttoRep , before the auction. A 75% retention records ⌊22 × 75%⌋ = 16 total effective REP and ⌊12 × 75%⌋ = 9 aggregate continuation REP. This makes the carried claims absorb the same repair loss without pretending that their backing belongs to a particular child vault. Type Conservation Enforcement status Reviewed preservation Primary evidence Continuation initialization , truth-auction retention accounting ESC-02 Local unresolved REP reconciliation Required property totalLocalUnresolvedAttoRep equals the sum of unresolved local deposits, and each vault-local counter is its exact component. Example: If Alice has 3 unresolved REP and Bob has 5, their counters are 3 and 5 and the global local-unresolved total is 8. Type Conservation Enforcement status Reviewed preservation Primary evidence Unresolved counters ESC-03 Outcome carry reconciliation Required property For each outcome, currentCarryTotalAttoRep equals effective inherited unresolved principal plus unresolved local deposits. Effective inherited principal subtracts immediate-parent direct claims and becomes zero for an inherited losing outcome after finalization. Example: A child snapshots 7 Yes REP, the parent directly claims 2 REP, and the child receives a new unresolved 2 REP Yes deposit. Its current Yes carry is (7 - 2) + 2 = 7 REP . Type Conservation Enforcement status Reviewed preservation Primary evidence Escalation accounting invariants ESC-04 Stable deposit identifiers Required property An ordinary game's stable deposit identifier is its local deposit index. A continuation game derives the identifier from its own address, outcome, and local index, so two different continuation deposits cannot share a carry or nullifier key merely because their local indexes match. Example: Deposit zero on Yes in a child game and deposit zero on Yes in its grandchild produce different stable identifiers. Type Safety Enforcement status Reviewed preservation Primary evidence Stable deposit identity ESC-05 Single-use claims per branch Required property Along any single root-to-descendant fork path, each local or inherited deposit may be claimed, settled, refunded, or exported at most once. Child nullifiers are branch-local, but a direct ancestor claim invalidates the matching proof in every descendant and reduces their effective inherited principal. Otherwise, selected sibling branches maintain separate claim authorization. Example: Consuming a carried proof in a Yes grandchild prevents replay farther down that branch while a sibling remains independent. If the deposit was instead claimed directly from their ancestor, both descendants reject it. Type Safety Enforcement status Reviewed preservation Primary evidence Carry nullifiers , MMR proofs ESC-06 Aggregate export accounting Required property Aggregate unresolved export clears the exporting vault's three outcome totals, unresolved counter, and parent escrow exactly once. It retains the deposit rows and carry commitment solely as immutable proof material for selected children. Example: After Alice exports her aggregate claim, her parent escrow is zero even though her historical deposit row remains available to prove a child claim. Type Conservation Enforcement status Reviewed preservation Primary evidence exportVaultUnresolvedTotalsWithoutTransfer ESC-07 Authenticated carried winner payout Required property A carried claim must authenticate an unconsumed proof for the final winning outcome. The proof fixes the original claim identity and its committed depositor, who receives the normal winning payout even when another account relays the transaction. Liquidation cannot change that recipient. Example: Bob may relay Alice's valid winning proof, but the payout still goes entirely to Alice as the committed depositor. A losing proof cannot consume backing, and reward math may make a winning payout greater than its principal. Type Conservation Enforcement status Contract guard Primary evidence withdrawDeposit ESC-08 Game REP settlement conservation Required property Every REP unit held for a game is accounted for exactly once as winning principal, reward funding, losing principal, unsettled residual, or REP swept back to the pool. Settlement cannot create an additional REP claim. Example: Paying winners and sweeping the final residual reduces the game balance to zero without total payouts exceeding the REP previously held. Type Conservation Enforcement status Reviewed preservation Primary evidence Settlement ESC-09 Terminal game outcomes Required property Once resolution or non-decision closes a game path, later deposits cannot reopen or change that terminal result. Example: After Yes becomes final, a later attempt to deposit on No reverts rather than changing the winner. Type Safety Enforcement status Contract guard Primary evidence EscalationGame ESC-10 Ancestor claim replay protection Required property If a deposit is claimed directly from an ancestor during an own fork, every existing or later descendant treats the corresponding proof as spent even if that descendant has not written its own nullifier. Example: Claiming deposit 4 from the parent blocks proof 4 in an already-created child and in a grandchild created later. Type Safety Enforcement status Reviewed preservation Primary evidence Forked claims ESC-11 Residual sweep preconditions Required property Residual REP cannot be swept until effective unresolved principal is zero for every outcome and totalDisputeStakedAttoRep is zero. Inherited losing principal retires at finalization without a proof; winning inherited proofs and every local unresolved deposit still require their applicable terminal transition. Example: After Yes finalizes, an unclaimed inherited No leaf does not block sweeping. An unclaimed inherited Yes proof, an unsettled local deposit, or a live vault escrow record still does. Type Conservation Enforcement status Contract guard Primary evidence sweepResidualRepToSecurityPool ESC-12 Pool and continuation payout agreement Required property When a universe forks on a pool's question, each child pool and its continuation game use that child's Invalid, Yes, or No branch as the final pool-question outcome. Once a pool inherits that fixed outcome, new local escalation deposits and every later fork transition revert, whether the new universe fork uses the same question or another one. Deposit withdrawal reverts unless the pool and game report the same outcome. Example: A Yes child created by a fork on the pool question pays carried Yes deposits even if copied game balances favored No. It rejects new local dispute-staked REP before escrow, so a later local non-decision cannot lock vault redemption. A later matching or unrelated universe fork leaves the stored SystemState.Operational and fixed Yes outcome unchanged, and eligible share, vault REP, and carried-proof redemption paths remain available, while universe-fork guards still freeze normal operating calls. Type Safety Enforcement status Reviewed preservation Primary evidence activateForkMode , getQuestionResolution , fixed-outcome propagation , and pool/game payout check ESC-13 Non-decision threshold and live start-bond arithmetic Required property For a positive fork threshold F , non-decision requires ⌈F / 2⌉ REP on two outcomes. Therefore the two threshold balances total at least F , while one attoREP less on both outcomes totals strictly less than F . At pool construction, the configured start bond is max(1 REP, theoretical REP supply / 10,000,000) , and a new origin requires the live non-decision threshold to exceed it. If an existing pool's live threshold later falls to or below that configured bond, ordinary game deployment clamps the live start bond to nonDecisionThresholdAttoRep - 1 , provided the threshold exceeds one attoREP. Example: If F = 5 , non-decision requires 3 REP on two outcomes, so the two balances hold 6 REP; balances of 2 and 2 remain below the 5 REP fork threshold. Type Safety Enforcement status Contract guard Primary evidence getNonDecisionThresholdAttoRep , origin threshold admission , deployEscalationGame , and start-liveness regression tests ESC-14 Carry commitment structural integrity Required property For every outcome, exported carry peaks, leaf count, unresolved total, and nullifier root equal the corresponding outcome state. Only leaf-count-selected peak heights are occupied, independently bagging those peaks yields the exported root, and consumed proof indexes are unique. The accounting definition of unresolved total is owned by ESC-03 . Example: Three Yes leaves occupy the height-zero and height-one peaks; bagging those two peaks reproduces the exported Yes root before and after one inherited proof is consumed. Type Conservation Enforcement status Reviewed preservation Primary evidence getForkCarrySnapshot and getForkCarryRoots , bagCarryPeaks , independent carry-structure invariant tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"oracle","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . Accepted oracle design properties—not security findings. Statoblast uses profitable correction, not operation-value collateralization. Report liquidity is intentionally not required to equal or bound the value of withdrawals, liquidations, capacity ownership changes, pool assets, or cumulative operations that use an accepted price. The protocol also makes no bounded-liveness claim for a pending sponsor lane. Every valid dispute restarts settlement, incurs transaction execution, and must fund the contract-specified replacement position. Under ordinary non-dust parameters, protocol fees and the required position also accumulate; before the escalation halt, integer flooring can leave dust-sized rounds unchanged. A participant willing and able to keep submitting and funding disputes may therefore delay unrelated users without a coordinator-level deadline. This paid, capital-unbounded pause is intentional and is not a violation of a Zoltar liveness invariant. Security review should verify the correction-profit calculation, report accounting, dispute access, and the stated inclusion assumption. A concrete bypass of the required position or fee, capital reuse, invalid deadline transition, or cache-conformance failure remains a finding. The mere absence of a report-notional bound or absolute pending-report duration does not. ORA-01 Authorized settlement callbacks Required property Only the configured OpenOracle may supply a settlement callback, and its report identifier must equal the coordinator's pending report. Example: A callback from another contract or for an older report ID cannot update the cached REP/ETH price. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback ORA-02 REP-per-ETH price direction Required property An accepted price is positive REP per ETH: amount2 REP × 1e18 / amount1 WETH . Higher values require more REP for the same ETH obligation. Example: A report of 200 REP for 2 WETH stores 100 REP per ETH, not 0.01 ETH per REP. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback , ratio direction ORA-03 Five-minute price validity Required property A cached price is usable only while nonzero and strictly younger than the five-minute validity window. Example: A price settled exactly five minutes ago is stale; one settled four minutes and 59 seconds ago remains usable. Type Safety Enforcement status Contract guard Primary evidence isPriceValid ORA-04 Single-use staged operations Required property Every staged operation has one immutable identifier. Once execution is attempted with a usable price, success or a caught failure consumes that identifier, emits the result, and prevents a retry. An uncaught transaction revert instead rolls back the entire attempt. Example: A pool-level withdrawal rejection is caught and emitted as failed, after which the same operation ID cannot be executed again. Type Safety Enforcement status Contract guard Primary evidence executeStagedOperation ORA-05 Staged operation targets Required property Withdrawals target the initiating vault. Liquidations name separate operator, receiver, and target roles; the receiver must differ from the target, while the operator may equal either. A liquidation fails when the protected target REP-backing-unit or capacity-ownership snapshot becomes stale. Example: Alice can stage her own REP withdrawal. A liquidation operator can target Bob, but its receiver cannot be Bob; execution fails if Bob changes his REP backing units or capacity ownership after staging. Type Safety Enforcement status Contract guard Primary evidence Staging and snapshot checks ORA-06 Consumed execution failures Required property An expired operation, a liquidation with a stale target snapshot, a withdrawal that would move zero REP, or a liquidation inside the configured minimum price distance is consumed with an observable failure result rather than remaining executable forever. Example: If an earlier withdrawal empties a vault, its second queued withdrawal is consumed as a zero-effect failure rather than retried after every price update. Type Liveness Enforcement status Contract guard Primary evidence executeStagedOperation ORA-07 Staged-operation queue and index bijection Required property Operation IDs are append-only. Active enumeration contains each and only each operation with a live initiator; the bounded pending settlement queue contains unique active IDs; and pendingOperationSlotId is zero for an empty queue or equals its head. Example: Settling four queued operations removes those four IDs from both indexes, leaves an overflow operation active for manual execution, and does not reuse any consumed ID. Type Safety Enforcement status Reviewed preservation Primary evidence active and pending staged-operation indexes , queue/index invariant test ORA-08 Dispute and settlement boundary Required property The settlement deadline is exclusive: disputes require the current clock to be strictly before the deadline, while settlement is available at and after it. Example: At timestamp deadline , a new dispute is rejected and settlement is allowed. Type Safety Enforcement status Contract guard Primary evidence settle and dispute validation ORA-09 Oracle balance-domain boundary Required property Direct REP or WETH held by the coordinator is outside OpenOracle and remains untouched by settlement or recovery. OpenOracle credits balances by beneficiary rather than by report: the callback sends the coordinator's entire withdrawable internal REP and WETH credit to the recorded pending sponsor, including any third-party deposit that names the coordinator as beneficiary. Example: REP transferred directly to the coordinator remains there, while REP deposited into OpenOracle for the coordinator is swept with the report proceeds to the current sponsor and leaves only OpenOracle's balance sentinel. Type Conservation Enforcement status Reviewed preservation Primary evidence pendingReportSponsor and _withdrawOpenOracleReporterBalances , deposit and withdrawTo , coordinator/OpenOracle balance-domain test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"auctions","heading":"Truth Auctions","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"AUC-01 Bid admission Required property Bids are accepted only during the one-week window, at a supported tick whose computed ETH-per-REP price is positive, and at or above the auction's minimum bid size. Example: A bid submitted one second after the one-week window or below the minimum bid size reverts without being recorded. Type Safety Enforcement status Contract guard Primary evidence submitBid AUC-02 Auction caps Required property Final retained ETH never exceeds the ETH raise cap, and total purchased REP never exceeds the REP sale cap. Example: If bids offer 12 ETH against a 10 ETH cap, finalization retains at most 10 ETH and refunds the rest. Type Conservation Enforcement status Contract guard Primary evidence computeClearing and finalize AUC-03 Single-use bid settlement Required property Each bid is settled or refunded at most once; claimed state is set before an external ETH refund. The best-effort push is gas-bounded, and any rejected, reverted, or gas-exhausted positive refund is moved to bidder-specific pull escrow without reverting the bid's REP or capacity ownership settlement. Example: A bidder whose refund callback reenters, rejects ETH, or consumes its complete callback budget cannot withdraw the same bid a second time or starve the remaining settlement because the bid is already marked claimed and the push is gas-bounded; deferred ETH remains available through withdrawPendingEthRefund . Type Safety Enforcement status Contract guard Primary evidence withdrawBids and refund functions AUC-04 Claim-order independence Required property Cumulative allocation math makes per-bid REP, refund, and capacity ownership results independent of post-finalization claim order. Example: Alice claiming before Bob gives each vault the same REP, ETH refund, and capacity ownership as Bob claiming before Alice. Type Conservation Enforcement status Reviewed preservation Primary evidence _allocateFromCumulativePosition AUC-05 Allocation and refund reconciliation Required property A zero-demand auction records zero purchased REP. Otherwise, the sum of all winning-bid REP allocations equals the stored aggregate purchase, and each bid's unretained ETH remains refundable. Example: If finalization stores 30 purchased REP, all winning claims together receive exactly 30 REP and every unretained attoETH is refunded. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction settlement AUC-06 Bidder-vault credits Required property A bid claim keeps purchased REP pool-held and credits corresponding REP backing units plus the bid's fixed-position share of the complete unmigrated capacity ownership to the bid's recorded vault. These credits add to rather than replace that vault's migrated position, and cumulative allocation keeps capacity ownership rounding independent of claim order. A winning dust bid still receives a positive fixed-position capacity ownership share when its REP allocation rounds to zero. Example: A vault with migrated REP backing units keeps them when settling a winning auction bid; the claim adds the REP backing units and deterministic capacity ownership share assigned to that bid while the purchased REP remains pool-held. If a dust bid's REP share is zero but its capacity ownership share is positive, claiming that bid alone still credits the capacity ownership. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction-claim settlement AUC-07 Uniform weak-demand allocation Required property In underfunded clearing, qualifying ETH is retained bid ETH offered at or above the cap-implied qualification threshold. The threshold is not an execution-price floor. Qualifying bidders collectively receive maxAttoRepBeingSold in proportion to that ETH at the aggregate effective price underfundedWinningAttoEth / maxAttoRepBeingSold . Per-bid cumulative floors then assign indivisible attoETH and attoREP, so a dust winner can round to zero REP without changing the aggregate price. With no qualifying ETH, every bid refunds. Example: If Alice supplies 60% and Bob 40% of qualifying ETH, they receive 60% and 40% of the complete REP sale cap subject to deterministic attoREP rounding. Type Conservation Enforcement status Reviewed preservation Primary evidence Truth Auction clearing design AUC-08 Forced-ETH-resistant finalization Required property Auction finalization succeeds regardless of unsolicited ETH already present in the child pool or forker. Example: One attoETH forced into the child before finalization remains surplus and does not change protocol-accounted collateral. Type Liveness Enforcement status Reviewed preservation Primary evidence finalizeTruthAuctionRepair AUC-09 Bounded bid settlement Required property After the auction deadline, value-free finalization succeeds using migration-routed collateral plus retained auction ETH. Qualifying bids settle for REP, non-qualifying bids refund, and nonzero finalizer ETH reverts. Example: For a 10 ETH target with 6 ETH migrated and 3 ETH retained from bids, the child activates with 9 ETH of accounted collateral and every bid can then settle. Type Liveness Enforcement status Contract guard Primary evidence Weak-demand behavior , finalizeTruthAuctionRepair AUC-10 Persistent tick bid history Required property Bid cumulative ETH remains append-only for a tick even when the active AVL node is deleted and later recreated. Refunded history is subtracted exactly once, so every later accepted bid retains a valid allocation or refund path. Example: Deleting an empty price tick and later bidding at that price continues cumulative ETH after the old history instead of making the new bid unclaimable. Type Conservation Enforcement status Reviewed preservation Primary evidence _appendBid and refund-prefix accounting and repeated tick deletion/recreation tests AUC-11 Auction tree and public-model equivalence Required property Before finalization, active tick pages contain exactly the nonzero ticks not removed by pre-finalization refunds, in descending order, and clearing computed from the AVL tree equals an independent calculation over those bids. Finalization freezes that tree and clearing result: later claims change bid flags and ETH liabilities but not active tick pages. Post-finalization liabilities are owned by AUC-12 . Historical insertion and refund-prefix obligations are owned by AUC-10 . Example: Before finalization, an independent descending-tick scan returns the same clearing result as the contract. After a winner claims, its bid is marked claimed and its refund liability leaves, while the final clearing page and result remain unchanged. Type Safety Enforcement status Reviewed preservation Primary evidence AVL tree, enumeration, and clearing , seeded independent-model and frozen-snapshot tests AUC-12 Auction ETH liability conservation Required property Before finalization, auction ETH equals active unrefunded bids plus aggregate pendingEthRefundsAttoEth and explicit surplus. After finalization, the remaining balance equals refunds still attached to unclaimed bids plus deferred pendingEthRefundsAttoEth and explicit surplus. Per-bid settlement partitions and aggregate REP allocation are owned by AUC-05 . Example: If a rejected pre-finalization push removes a 2 ETH losing bid from active clearing, raw ETH still equals the remaining active bids plus the 2 ETH deferred refund. After finalization, the same deferred bucket composes with unclaimed-bid refunds and any forced surplus. Type Conservation Enforcement status Reviewed preservation Primary evidence finalize , withdrawBids , and deferred refunds , seeded and deferred-refund ETH-liability tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"lifecycle","heading":"Lifecycle, Liveness, Observability, and External Calls","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A02 viable operating costs , A16 timely inclusion , A06 lifecycle executors , and A26 Ethereum execution . LIFE-01 Pool state transitions Required property Pool state topology permits only Operational → PoolForked for a parent and ForkMigration → ForkTruthAuction → Operational for a child. Example: A child cannot skip directly from ForkMigration to Operational while repair auction work remains required. Type Safety Enforcement status Reviewed preservation Primary evidence SystemState , fork transitions ; see FORK-12 for child accounting consistency. LIFE-02 Permissionless transition liveness Required property Once a resolution, migration, auction-completion, or withdrawal function's documented semantic preconditions hold, dust, unsolicited balances, empty collections, or earlier permissionless calls cannot permanently prevent some caller from completing it. Example: Forced ETH and an empty losing-bid list do not prevent a caller from finalizing an otherwise complete truth auction. Type Liveness Enforcement status Proposed guard Primary evidence FORK-04 , BAL-02 , and AUC-08 LIFE-03 Atomic failure behavior Required property An uncaught revert rolls back every state mutation and transfer in the transition; deliberate consumed failures emit an explicit result and cannot be retried. Example: If a child migration reverts after an attempted token transfer, neither the transfer nor its accounting update remains committed. Type Safety Enforcement status Reviewed preservation Primary evidence Solidity atomicity, coordinator consumed-failure paths LIFE-04 Deadline equality rules Required property Every deadline assigns equality to one documented side of the boundary: migration remains open at equality; auction bidding and public pool finalization are both closed at equality, and finalization opens immediately afterward; oracle disputes are closed and settlement is open at equality; staged operations expire only after equality; and an escalation result is final only after its end timestamp. Example: Exactly at an auction deadline, both a bid and public pool finalization revert; one second later, finalization succeeds. Type Safety Enforcement status Proposed guard Primary evidence Boundary tests across fork migration, auctions, escalation, and OpenOracle LIFE-05 Bounded transition work Required property No mandatory transition loops over an attacker-unbounded history in one transaction; callers use bounded pages, fixed outcome sets, proofs, or balanced-tree traversal. Example: Settling one carried deposit verifies its bounded proof instead of iterating over every deposit ever made in its ancestors. Type Liveness Enforcement status Reviewed preservation Primary evidence Vault pages, bid pages, MMR proofs, and auction AVL traversal OBS-01 Event-state replay equivalence Required property Ordered canonical events reconstruct the same universe, pool, vault, coordinator, and escalation carry state exposed by storage. Replay isolates contract-local counters and recognized emitters, and every replay-supported state transition emits the checkpoint or delta needed to reach its resulting storage value. Example: Replaying seeded pool and vault operations matches their accounting storage, while replaying a deployed parent deposit, fork snapshot, and child carry checkpoint matches the child's roots, peaks, leaf counts, unresolved totals, and nullifier roots. Type Safety Enforcement status Reviewed preservation Primary evidence contract interaction reference , event replay model , actual-log storage-equivalence tests EXT-01 Genesis token behavior boundary Required property Genesis REP and configured WETH obey the exact transfer, return-value, decimal, and callback behavior assumed by pool and oracle accounting. Mainnet relies on external token behavior; Sepolia relies on verified deployments of the reviewed GenesisReputationToken and WETH9 contracts for issuance and transfer mechanics. Burn-sink inaccessibility remains an external assumption on both networks. Example: Genesis REP transfer returns and moves exactly the requested attoREP rather than charging a fee that leaves pool accounting overstated; child REP supply remains bounded by its contract-enforced theoretical ceiling. Type External assumption (mainnet); mixed safety and external assumption (Sepolia) Enforcement status External on mainnet; reviewed issuance and transfer guards under A23 and A26 on Sepolia, with burn-sink inaccessibility still assumed Primary evidence SafeERC20Ops , GenesisReputationToken , WETH9 , the network deployment manifests, and security assumption A21 Related enforced property Canonical child REP behavior is enforced by ReputationToken and its Zoltar-only mint and burn authority. EXT-02 Callback-safe accounting Required property External ETH or token callbacks observe effects already committed for that action. A required delivery that fails reverts without leaving partial accounting; an explicitly deferrable delivery instead commits the action together with an exactly accounted recipient liability. Example: A fee recipient's fallback observes its claimable fees already cleared, so reentry cannot redeem the same claimable fees twice. Type Safety Enforcement status Reviewed preservation Primary evidence Pool redemption, auction refund and deferred-credit, and oracle refund paths Related external liveness boundary A22 participant-controlled recipient compatibility EXT-03 ERC-1155 callback safety Required property ERC-1155 receiver callbacks cannot mint beyond pool capacity, alter the amount minted, or reenter the same accounting effect twice. Example: A receiver callback during complete-set minting cannot call back into the pool and reuse the same ETH capacity for a second mint. Type Safety Enforcement status Reviewed preservation Primary evidence createCompleteSet , ERC-1155 receiver checks, and receiver callback tests Related external liveness boundary A22 participant-controlled recipient compatibility EXT-04 Same-block deadline ordering Required property At a deadline timestamp, mutually exclusive before-deadline and after-deadline actions cannot both succeed. Explicit comparison operators assign equality to one phase, and transaction ordering within the block cannot change that assignment. Example: At the OpenOracle settlement deadline, ordering a dispute before settlement in the block still cannot make the dispute valid. Type Safety Enforcement status Proposed guard Primary evidence ORA-08 and same-block auction ordering analysis EXT-05 Recursive fork gas bound Required property Origin registration, inherited-fork detection during child construction, and direct-claim replay checks must not traverse universe or pool ancestry. Example: At lineage depths 1 and 32, a replay query for the same global deposit id performs the same lineage-registry and claimed-id lookups rather than 1 or 32 parent calls. Type Liveness Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory.getOriginId and getPoolId , cached inherited-fork registry tests, SecurityPoolForker.getEscalationDepositId , and depth-independent replay gas tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["assumptions","security","threats","guarantees"],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Statoblast and Zoltar enforce accounting, authorization, and lifecycle rules onchain. Their economic safety and practical liveness still depend on the participant, market, asset, deployment, client, data, Ethereum, and cryptographic assumptions below. An assumption is not a guarantee enforced by the contracts. Each entry says what must remain true, what can fail if it does not, and where the related mechanics are explained. The Protocol Invariants catalog lists contract-enforced properties and their implementation evidence. Participation Forks and markets Oracle and liveness Technical foundation Excluded guarantees","title":"Security model","topic":"Safety","weight":1},{"fragment":"orientation","heading":"Security-model orientation","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"This catalogue protects REP, ETH settlement collateral, outcome shares, vault backing, escalation deposits, child-universe accounting, and the integrity of event-derived state. Boundary Examples Trusted protocol components Zoltar, security pools, share tokens, coordinators, forkers, and their configured delegates. External dependencies OpenOracle, WETH/REP token behavior, Ethereum execution and finality, RPCs, wallets, and proof data. Untrusted actors Traders, vault owners, liquidators, disputers, bidders, settlers, keepers, and indexers. Failure outcomes Reverted calls, delayed transitions, underfunded child repair, stale prices, incorrect external data, or unreconstructable history. The assumptions below explain what must remain true for these assets and transitions to retain their intended safety or liveness properties; they are not additional contract guarantees.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-value","heading":"Participation and coordination","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions explain why someone performs the economically useful action instead of merely being permitted to perform it.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a01","heading":"Participants act on risk-adjusted economic incentives","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Enough participants prefer actions with greater expected value after gas, fees, delay, capital lockup, and risk. The model does not rely on altruistic defense. Failure boundary: A nominally profitable liquidation, dispute, bid, migration, or fork defense may not happen when its risk-adjusted return is unattractive. Mechanics: economic roles , liquidation incentives , auction clearing , and oracle incentives .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a02","heading":"Required actions remain worth their operating costs","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each time-sensitive action, transaction fees, opportunity cost, coordination cost, and capital lockup remain small enough relative to the value protected or earned. Failure boundary: An action can be technically executable but economically abandoned before its deadline. Mechanics: auction gas bounds , oracle gas parameters , and escalation deployment costs .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a03","heading":"Each economic role has enough independent participants","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Trading, liquidation, escalation, migration, auction bidding, price correction, and settlement have enough non-colluding participants for the relevant action. Secondary-market trading is external to Statoblast. Failure boundary: Thin or controlled participation can remove price discovery, competitive bidding, or an opposing outcome deposit. Mechanics: economic roles , fork coordination , and auction participation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a04","heading":"Participants can mobilize REP to raise an alarm","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Market participants can mobilize enough vault-backed REP through vault owners to fund an early competing Invalid , Yes , or No deposit and keep the local contest active while additional capital reacts. Failure boundary: A wrong local outcome can become final before better-capitalized participants enter. Mechanics: escalation resolution , escalation accounting , and operator guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a05","heading":"Participants value continued access to the supported Statoblast lineage","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The participants whose coordination determines durable use value continued access to the supported Statoblast lineage at least as highly as its configured statoblastSecurityMultiplierBps basis points of the value secured by that lineage. BPS_DENOMINATOR = 10_000 converts that stored value to its human multiplier. Value Statoblast access × BPS_DENOMINATOR ≥ statoblastSecurityMultiplierBps × Value secured Compare one lineage’s continued-use value and secured value at the same pre-attack time. Failure boundary: Participants may rationally abandon, grief, or coordinate away from a lineage whose continued-use value is too small. Mechanics: fork security , fork migration , and Statoblast security boundary .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a06","heading":"At least one executor advances every required public transition","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"An adequately funded participant or service monitors and calls each needed permissionless transition, including liquidation initiation, local escalation deposits and settlement, fork activation, child creation, vault and share migration, auction start and finalization, bid settlement, OpenOracle report settlement, settled-report callback recovery, overflow operation execution, and proof-based claims. Failure boundary: Permissionless state can remain stalled even though no contract authorization prevents progress. Mechanics: lifecycle liveness , fork operations , and oracle operations .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-forks","heading":"Forks, truth, value, and liquidity","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions connect onchain branching and accounting to offchain truth judgments, asset value, liquidity, and migration.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a07","heading":"A supported lineage preserves enough aggregate child value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each protected Statoblast origin lineage, evaluate the parent and its child-pool continuations in one numeraire at the fork-security decision horizon, before long-run value has fully concentrated on one branch. The sum of traded child-lineage values remains large enough compared with that parent lineage’s pre-fork value under the origin’s immutable statoblastSecurityMultiplierBps . Each child asset is valued in its own branch; unrelated external collateral is not counted again. Value parent lineage ≤ ∑ child ∈ Lineage continuations Value child × BPS_DENOMINATOR statoblastSecurityMultiplierBps lineage Parent and child values use the same origin lineage, valuation source, numeraire, and decision horizon. For multiplier 20_000 BPS , aggregate child-lineage value must be at least twice parent-lineage value; multiplier 30_000 BPS requires three times that value. Failure boundary: Value destruction or fragmentation can make a malicious or unnecessary fork cheaper than the harm it causes. Mechanics: fork economics , fork coordination , and pool migration .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a08","heading":"REP market capitalization approximates economic value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At the stated valuation time and system scope, REP market capitalization is a sufficiently accurate observable proxy for the discounted cash flow participants expect from REP. Failure boundary: Thin, manipulated, or speculative pricing can overstate the economic value securing open interest. Mechanics: REP economics and economic backing claims .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a09","heading":"Users value the universes they judge truthful","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users prefer the universe or universes they judge truthful, so economic activity and durable asset value concentrate there. Unlike A07’s fork-security decision horizon, this assumption describes the later, durable allocation of value after users coordinate. Value before fork ≈ Value truthful universe When several branches remain plausibly truthful, document the value split instead of assuming one winner. Failure boundary: Coordination on a false branch, or durable fragmentation across branches, defeats the value-concentration security argument. Mechanics: Zoltar security and child outcome resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a10","heading":"Participants can access timely outcome evidence","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users can obtain enough reliable real-world evidence before the relevant protocol deadline to judge which universe is truthfull. Failure boundary: Missing, delayed, or conflicting evidence can split honest capital or let a false local outcome become final. Mechanics: question encoding , invalid outcomes , and child resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a11","heading":"Migration is operationally practical","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users, exchanges, interfaces, custodians, and other integrations are willing and able to migrate assets and state into the child universe or universes they support during the applicable windows. Failure boundary: Supported branches can remain illiquid or undercollateralized because economic claims and backing do not move there. Mechanics: REP splitting , pool migration , and operator migration guidance .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a12","heading":"Truth auctions can convert supported child REP efficiently","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"When repair is needed, enough demand can buy some supported-child REP for ETH at an acceptably small discount and operating cost. This does not assume that the full repair target is raised. Failure boundary: Weak or strategically withheld demand leaves the child operational with impaired collateral. Mechanics: auction lifecycle , clearing , and repair accounting .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a13","heading":"REP is sufficiently liquid","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can acquire or sell the required REP quantities without price impact or delay large enough to invalidate reporting, disputing, liquidation, auction, or migration economics. Failure boundary: Capital may exist in principle but cannot be assembled at a usable price before the deadline. Mechanics: economic roles , escalation , and oracle positions .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a14","heading":"REP economic value exceeds the open interest it secures","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For a stated pool, lineage, or system scope at one timestamp, open interest and REP discounted cash flow are valued in the same numeraire, and REP discounted cash flow remains strictly greater. For a pool-level evaluation, open interest means the pool’s protocol-accounted ETH collateral unless the analysis explicitly names a broader exposure measure. The REP supply and price basis must also be stated; market capitalization is the observable proxy in A08 . This external relationship is separate from each pool’s onchain multiplier-adjusted REP backing check. Open Interest < Discounted Cash Flow REP Record scope, timestamp, common numeraire, price source, REP supply basis, and open-interest definition when evaluating this strict inequality. Failure boundary: REP holders may rationally accept or cause losses larger than the durable REP value placed at risk. Mechanics: security pools , liquidation backing , and oracle exposure analysis .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a15","heading":"Question selection remains aligned with user intent","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces and users verify the immutable question ID and metadata when selecting a market or pool, and the question has evidence and wording from which participants can reach an outcome or intentionally choose Invalid . Failure boundary: Unverified selection can route users and capital to an unintended market or pool, while ambiguous wording or unavailable evidence can split local outcome coordination. Mechanics: UNI-02 , question identity , and pool selection .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-oracle","heading":"Oracle correction, inclusion, and operational data","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions cover truthful REP/ETH price discovery, deadline access, capital, independent correction, and the offchain information required to act.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a16","heading":"Deadline transactions can obtain Ethereum inclusion","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Honest users can submit competitively priced Ethereum transactions and obtain canonical inclusion within every relevant dispute, escalation, migration, auction, staging, and settlement window. Failure boundary: Censorship or congestion lasting through a deadline can finalize a wrong report or prevent a supported action. Mechanics: deadline invariants , auction windows , and oracle censorship model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a17","heading":"OpenOracle has an available, adequately capitalized corrector","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one OpenOracle participant continuously monitors pending REP/ETH reports, can identify candidate errors using the reference market in A19 , can fund the scenario-dependent WETH and REP replacement position plus transaction costs, and can obtain inclusion before settlement. The participant's net incentive to perform the correction is the separate boundary in A18 . Failure boundary: An incorrect report can settle and authorize price-sensitive pool actions when no capable participant detects, funds, and includes a correction. Mechanics: REP/ETH oracle , correction incentives , and attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a18","heading":"At least one available corrector has an independent net incentive","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one participant satisfying A17 is not under common control with the sponsor-funded coordinator reporting path, does not share the manipulation payoff, and is not bribed or compensated. The participant's net payoff from correcting is strictly greater than its net payoff from leaving the harmful report uncorrected. Two external submissions are not required for every request. Failure boundary: A sponsor-controlled, bribed, or economically indifferent corrector can make the contestable report path behave like a single trusted reporter. Mechanics: settlement validation scope , economic tradeoffs , and oracle invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a19","heading":"A robust reference market makes harmful REP/ETH errors correctable","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Independent participants can observe a manipulation-resistant reference REP/ETH price. Every price error large enough to unlock a security-relevant pool action also leaves a correction opportunity that remains profitable after actual gas, prevailing priority fees, OpenOracle fees, price impact, and required capital lockup. Failure boundary: A harmful deviation below the profitable correction threshold, or a manipulated/illiquid reference market, can settle without an economically rational challenge. Mechanics: report sizing , external-payoff model , and settlement limits .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a20","heading":"Canonical chain data and proof construction remain available","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can access reorg-aware Ethereum history, recognized contract addresses, OpenOracle report state, and the event data needed to construct carry and nullifier proofs before use. Failure boundary: A valid dispute, settlement, migration, or inherited claim can become practically unavailable even while its contract entrypoint remains live. Mechanics: event replay , carry proofs , and operator orientation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-technical","heading":"Assets, clients, deployment, Ethereum, and cryptography","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions define the technical foundation below the protocol’s onchain guards.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a21","heading":"Genesis REP and configured WETH obey the required accounting model","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Genesis REP and configured WETH move exactly the requested base units, use 18 decimals, return supported ERC-20 values, and do not rebase, charge transfer fees, invoke unexpected callbacks, blacklist protocol contracts, pause required transfers, or permit unauthorized minting. WETH remains redeemable 1:1 for ETH, the genesis theoretical supply is accurate, and the configured genesis REP burn sink remains inaccessible. On mainnet these token behaviors are external assumptions. On Sepolia, the reviewed GenesisReputationToken and WETH9 enforce issuance and transfer behavior, provided A23 verifies their deployed bytecode and wiring and A26 preserves Ethereum execution. The configured burn sink's inaccessibility remains an external assumption on Sepolia as well as mainnet. Canonical child REP is likewise implemented by the reviewed ReputationToken contract rather than being an additional external-token assumption. Failure boundary: Origin-pool, genesis-fork, or oracle accounting can overstate balances, backing, burned supply, or WETH value. Mechanics: EXT-01 , genesis REP handling , and oracle token pair .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a22","heading":"Participant-controlled recipients can accept required asset deliveries","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Vaults, bidders, oracle sponsors, traders, share holders, and other caller-controlled recipients use addresses that accept native ETH when claiming fees, collateral, proceeds, or refunds. Contract recipients also return the required ERC-1155 receiver selector when complete-set creation, share migration, or an outcome-share transfer delivers tokens to them. A rejected ETH push may revert or become bidder-specific deferred credit; a rejected ERC-1155 callback reverts the mint or transfer and its surrounding protocol action. No protocol path can force a rejecting address to accept a later delivery or redirect every payout or mint. Failure boundary: A contract account that rejects ETH or the required ERC-1155 callback can strand its own payout or make its complete-set, migration, transfer, or claim action unavailable even while protocol-wide accounting remains safe. Mechanics: ETH callback safety , ERC-1155 callback safety , share-token receipt , auction refunds , oracle request refunds , and pool payout guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a23","heading":"Users select verified canonical deployments","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The reviewed release is built reproducibly with its pinned compiler and toolchain configuration. Release artifacts identify the manifest addresses and reviewed bytecode hashes. Users and integrations compare locally rebuilt and deployed runtime bytecode with those reviewed hashes, then verify constructor-installed dependencies, factory and forker provenance, pool lineage, question ID, and manifest. Constructor admission is not a safety proof. Failure boundary: Toolchain drift, unavailable or mismatched reviewed hashes, permissionless lookalike instances, wrong dependencies, or unintended lineage and question wiring can bypass the reviewed protocol even when the selected bytecode functions as configured. Mechanics: release posture , deployment status , compiler configuration , OpenOracle build provenance , deployment bytecode , and authority invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a24","heading":"Clients, RPCs, and wallets preserve user intent and canonical state","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces, indexers, RPC providers, and wallet software return canonical, reorg-aware state; construct and display the intended chain, target, calldata, parameters, and ETH value; and submit only transactions the user authorizes. Users verify transaction previews rather than treating an application label as an onchain guarantee. Failure boundary: Compromised or stale offchain software can misroute signatures or capital, hide deadlines or events, or cause users to authorize valid but unintended calls without violating contract guards. Mechanics: deployment discovery , contract interactions , and operator verification .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a25","heading":"Immutable parameters preserve economic and executable security margins","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The selected parameter relationships keep the fork threshold and haircut economically meaningful, the initial escalation deposit affordable, the pool multiplier and effective pool-held vault REP backing multiplier large enough for their distinct coverage roles, the effective pool-held vault REP backing multiplier at least the 10,500-BPS liquidation-award reserve, the oracle target error profitable to correct after fees and gas, dispute and settlement windows usable, and callback and transition gas executable. The Statoblast pool statoblastSecurityMultiplierBps is distinct from openOracleSecurityMultiplierBps . Failure boundary: Weak or internally inconsistent immutable parameters can defeat the economic or liveness argument even when the canonical contracts function exactly as configured. Mechanics: Statoblast parameters , fork economics , liquidation economics , oracle parameters , and oracle attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a26","heading":"Ethereum preserves the execution environment the protocol relies on","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Ethereum continues to provide correct EVM execution and atomic rollback, sufficient canonical finality, usable timestamp and block.basefee semantics, and block gas limits and gas schedules under which every mandatory immutable transition remains executable. Failure boundary: Deep reorganizations, invalid state execution, adversarial time behavior, or gas repricing can reverse accepted state or strand required transitions. Mechanics: reorg handling , atomicity and deadlines , and bytecode and gas bounds .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a27","heading":"Ethereum cryptographic primitives remain secure","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Keccak-256 remains collision- and preimage-resistant at the truncated widths used by the protocol, and CREATE2 identity remains sound. secp256k1 ECDSA signatures remain unforgeable with correct EOA signer recovery for Ethereum transactions and EIP-712 authorizations. OpenOracle's fixed Permit2 singleton additionally validates either an EOA signature or an ERC-1271 contract-account signature correctly. OpenOracle fixes the Permit2 address, requires the exact permitted amount, sends the requested tokens to itself, and constructs the witness from the beneficiary, relayer, token owner, and intent. Permit2 defines the EIP-712 domain, deadline, permitted-amount ceiling, unordered-nonce replay protection, and EOA/ERC-1271 signature validation. Verification under A23 must cover the pinned Permit2 implementation at that fixed address. Failure boundary: Forged identities, colliding questions or universes, false carry proofs, forged EOA transactions, incorrect EOA recovery or ERC-1271 acceptance, or broken domain and nonce enforcement can violate otherwise-correct guards or authorize an unintended token transfer. Mechanics: universe identity , proof hashing , Permit2 deposit authorization , Permit2 signature interface , pinned Permit2 implementation provenance , and deterministic deployments .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a28","heading":"Participants retain control of account authority","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants protect the private keys, signing devices, session permissions, and delegated ERC-20 or ERC-1155 approvals that authorize their accounts. Account recovery and custody arrangements do not give an unintended party effective control during a security-sensitive window. Failure boundary: A stolen key, compromised signer, or overbroad approval can authorize valid but unwanted transfers, deposits, bids, migrations, or configuration changes without violating a contract guard. Mechanics: transaction authority , vault and pool operations , and account-authority monitoring .","title":"Security model","topic":"Safety","weight":0},{"fragment":"excluded-guarantees","heading":"Guarantees deliberately not made","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These outcomes remain possible without contradicting the security model. They should be included in user, operator, and integration risk disclosures. EG01. Zoltar creates valid branches but does not enforce that the fork question is relevant to a user, establish the objectively truthful branch, obtain user consent to a fork, or force users to coordinate on one branch. EG02. A truth auction may raise less than its repair target; the child then activates with migrated collateral plus accepted auction ETH. EG03. OpenOracle report liquidity does not cap the notional value of operations using an accepted price, and settlement does not prove external price correctness. EG04. A participant with unbounded capital can keep funding valid disputes and delay the sponsor lane indefinitely; no bounded coordinator-availability claim is made. EG05. The immutable protocol has no administrator who can pause, upgrade, roll back, or rescue an unsafe deployment after launch. EG06. Escalation principal and expected rewards are not senior to open-interest repair. A child truth auction may proportionally reduce them, and a losing escalation claim pays its committed depositor nothing.","title":"Security model","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["operators","guardrails","launch","recovery"],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Operators, indexers, reviewers, and UI maintainers consult this reference for contract-accurate guardrails, launch procedures, and edge-case behavior in one subsystem at a time. The explanations carry the full protocol story. Each section answers a different operational question: how the immutable launch posture constrains releases, what a security pool or escalation game will and will not accept, how fork migration behaves, how auction settlement works, and how the REP/ETH coordinator stages or recovers operations. Implementation guardrails map to their contract sources so operators can check each rule against the exact Solidity implementation. The Protocol Invariants catalog connects those local guardrails to cross-contract conservation, lifecycle, liveness, and economic security properties, including launch-critical properties that the current contracts do not preserve. The Protocol Security Model is normative for the protocol's external assumptions.","title":"Operator guardrails","topic":"Operations","weight":1},{"fragment":"security-review-orientation","heading":"Security Review Orientation","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before classifying an economic or liveness concern, use the security model's grouped assumptions to identify external dependencies, then follow its canonical mechanics links. The invariant catalog owns the current requirement, status, and evidence for EXT-05 recursive-fork gas behavior . Operator work relies particularly on A23 verified deployments , A06 lifecycle executors , A20 chain-data and proof availability , A26 Ethereum execution and finality , A27 cryptographic security , A15 intended question selection , A25 safe immutable parameters , A22 asset-recipient compatibility , A24 client, RPC, and wallet integrity , and A28 account authority .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"assumption-monitoring","heading":"Assumption Monitoring","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The security model is the canonical definition of each boundary. This table assigns operational evidence and response for assumptions whose truth can change with market or service conditions. Evaluate each signal at the same lineage, pool, timestamp, numeraire, and decision horizon as the protected action. Assumptions Operational evidence Review cadence Response when the boundary is weak or unknown A01, A02, A03, A06 Expected reward after gas, fees, delay, and capital lockup; number and independence of active liquidators, escalation participants, bidders, correctors, and transition executors; settle-ready OpenOracle reports, callback-recovery candidates, and overflow operations Continuously for deadline-bearing flows; before launch and after parameter or gas-regime changes Fund or arrange independent executors, reduce exposed value, or disclose that the affected transition may stall A05, A07, A09, A11 Parent and supported-child lineage value in one numeraire; observed user, custodian, exchange, interface, and proof-tool readiness to migrate assets and state within the applicable windows; documented continued-use value and secured value Before a fork-security claim and throughout migration and coordination windows Do not claim fork deterrence, practical migration, or value concentration; narrow supported lineage and exposure A08, A13, A14 REP supply basis, price source, depth and price impact; REP discounted-cash-flow methodology; protocol-accounted ETH collateral or explicitly broader open interest At launch, continuously while open interest exists, and after material price, supply, liquidity, or exposure changes Cap or stop new exposure in clients and disclosures; do not describe REP backing as economically sufficient A04, A10 Outcome evidence sources and availability; immediately mobilizable REP and transaction budget before each local-escalation deadline From question creation through local resolution and any fork window Mark the market or action impaired, surface the deadline, and avoid asserting that honest local correction remains fundable A12 Auction demand, indicative REP/ETH depth, expected discount, and bidder operating cost Before and during every repair auction Disclose likely under-repair and plan operation of the child at only migrated collateral plus accepted auction ETH A16, A26 Inclusion delay, base fee, priority fee, reorg depth, block gas limit, and gas schedule against each immutable window and mandatory transition Continuously while a deadline or mandatory transition is live Raise transaction fees where rational, use independent submission paths, and disclose missed-deadline or stranded-transition risk A17, A18, A19 Independent corrector availability, ownership and payoff independence, scenario-dependent WETH and REP replacement capital, reference-market depth, manipulation cost, correction profit after fees/gas/impact, transaction budget, and settlement inclusion Continuously while a report is pending or a cached price can authorize operations Block or discourage new price-sensitive operations and disclose that an incorrect report may settle A21, A22 Mainnet external-token code and behavior; Sepolia reviewed Genesis REP/WETH runtime bytecode, constructor allocations, and wiring; WETH redeemability; burn-sink status; recipient ETH/ERC-1155 compatibility At deployment verification and before first use of a new recipient or integration Reject the deployment or incompatible recipient; do not rely on redirected recovery A20, A23, A24, A27, A28 Pinned compiler and toolchain identity; reproducible-build output, reviewed bytecode hashes, release manifest, deployed runtime code and wiring; independent reorg-aware RPC/event replay; hash and CREATE2 verification; signer, typed-data domain, nonce, witness, session, and approval inventory At release, on every environment or account change, and continuously for indexers Stop signing or operating, reject an unverifiable deployment, revoke compromised authority where possible, and re-establish canonical state from independent sources A15 Immutable question ID and metadata, evidence plan, and pool relevance Before pool selection Reject or warn on the selection; do not represent an admitted question as relevant or resolvable A25 Deployed immutable values and every documented economic, timing, integer-width, and gas relationship Before deployment and after any change in external gas or market conditions used by the model Treat the immutable deployment as unsupported; there is no administrative parameter repair","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"immutable-protocol-release-posture","heading":"Immutable Protocol Release Posture","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Zoltar and Statoblast are intended to launch as immutable, permissionless contracts. Launch documentation should not assume an operator can pause, upgrade, roll back, or disable protocol behavior after deployment. Release work should instead publish verifiable provenance: final commit and tag, deterministic addresses, CI and local QA results, production UI artifact hash, and any known limitations. UI deployments may describe data freshness and route users to verified artifacts, but they must not be documented as emergency controls over the protocol. This is the protocol's EG05 no-pause, upgrade, rollback, or rescue boundary .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"launch-release-checklist","heading":"Launch Release Checklist","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before tagging a launch release, run bun run ui:build:prod from a fresh dependency install and run bun run check:mainnet-deployment . This command fails when either generated deployment manifest is stale. Refresh and review both manifests with bun ./scripts/check-mainnet-deployment.mts --write before rerunning the check. That command writes and checks both mainnet-deployment-addresses.json and sepolia-deployment-addresses.json . Use the pinned compiler configuration in solidity/ts/compile.ts , the exact tool versions in solidity/package.json , and the imported OpenOracle profile in UPSTREAM.md . Publish the reproducible-build runtime hashes with the release, keyed to the manifest addresses, and compare both a clean local rebuild and deployed runtime code with those reviewed hashes before marking the release canonical. Push the final v* tag only after the CI Gate succeeds on the same commit. The Build and Push to IPFS workflow must succeed for that tag, and the resulting GitHub release must record the IPFS hash emitted from the published artifact. Production UI release notes should state that ?simulate=1 is a browser-local sandbox. Mainnet and Sepolia quote-dependent actions use live RPC data plus available network-local Uniswap liquidity, while simulation uses its configured mock where supported. The OpenOracle integration guide owns the quote-source order and network-address details. RPC and client integrity remain covered by A24 , while quote availability and representativeness are official-client limitations rather than protocol security assumptions. Stale, unavailable, or unsupported quotes are production blockers for the affected client action, not inputs that should be replaced with simulation prices.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"genesis-rep-deployment","heading":"Genesis REP Deployment","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Sepolia genesis REP allocations are canonical source data in sepoliaRepAllocations.ts . The configured 11 million REP mint cap is divided equally among the listed holders; adding a holder automatically reduces every holder's allocation. Integer division may leave the total minted supply below the cap by less than one attoREP per holder. Review both the holder list and the computed bigint total before deployment. The GenesisReputationToken constructor requires at least one allocation, matching holder and balance array lengths, unique nonzero holders, nonzero balances, and a total supply no greater than 11 million REP. It emits the standard ERC-20 mint Transfer event once for each allocation. The sum of the constructor balances is immutable and is returned by getTotalTheoreticalSupplyAttoRep() . There is no administrator, post-deployment mint, allocation correction, or recovery entrypoint. An incorrect allocation must be fixed in the source list before deployment and the deterministic manifests must then be regenerated. Because constructor arguments are part of CREATE2 init code, any allocation change produces a new genesis REP address and new addresses for every deployment step whose constructor wiring depends on it. Never continue a deployment from a manifest generated for a different allocation list. A future release that intentionally changes starting holders must follow the same source review and full-manifest regeneration procedure.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"security-pool-guardrails","heading":"Security Pool Guardrails","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool creation, minting, withdrawal, and direct-ETH rules are collected here in contract-first form. 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, 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 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 derived from total REP capacity ownership, the current REP/ETH price, and the pool multiplier. Total ownership includes an unclaimed truth-auction allocation, so it can provide minting headroom before the vault owner claims it. Claiming later moves that already-counted ownership into fee eligibility without adding it to total ownership again. The pool updates accounting before the share-token mint call. SecurityPool.createCompleteSet , SecurityPool.depositRepToVault Fee accrual clamp Fee accrual is clamped to the question end time while the universe is unforked; after a universe fork, the accumulator is clamped to the fork time. SecurityPool.sol Fee accrual and rounding Only vault-assigned capacity ownership is fee-eligible. Settlement-collateral decay first enters an unallocated reserve; vault checkpoints 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, while capacity-ownership-scoped index carry is cleared when capacity ownership attribution changes so old-denominator dust is not reassigned. After a fork permanently ends accrual, the pool tracks capacity ownership still awaiting the final index; once every eligible vault checkpoints, any whole reserve attoETH that no vault can individually claim returns 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 auction ownership therefore already affects utilization. A later claim changes fee eligibility but not 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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"share-migration","heading":"Share Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"escalation-resolution-and-deposits","heading":"Escalation Resolution and Deposits","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Accepted deposits, edge-case resolution results, and carry-proof or residual-REP consumption all live in this section. 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 transition has occurred. 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. The game closes further deposits. With a fixed child outcome it follows the continuation clock and cannot trigger its own fork; without one, canTriggerOwnFork() returns true directly. EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances , EscalationGameCalculations.getEscalationGameEndDate , EscalationGameCalculations.canTriggerOwnFork 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. One mantissa/exponent ratio applies every intervening auction haircut exactly once to both proof amount and cumulative reward position without walking ancestors, and excludes losses before a continuation-local deposit was created. Consumption asks the immediate source game for retained cumulative-prefix differences, so per-leaf source-basis allocations telescope exactly to the aggregate checkpoint in every proof order. 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 freezes the carry root/count and retention metadata and performs no claim or owner import. The 64 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 , EscalationGameCarry._consumeCarriedDeposit Optional unresolved parent escalation-deposit accounting cleanup The public SecurityPoolForker.migrateVaultWithUnresolvedEscalation wrapper first runs ordinary migration for that same vault: REP backing units and capacity ownership move 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, unresolved principal is cleared, and no escrow remains, residual REP in the escalation game can be swept back to the security pool. EscalationGameSettlement.sweepResidualRepToSecurityPool","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"fork-migration","heading":"Fork Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool-level migration mechanics after a universe fork live here: 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 Fork Migration Proportion and Fork Collateral Ceiling own checkpoint order, cumulative allocation, and repair derivation. 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 the canonical fork-migration derivation and 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, retention checkpoint, 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 Proofs . 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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"truth-auction-operations","heading":"Truth Auction Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"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. Only vault-assigned capacity ownership accrues; each claimed auction capacity ownership joins incrementally at the current fee index. A delayed claim adds to the pool’s live eligible total, so capacity ownership changes or liquidations since activation are preserved rather than replaced by fork-time counters. SecurityPool.sol , SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"repeth-oracle-operations","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The coordinator quick reference below covers staging, callback recovery, stale-operation handling, and liquidation boundaries. Report sizing, request cost, and current OpenOracle parameters are canonical in OpenOracle Integration . Area Implementation behavior Source Atomic initial report The sponsor funds the ETH bounty and WETH/REP position; the coordinator 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 Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and EIP-712 permits from EOAs or ERC-1271 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 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 , SecurityPoolLiquidationDelegate.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"support-module-inventory","heading":"Support Module Inventory","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The explanation focuses on protocol flow. The table below names the remaining support contracts that matter for integrators, code reviewers, and interface inventory work.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"caller-and-trust-boundaries","heading":"Caller and trust boundaries","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Deployment helpers are not interchangeable with canonical protocol discovery. Several are intentionally permissionless and namespace CREATE2 salts by msg.sender ; an application must discover canonical instances through SecurityPoolFactory , recognized pools, and recognized forker events rather than treating every factory-created address as canonical. Module Direct caller boundary Canonical-use boundary EscalationGameFactory deployEscalationGame and deployEscalationGameFromFork have no permission check, but a successful caller must implement the ISecurityPool reads used during construction; an EOA or incompatible contract reverts. The caller is bound into the new game as its securityPool ; the factory itself becomes the game's immutable owner and immediately calls start or startFromFork . Ordinary deployment requires a threshold above one attoREP and lowers a start bond at or above that threshold to threshold - 1 . The factory has no owner role and no later resumeFromFork relay. Accept a game only after EscalationGameSet from a recognized pool. In the supported continuation path, the owning pool calls resumeFromFork , which also verifies aggregate funding. ShareTokenFactory Anyone can call deployShareToken to deploy or retrieve a token under keccak256(abi.encode(msg.sender, salt, questionId)) ; that caller is the token's initial authorized address. Origin lineage tokens are the instances created by SecurityPoolFactory and named in DeploySecurityPool ; children reuse the parent's token. UniformPriceDualCapBatchAuctionFactory Anyone can call deployUniformPriceDualCapBatchAuction . Its salt is namespaced by caller and supplied salt, and its operational owner is the caller-supplied owner address. During external-universe initiation, the source must be authorized by its declared share token; own-game initiation does not perform that check, and neither relationship proves configured-factory registration. Child linking requires a deployed auction that this forker has never trusted before and preserves the source's factory and forker. Canonical pools remain the instances registered by the configured SecurityPoolFactory . PriceOracleManagerAndOperatorQueuerFactory Anyone can call deployPriceOracleManagerAndOperatorQueuer under a caller-namespaced salt with caller-supplied OpenOracle, REP token, and positive initialReportPriorityFeeAttoEthPerGas ; coordinator construction rejects zero and values that would consume the reserved OpenOracle uint128 report and escalation-halt capacity. The caller therefore chooses this immutable gas-price assumption within that bound. A pool coordinator is the instance named by canonical DeploySecurityPool ; SecurityPoolFactory atomically binds it to that pool before returning, and canonical child deployment inherits the value from the parent coordinator. SecurityPoolDeployer and SecurityPoolDeploymentWorker Only the configured SecurityPoolFactory may call the deployer; only that deployer may call its worker. They are code-size deployment plumbing. Pools become canonical only when the factory emits SecurityPoolRegistered and DeploySecurityPool . SecurityPoolMigrationProxy Only its immutable owner , the SecurityPoolForker that created it, may call lockRep , forkUniverse , splitToChild , or sweepChildRep . Its deterministic per-parent address is exposed by getMigrationProxyAddress and recorded in fork snapshot events; users do not call it. EscalationGameDepositDelegate , EscalationGameClaimDelegate , EscalationGameForker , SecurityPoolForkerVaultMigrationDelegate , and SecurityPoolLiquidationDelegate Their public methods are delegatecall implementations. The deposit delegate implements recordDeposit , recordForkedEscrowForOutcome , applyTruthAuctionHaircut , funding-gated resumeFromFork , consumeEscrowedRepForOwner , consumeUnresolvedRepForClaimOwners , creditClaimOwners , and creditExternalClaimOwners . The shared claim delegate handles retention checkpoint reads and initialization; it has no ownership or import surface. The forker delegates implement escalation and vault migration. The pool helper implements funded REP-backing-unit, capacity-ownership, receiver-debt, and full-request bad-debt liquidation accounting while isolating fees and claims, plus permissionless continuation resume. Direct calls operate on isolated or uninitialized helper storage and normally revert; they are not protocol actions. Accept resulting state and logs only when the recognized game, pool, or forker executes the matching wrapper through delegatecall . State changes and events then occur in that recognized contract's context. Canonical liquidation enters through SecurityPool.performLiquidation ; continuation progress enters through SecurityPool.resumeForkedEscalationGame . SecurityPoolEventEmitter.sol emitPoolAccountingCheckpoint , emitVaultAccountingCheckpoint , and emitForkSnapshotEvents are externally callable and payable , but direct calls execute against the helper's own storage and emit from the helper address. Payability permits delegatecalls from value-bearing protocol flows; callers must not send ETH directly because it is not protocol collateral and the helper has no recovery surface. Pools and the forker use constructor- or factory-installed modules through delegatecall , so logs are emitted from the recognized pool or forker address. Indexers must reject matching signatures from the helper or any unrecognized emitter. External-universe fork initiation requires declared-share-token authorization; own-game initiation does not. Child linking preserves the source factory and forker and rejects undeployed or previously trusted auctions. Configured-factory registration remains the canonicality boundary. Storage-layout tests protect the delegate's fixed slots. EscalationGameProofVerifier , MerkleMountainRange , and SecurityPoolUtils Stateless or library math and proof routines have no lifecycle authority. Public verifier calls can be used for previews, but do not mutate a game or pool. A result becomes protocol state only through a recognized game, pool, or forker transaction and its events. DeploymentStatusOracle Anyone can call getDeploymentMask ; the constructor alone fixes the ordered address list and emits DeploymentAddressesSet . It reports code presence only, not canonical wiring or readiness. Decode it using its constructor event or the matching deployment manifest order.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"securitypoolutils-read-surface","heading":"SecurityPoolUtils read surface","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"These external pure previews distinguish REP-denominated capacity ownership from ETH-denominated open interest. REP-per-ETH prices use PRICE_PRECISION , and health factors and security multipliers use basis points. The previews have no lifecycle, caller, freshness, or canonical-pool authority; only a recognized pool or coordinator transaction can apply their result. Function Exact boundary behavior Conceptual owner calculateCumulativeAuctionBadDebt(auctionedBadDebtAttoEth, nextClaimedCapacityOwnershipAttoRep, auctionedCapacityOwnershipAttoRep, previouslyClaimedBadDebtAttoEth) Assigns auctioned bad debt from cumulative claimed capacity ownership, so claim order cannot change the total. Intermediate claims round down; the final claim receives the exact residual. Truth-auction settlement calculateFeeAccrual(settlementCollateralAttoEth, retentionRate, timeDelta, indexRemainder, feeEligibleCapacityOwnershipAttoRep, feesOwedRemainder) Applies fixed-point retention over elapsed time, carries the global index remainder, and returns whole credited fees plus the next fee remainder. Canonical callers avoid zero eligible capacity ownership before this division. Fee accrual calculateVaultFee(capacityOwnershipAttoRep, feeIndexDelta, remainder) Floors the vault's whole fee credit and returns the remaining fixed-point numerator for its next checkpoint. Fee accrual calculateMintingCapacityAttoEth(capacityOwnershipAttoRep, repEthPrice, securityMultiplierBps) Converts aggregate REP-denominated ownership into current ETH capacity using the live REP-per-ETH price and pool multiplier. Zero ownership or price returns zero. Dynamic capacity calculateVaultOpenInterestAttoEth(activeOpenInterestAttoEth, vaultCapacityOwnershipAttoRep, totalCapacityOwnershipAttoRep) Attributes live pool open interest pro rata to capacity ownership, rounding a positive vault share upward. Zero vault or total ownership returns zero. Dynamic capacity calculateBundledLiquidationTransfer(targetBackingUnits, targetCapacityOwnershipAttoRep, targetOpenInterestAttoEth, requestedDebtAttoEth, repEthPrice, currentPoolHeldAttoRepBalance, currentTotalRepBackingUnits, minimumRemainingAttoRep) Returns zero when the request, target open interest, capacity ownership, or price is zero. Caps a nominal debt quote by target open interest and by the complete 5%-bonus award fundable from target pool-held REP, then rounds proportional capacity ownership downward. Execution derives moved debt separately from the receiver's exact live open-interest increase. Partial requests preserve the configured REP minimum. On a full-target request, bad debt is target open interest minus exact moved debt and can include both an award-unfunded slice and integer-allocation residue. Vault liquidation isVaultHealthy(poolHeldVaultRepBackingAttoRep, disputeStakedAttoRep, openInterestAttoEth, repEthPrice, poolSecurityMultiplierBps) Checks the associated-REP and free pool-held REP requirements against live open interest. Both requirements round upward; zero open interest is healthy. isVaultHealthyAtFactor applies an approval-selected factor of at least 10,000 to both branches. Capacity and health calculateRetentionRate(settlementCollateralAttoEth, mintingCapacityAttoEth) Zero live minting capacity returns the maximum retention rate. Otherwise retention decreases linearly through the 80% utilization dip and remains at the minimum rate above it. Retention rate Area Named contracts and modules Role Deployment tracking DeploymentStatusOracle.sol Reports one code-presence bit per configured deployment-step address. See Deployment Status Oracle . Carry proof hashing MerkleMountainRange.sol , EscalationGameProofVerifier.sol , EscalationGameTypes.sol Defines the carried-deposit leaf shape, the 64-peak limit, proof-length rules, and nullifier-root replay protection. See Merkle Mountain Range carry proofs . Escalation game composition EscalationGame.sol , EscalationGameCalculations.sol , EscalationGameCarry.sol , EscalationGameClaimDelegate.sol , EscalationGameDepositDelegate.sol , EscalationGameEscrow.sol , EscalationGameSettlement.sol , EscalationGameState.sol , EscalationGameStorage.sol Splits the game across immutable ownership and storage, claim and deposit delegation, calculations, continuation proofs, escrow, and settlement without exposing those internal modules as separate canonical games. ERC-20 support ERC20.sol , IERC20.sol , IERC20Metadata.sol , Context.sol , SafeERC20Ops.sol , GenesisReputationToken.sol , ReputationToken.sol Base REP token implementation, interfaces, metadata, execution context, safe transfer wrappers, the constructor-allocated genesis token with fixed theoretical supply, and the inherited transfer , approve , and transferFrom entrypoints. ERC-1155 and token ids ERC1155.sol , ShareToken.sol , TokenId.sol , IERC1155.sol , IERC1155Receiver.sol , IERC165.sol , IShareToken.sol Outcome-share token plumbing, interface support, token-id encoding, inherited setApprovalForAll , and both single and batch safeTransferFrom forms. Factories and deployers EscalationGameFactory.sol , SecurityPoolFactory.sol , SecurityPoolDeployer.sol , ShareTokenFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , PriceOracleManagerAndOperatorQueuerFactory.sol Deterministic deployment entrypoints for pool, share-token, oracle-coordinator, escalation-game, and auction instances. Migration, liquidation, and storage modules SecurityPoolStorage.sol , SecurityPoolLiquidationDelegate.sol , SecurityPoolMigrationProxy.sol , SecurityPoolForker.sol , SecurityPoolForkerAuctionSettlementBase.sol , SecurityPoolForkerBase.sol , SecurityPoolForkerStorage.sol , SecurityPoolForkerTypes.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForkerVaultMigrationDelegate.sol , EscalationGameForker.sol , SecurityPoolEventEmitter.sol , LiquidationApprovalRegistry.sol , SignatureValidation.sol Defines shared pool storage, delegated liquidation accounting and bounded approval reservations, ECDSA/ERC-1271 signature validation, funded continuation delegatecalls, fork-time state, truth-auction settlement, vault and unresolved-escalation migration, event encoding, and stable proxy identity used while routing parent state into child pools. Protocol interfaces IEscalationGame.sol , ISecurityPool.sol , ISecurityPoolForker.sol , ISecurityPoolForkerChildEscalationGameInitializer.sol , IUniformPriceDualCapBatchAuction.sol , IWeth9.sol , IAugur.sol Typed boundaries used by games, pools, migration delegates, auctions, WETH integration, and external Augur compatibility. Protocol utilities Constants.sol , ScalarOutcomes.sol , BinaryOutcomes.sol , SecurityPoolUtils.sol , Multicall3.sol , WETH9.sol Shared constants and math, outcome representations, Multicall3 aggregate , tryAggregate , tryBlockAndAggregate , blockAndAggregate , aggregate3 , aggregate3Value , and block-context reads, plus standard WETH9 deposit , withdraw , approval, and transfer support. Imported oracle integration UPSTREAM.md , OpenOracle.sol , Errors.sol , ISignatureTransfer.sol , interfaces/IERC20.sol , interfaces/IERC165.sol , token/ERC20/IERC20.sol , IERC1363.sol , SafeERC20.sol , Panic.sol , ReentrancyGuard.sol , StorageSlot.sol , utils/introspection/IERC165.sol , Math.sol , SafeCast.sol OpenOracle's imported source, exact SlimStorage revision, compiler profile, and dependency provenance are owned by UPSTREAM.md . The coordinator submits attoETH/attoREP reports with dispute history and full game storage enabled; OpenOracle's packed event stream remains separate from Zoltar replay.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"","heading":"","keywords":["deployment","bitmask","runtime bytecode","status"],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"DeploymentStatusOracle is a deployment-progress helper. It does not measure protocol readiness or initialization success. It only reports which configured deployment-step addresses currently contain runtime bytecode. The canonical mainnet and Sepolia manifests list this contract directly, so operators and frontends need a precise explanation of the returned bitmask and the built-in 256-step ceiling.","title":"Deployment status oracle","topic":"Deployment","weight":1},{"fragment":"mask","heading":"What getDeploymentMask() Returns","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor stores one ordered address[] deploymentAddresses . getDeploymentMask() loops over that array and checks deploymentAddresses[index].code.length . When code exists, the oracle sets bit index in the returned uint256 . This is a code-presence bitmap. A set bit means code exists at the configured address. A clear bit means the address currently has no code. The oracle does not verify constructor args, ownership wiring, or post-deployment setup. A row of deployment steps on the left maps by index into bit positions in a uint256 mask on the right. Each step sets its bit only when code exists at that configured address. Deployment Status Mask Each configured deployment step uses one bit position in the returned word. The oracle sets that bit only if the step's address currently contains code. deployedMask = ∑ i = 0 codePresent ( i ) ⋅ 2 i Each configured deployment step contributes one bit position to the returned word.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"ordering","heading":"How Bits Map To Deployment Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Bit positions are array positions. Bit 0 maps to the first constructor address, bit 1 to the second, and so on. Offchain decoding must use the same ordered list that seeded the oracle. The constructor emits DeploymentAddressesSet(address[] deploymentAddresses) with that exact ordered list. It is the onchain source for recovering a particular oracle instance's bit mapping; the manifest below describes the current planned deployment and must match the constructor event for that instance. The current UI and deployment helpers derive the constructor list from deploymentSteps with one exception: the oracle does not include its own address in its constructor array. The manifest still lists deploymentStatusOracle as a deployment step, but the UI tracks that step out-of-band and starts consuming mask bits from the remaining addresses. The tables below render the complete bit mappings directly from the canonical mainnet and Sepolia deployment manifests, excluding deploymentStatusOracle . Each manifest's ordered deploymentSteps array is the canonical mapping for that network. Bit Mainnet step id Label Status in entered mask Loading the canonical manifest… Bit Sepolia step id Label Loading the canonical manifest… If the manifest's constructor order changes, the rendered bit meanings change with it; this page does not maintain a second list. The manifest's derivedContracts list is separate. Those addresses are not part of the deployment-status mask unless they also appear in the constructor array. Decode a deployment mask Mask value Loading the canonical mapping… Decoder controls will be available when the mapping loads. Retry loading the canonical mapping","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"limit","heading":"Why The Cap Is 256 Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor requires deploymentAddresses.length <= uint256(type(uint8).max) + 1 , which is 256 . The limit exists because the result is a single uint256 , and each tracked step consumes one bit. Indices 0...255 are supported. A deployment process with more than 256 tracked addresses must split status across multiple masks or use a different representation.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Interpretation and Verification Boundaries","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Evidence What it proves What it does not prove Bit set Runtime bytecode exists at the configured address. Correct bytecode, constructor values, wiring, permissions, or operational readiness. Bit clear No runtime bytecode was observed at that address by this call. Whether the address is intentionally empty or the manifest is wrong. Manifest match The decoder used the intended ordered address list for the network. That deployed code matches the release or that RPC responses are honest. Full deployment verification Code hashes, required wiring, permissions, and manifest provenance can be checked together. Economic safety or future operational liveness. This page documents the deployment-status bitmask only; it does not provide complete bytecode, wiring, permission, or manifest verification. The getDeploymentMask() return type is uint256 ; the constructor reverts when more than 256 addresses are supplied.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Static decoder test vectors","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Mask Meaning 0x0 No tracked constructor address contains runtime code. 0x1 Only constructor address index 0 is present. 0x5 Constructor address indices 0 and 2 are present. 2^255 Only the highest supported bit, index 255, is present. The interactive tables require JavaScript. The ordered source lists are available in the Mainnet and Sepolia manifests; bit i always corresponds to the i th constructor address after excluding deploymentStatusOracle .","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"","heading":"","keywords":["MMR","carry proof","hashing","nullifier"],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Statoblast uses a Merkle Mountain Range only for inherited escalation carry. Parent escalation games export unresolved deposits into compact snapshots, and child continuations later verify withdrawals against those snapshots without replaying the full parent history onchain. The hashing primitives live in MerkleMountainRange.sol . Snapshot storage, proof structs, peak bounds, and replay protection live in EscalationGameTypes.sol , EscalationGameCarry.sol , and EscalationGameProofVerifier.sol .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":1},{"fragment":"leaf-shape","heading":"Leaf Shape And Hash Order","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each leaf hashes one unresolved deposit with depositor , outcome , amount , parentDepositIndex , cumulativeAmount , and sourceNodeId . Leaf hashes use keccak256(abi.encode(...)) . Parent hashes use keccak256(abi.encodePacked(left, right)) . Field Why it is committed depositor Binds the proof and payout to the original depositor. This ownership is immutable: liquidation moves only pool-held vault REP backing and cannot acquire or split the committed escalation claim. outcome Separates invalid, yes, and no carry domains. amount Commits to the source principal. parentDepositIndex Provides the stable identity later consumed by nullifiers. cumulativeAmount Preserves payout-order prefix data. sourceNodeId Distinguishes otherwise identical leaves copied from different local nodes. Hash order matters. Internal nodes always hash left before right , so proofs are position-sensitive inside each peak.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Normative Encoding","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The leaf ABI types are (address,uint8,uint256,uint256,uint256,uint256) , in the order depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId . outcome is the Solidity enum ordinal. Leaf hashing uses standard ABI word padding through abi.encode ; parent hashing uses the 64 raw bytes from abi.encodePacked(left, right) . There is no additional domain separator. leafHash = keccak256(abi.encode(depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId)) parentHash = keccak256(abi.encodePacked(left, right)) root = bagPeaks(occupiedPeaks, peakCount) Proof verification rejects an empty or out-of-range peak, leafIndex >= 2^peakHeight , a sibling array whose length is not peakHeight + peakCount - 1 , a nullifier path whose length is not 64 , a root mismatch, or an already-consumed nullifier.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Two-leaf conformance vector","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Input or intermediate Value Leaf 0 ABI values 0x0000000000000000000000000000000000000001, 0, 1, 0, 1, 0 Leaf 0 hash 0xb01042d963a0c261d64893dfb7e1f221c93ef608653cb92f9ca2c8899ca4572b Leaf 1 ABI values 0x0000000000000000000000000000000000000002, 1, 2, 1, 2, 1 Leaf 1 hash 0xe0ca39e8852dc96783e8b8f0859cfbf155338f9144ba4b9fdd6af847299fc867 Peak 1 / root 0x1c9c7f3c2c8bebf92ce393deb5dccdd253f2b10084af72defb5dcd4486832b27","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"peaks","heading":"Peaks And The 64-Peak Bound","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The carry snapshot stores one peak per set bit in the leaf count. The system fixes MERKLE_MOUNTAIN_RANGE_MAX_PEAKS = 64 , and carry initialization requires each snapshot leaf count to be less than 2^64 . That does not cap the snapshot at 64 leaves. It caps the number of peak positions. Any leaf count from 0 up to but not including 2^64 is valid because its binary decomposition fits inside 64 peak slots. A leaf count of thirteen is shown as binary one one zero one, corresponding to occupied peaks at heights zero, two, and three which are later bagged into one root. MMR Peaks The occupied peak heights are exactly the set bits in the snapshot leaf count. Those occupied peaks are later bagged into one root. To form one root, the verifier collects occupied peaks in ascending height order and then bags them from right to left with bagPeaks() . snapshotLeafCount < 2 64 The 64-peak constant means each inherited snapshot leaf count must fit below 2^64 . Local carry appends behave like binary addition with carries: a new leaf merges upward through occupied lower peaks until it finds the first empty peak slot. If that upward carry would reach height 64 , the append reverts with MMR too tall .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"proofs","heading":"Proof Structure","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"A carried-deposit proof has two parts. First it proves membership in the inherited MMR snapshot for one outcome. Then it proves that the same parentDepositIndex has not already been consumed in this continuation's nullifier tree. Component Purpose leafIndex , merkleMountainRangePeakIndex , merkleMountainRangeSiblings Proves the carried deposit belongs to the inherited snapshot root for that outcome. nullifierSiblings Proves the nullifier leaf is still empty so the carried deposit cannot be replayed. The membership lane combines the carried deposit leaf with bottom-up siblings inside its selected peak, then with other occupied peak roots in ascending height order to reconstruct the snapshot root. The nullifier lane hashes the stable parent deposit index and combines it with a fixed-depth sibling path to prove the claim has not already been consumed. Carry Proof Anatomy The MMR sibling array first reconstructs one selected peak and then supplies every other occupied peak. The independent 64-level nullifier path prevents the same stable deposit identity from being consumed twice. The index semantics are stricter than the field names might suggest. merkleMountainRangePeakIndex is the occupied peak height, not the ordinal position of that peak among occupied peaks. leafIndex is the leaf's offset inside that selected peak, so the verifier requires leafIndex < 2^merkleMountainRangePeakIndex . For example, if the full snapshot has 13 leaves, its occupied peak heights are 0 , 2 , and 3 . A proof for a leaf inside the height- 2 peak uses merkleMountainRangePeakIndex = 2 and a peak-local leafIndex in 0...3 , not the leaf's global index across all 13 leaves. merkleMountainRangeSiblings is also ordered in two phases. The first merkleMountainRangePeakIndex entries are the bottom-up Merkle siblings inside the selected peak. The remaining entries are the other occupied peak roots in ascending peak-height order, skipping the selected peak height. That exact ordering is why the verifier checks merkleMountainRangeSiblings.length == peakHeight + peakCount - 1 . Separately, the verifier requires nullifierSiblings.length == NULLIFIER_DEPTH with NULLIFIER_DEPTH = 64 . Nullifier paths are keyed by uint256(keccak256(abi.encode(parentDepositIndex))) , so the stable parent deposit index is the replay-prevention identity. Plan a carry proof Choose a snapshot leaf count and one occupied peak. The planner reports the peak-local index bound and the exact sibling-array lengths enforced by the verifier. It does not construct hashes or replace an offchain proof builder. Snapshot leaf count Occupied peak height Height 2 Peak-local leaf index Binary leaf count 1101₂ Occupied peak heights 0, 2, 3 Selected peak capacity 4 leaves; local indexes 0…3 MMR sibling hashes required 4 Nullifier sibling hashes required 64 Selection Valid peak-local index","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"snapshots","heading":"Snapshots In Child Continuations","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each continuation outcome stores both an inherited snapshot baseline and a mutable current state. Field Meaning snapshotLeafCount and snapshotPeaks The immutable inherited carry commitment. currentLeafCount and currentPeaks The descendant carry snapshot after inherited snapshot initialization plus local carry appends and local carry-leaf removal. Inherited proof consumption is tracked by currentNullifierRoot and unresolved totals rather than by mutating these peak fields directly. inheritedUnresolvedTotalAttoRep and localUnresolvedTotalAttoRep The principal totals the current carry state must still represent. currentNullifierRoot The replay-protection root after any carried proof has been consumed. The continuation API exposes the current descendant carry snapshot through getForkCarrySnapshot() and pages only local unresolved leaves through getCarryLeafPageByOutcome() . The immutable inherited baseline remains in snapshotPeaks and snapshotLeafCount inside outcome state, while getForkCarrySnapshot() reports the current carry peaks, current leaf counts, current totals, and current nullifier roots after local appends and proof consumption.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"sources","heading":"Sources","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"MerkleMountainRange.sol EscalationGameProofVerifier.sol EscalationGameCarry.sol EscalationGameTypes.sol","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"","heading":"","keywords":["Statoblast","markets","security pools","overview"],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Prediction Market draft This white paper develops the Statoblast prediction-market design, its REP-backed resolution path, and its security assumptions. The documentation overview introduces the system; the sections below provide the detailed protocol model and accounting rules. Related topics: documentation overview , escalation game , Zoltar , and security model . It is designed so that: Honest reporting is economically rewarded. Dishonest reporting becomes increasingly expensive. Anyone can challenge an incorrect resolution by risking REP. Markets continue operating even when participants fundamentally disagree. Every trader can ultimately settle their position according to the outcome they believe is truthful, even if multiple realities continue to exist simultaneously. Statoblasts design is based on Artic Tern Oracle and Sisyphean Exchnge .","title":"Statoblast protocol","topic":"Protocol","weight":1},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast consists of two separate protocol identities. Zoltar is the base forking oracle layer for universes, questions, and REP branching. Augur Statoblast is the prediction market layer that adds market collateral (open interest), local disputes and fork resolution mechanisms Statoblast's lifecycle starts by creating a pool scoped to one question and universe, letting traders use it and vault owners fund its security vaults, trying to resolve disputes locally, and only then turning to fork. A question is registered in Zoltar and a Statoblast security pool is deployed for that question in one universe. Vault owners deposit REP into security vaults and select a target health factor. Their REP-denominated capacity ownership is price independent; the current REP/ETH price converts the pool's aggregate ownership into live ETH minting capacity without iterating through vaults. After the market ends, REP reporters try to resolve the market outcome locally through the escalation game. In the unfortunate case where the escalation game raises sufficient amount of REP without reaching a decision, Statoblast triggers a Zoltar fork. An unrelated external Zoltar fork can also interrupt the pool before local resolution. After a fork occurs in Zoltar, a migration period begins in each Statoblast pool. Each vault holder chooses a market outcome, migrates its backing units there, and routes a proportional share of that pool's settlement collateral . If a child pool is missing ETH collateral after migration, it sells child-universe REP through a Truth Auction to repair that collateral gap. The child pool in the branch users choose to keep using resumes operation and users settle or redeem positions there. The lifecycle is shown in the system decision-flow diagram below: operation leads to local escalation, then either settlement or fork migration, auction repair, and child settlement.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"2. Protocol participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"#","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Traders / Liquidity providers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit ETH to mint complete sets containing transferable Yes , No , and Invalid outcome shares. Pay time-based fees in ETH to vault owners for the duration that the complete sets remain outstanding. Trade by splitting complete sets into their individual outcome shares. This trading occurs outside the protocol. Burn complete sets to recover ETH before resolution, or redeem individual winning shares for ETH after the market resolves.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Vault owners","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit REP into a security pool. Provide REP-backed security capacity for traders. Receive time-based fees in ETH from traders holding outstanding complete sets. Participate in the Escalation Game after the market ends. Choose where to migrate their REP backing units and allocated share of pool settlement collateral after a fork. Keep the value of their REP backing above the amount required to secure their allocated open interest. Trigger OpenOracle games when a REP/ETH price is required for a protocol operation.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"REP holders","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Decide whether to create a security pool for a question. Decide whether to participate in an existing security pool. Split their REP into outcome-specific child-universe REP during a Zoltar fork.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"OpenOracle disputers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"OpenOracle disputers, such as arbitrage bots, monitor active OpenOracle games and challenge inaccurate REP/ETH prices when doing so offers a profitable arbitrage opportunity.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Truth auction participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"After a Zoltar fork, truth auction participants provide ETH to acquire REP-backed positions from an auction. Their ETH helps repair missing settlement collateral in a child security pool.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Liquidation receivers and operators","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"A receiver vault accepts moved security-bond debt and receives the REP backing-unit and capacity-ownership award. An operator monitors vault health, submits the liquidation, and pays its gas and oracle costs; the protocol does not compensate the operator merely for submitting. The same account can fill both roles through the self-receiving route, or a receiver can grant a bounded approval for a separate operator. See Capacity and delegated liquidation for the complete role, approval, and execution lifecycle. A target becomes liquidatable when it no longer satisfies the protocol's required backing condition: value of REP backing ≥ security multiplier × open interest secured","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"3. Security Pools","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# The purpose of security pools is to ensure that all open interest is supported by sufficient REP backing: value of REP backing ≥ security multiplier × secured open interest . This requirement protects outcome-share holders by making dishonest behavior economically unprofitable. If a vault owner attempts to manipulate the market's resolution, the REP they risk losing should be worth more than any potential gain. The protocol therefore rewards behavior that supports correct resolution and penalizes behavior that threatens it.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"dynamic-capacity","heading":"Target Health Factor and Dynamic Capacity","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Each REP deposit made for fee-earning capacity includes a target health factor of at least 10,000 BPS . The deposit adds ⌊deposited REP × 10,000 / target health factor⌋ of REP-denominated capacity ownership. Solidity rounds this quotient downward, so an extremely high target factor can make a valid deposit add zero attoREP of ownership. Capacity ownership aggregates at pool level in constant time and determines the vault's fee share. The pool's one Statoblast security multiplier and the current REP/ETH oracle quote convert total capacity ownership into ETH minting capacity: capacity ownership ÷ REP per ETH ÷ security multiplier . A higher REP-per-ETH quote means each REP is worth less ETH and therefore lowers live ETH capacity; a lower quote raises it. Neither change rewrites vaults or erases existing open interest. Vault health and liquidation instead compare live obligations against the vault's actual REP and proportional capacity ownership at the current price. Withdrawals burn capacity ownership in proportion to the REP removed. Vault owners maintain a chosen health target through deposits rather than continually editing an ETH-denominated allowance.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Fee Extraction and Retention Rate","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Being vault holder is valuable as it gives a permission to earn fees from traders that are holding complete sets or share tokens. Simply by holding REP, you are not entitled any fees in the system. Fee extraction and retention vary with vault capacity ownership and utilization; see Open Interest Fees for the complete fee curve and epoch accounting. read more from here on how fees work","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Shares and Complete Sets","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"ShareToken is an ERC-1155 contract. Each complete set mints one Invalid , one Yes , and one No share. The token id encodes the universe id and outcome, making positions fork-aware. ETH mints Invalid, Yes, and No shares. Operational pools redeem full sets, and finalized winning shares redeem through outcome settlement. Share Lifecycle Complete sets mint one Invalid, Yes, and No share; operational redemption burns the complete set, while finalized settlement redeems the winning outcome.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"escalation","heading":"4. Escalation Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# When a market ends, Statoblast attempts to resolve its outcome by running an Escalation Game based on The Isonzo Front . Participants escrow vault-backed REP behind Invalid , Yes , or No . Each deposit adds to that outcome's cumulative balance; it does not have to outbid the previous deposit or the current leader. The accepted deposit must normally meet the game's starting bond, unless it fills the selected outcome to the non-decision threshold. The cumulative binding-capital threshold increases with elapsed time, and the median outcome balance determines the scheduled end. Individual deposits still use the fixed start bond. After that deadline, a strict balance leader resolves the question. If two outcomes instead reach the non-decision threshold, the game enters local non-decision and Statoblast proceeds to its fork-resolution path. Why not fork immediately? A fork asks the whole universe to branch, so Statoblast first gives vault-backed REP a local path to settle the question. The rising cost curve makes late disagreement increasingly expensive, while the non-decision threshold keeps the fork path available when competing outcomes remain strongly funded. The Escalation Game system is an optimization, and in theory the system would work without it as well, in a more inefficient manner. read more from here on escalation game works","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"7. Forks and Migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# If the Escalation Game fails to reach consensus and enough REP has been committed to satisfy Zoltar's fork threshold, the game triggers a Zoltar fork and the fork resolution process begins. A pool can also enter fork resolution when another Zoltar application forks the same universe. The pool then migrates to the child universes, runs a truth auction if needed, and continues there. A pool moves from active operation through fork, child deployment, REP and collateral migration, auction repair, and child settlement. Fork Timeline The parent pool pauses, vault owners select child universes, and each child resumes only after migration and any required collateral repair. Fork Steps The parent pool enters fork mode. Anyone can deploy a child pool for a valid fork outcome. Vault owners can migrate their REP backing units and capacity ownership to one child of their choosing. Claimable fees remain checkpointed, funded, and redeemable in the parent vault. Unresolved ongoing escalation games fork into child pools and the games continue after a pause The child starts a truth auction after the migration deadline if collateral is missing. After the truth auction ends, winning bidders settle into REP backing units and capacity ownership. Vault owners claim winning own-fork escalation deposits; the own-fork path is open only during the parent migration window. A successful direct claim invalidates that stable deposit identity in every current and future child continuation. Shares migrate into new universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"8 week migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Vault holders have eight weeks to choose a child universe. Migration happens one pool at a time: a vault's backing units divided by the pool's total backing units determine its share of that pool's settlement collateral. No collateral moves from other pools or markets in the universe. During the migration period, vault owners choose which child universe to support by migrating their positions into the corresponding child security vault. This preserves disagreement by splitting the parent market into separate branches. However, ETH is not duplicated across those branches. Each child pool receives only the pool-level settlement collateral routed to it, migrated REP backing units and capacity ownerships, and converted child-universe REP backing that actually move into it. For this mechanism to remain secure, the value of the pool-held REP backing must exceed the value of the pool settlement collateral it moves. Otherwise, a malicious vault could shift a significant portion of this pool's collateral to a universe in which they hold favorable positions. Example: backing-unit and settlement-collateral migration Bob's vault has 5 backing units out of 20 backing units in this pool. At the fork snapshot, the parent pool has 40 REP represented by those 20 units and 10 ETH of settlement collateral, so this example uses a 2 REP per backing-unit conversion. When the oracle forks into REP A and REP B , Bob believes universe A is correct and migrates to universe A. As a result: Bob's vault receives 5 backing units ; the child pool receives 10 REP A after applying the snapshot's 2 REP per backing-unit conversion. A proportional share of this pool's settlement collateral moves to universe A: 5 backing units 20 backing units × 10 ETH = 2.5 ETH Bob's 5 of 20 pool backing units route 25% of this pool's 10 ETH settlement collateral, or 2.5 ETH, to the selected child. The remaining pool backing units route to universe B. Only this pool's collateral is divided; other markets and pools are unaffected. Their vaults receive the remaining 15 backing units ; the child pool receives the corresponding 30 REP B . The remaining proportional share of this pool's settlement collateral moves to universe B: 15 backing units 20 backing units × 10 ETH = 7.5 ETH The remaining 15 of 20 pool backing units route the remaining 7.5 ETH of this pool's settlement collateral to universe B. After migration: Universe A receives Bob's 5 backing units and 2.5 ETH of this pool's settlement collateral. Universe B receives the remaining 15 backing units and 7.5 ETH of this pool's settlement collateral. Escalation principal, rewards, carried claims, and externally supplied dispute-staked REP have zero parent-OI migration power. A losing participant would otherwise rationally route dispute-staked REP toward a false child merely to preserve valuable OI attached to it. Only pool-held vault REP backing determines a vault's migration weight. vaultMigrationPoweredAttoRep = vaultTotalAssociatedAttoRep − vaultEscalationGameAttoRep Escalation claims are excluded. Migration safety uses only pool-held vault REP backing. The effective multiplier is floored at 10,500 BPS so free backing normally funds the complete liquidation award. The exact live health rules are owned by the liquidation design . This separation lets REP perform escalation work without letting contingent claims secure parent open interest. A vault's settlement-collateral migration weight is its pool-held vault REP backing share of all pool-held vault REP backing at the fork. migrateVault transfers the vault's REP backing units and capacity ownership to one selected child and clears those two parent fields. The REP-backing share separately routes proportional pool-level settlement collateral, while the transferred capacity ownership determines the vault's live proportional open-interest allocation in the child. Claimable fees are checkpointed and remain funded and redeemable in the parent vault. The vault cannot divide or reuse its migration state across sibling universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"child-outcome-resolution","heading":"Child Outcome Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Child pools resolve outcomes through the forker. When the parent universe forks on the pool's question, each child stores its selected Zoltar outcome as the fixed result, whether the fork began through the pool-specific path or a direct Zoltar call. The pool stores and reports that result from child creation, and it is final for that pool. Pool asset redemptions begin after the child becomes operational. Question, pool, escalation, fork, child migration, auction repair, fixed-outcome settlement, and unresolved recursive continuation flow. System Decision Flow The lifecycle connects question registration, pool operation, local escalation, fork migration, auction repair, settlement, and recursive continuation when a child remains unresolved.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"Shares migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"When a fork occurs, share migration locks the source balance and accepts an ordered list of child targets. The first materialization gives each untouched selected child the full current source balance; later calls mint only that child's unmaterialized delta. Shares do not disappear from the source or split into a pro-rata remainder. On first materialization into untouched children, the full current parent balance materializes in each selected child token id while the source remains locked as an entitlement. Later calls mint only the unmaterialized delta for each child. Share Migration Share migration is not a pro-rata split across selected branches. The source balance remains as a transfer-locked entitlement after first use, and each untouched selected child first materializes the full current balance. Later calls mint only that child's unmaterialized delta.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"auction","heading":"8. Truth Auction","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# After migration, a child pool may still have less ETH than it needs to support its open interest. A Truth Auction can sell some child-universe REP for ETH to repair that shortfall. The auction has an ETH raise target and a cap on how much REP it may sell. The cap depends on migrated REP, pool-held REP, and unresolved dispute-staked REP; it may allow the full auctionable amount, reserve a small amount, apply a migration haircut, or be zero. The child resumes with the collateral actually migrated and raised. See the Truth Auction design for timing, caps, rounding, bidding, and settlement. See the collateral-repair illustrations Parent settlement collateral 50 ETH Migration-routed collateral 47.5 ETH Auction ETH raised 2.5 ETH A child pool before auction, after full auction repair, and after weak auction demand settles at the collateral actually raised. Truth Auction Balance Sheet The auction compares received settlement collateral with the parent settlement target and shows any remaining shortfall. Child collateral repair progress Collateral Repair Progress Blue shows settlement collateral routed during migration, green shows auction repair, and the dashed line marks the parent settlement target. Migration-routed collateral Auction repair Dashed line: parent settlement target Routed collateral 47.50 ETH Initial shortfall 2.50 ETH Remaining shortfall 0.00 ETH Repair status no contribution required","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"oracle","heading":"9. REP/ETH Price Oracle","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast uses a REP/ETH price to evaluate pool security. The price feed supports operations whose safety depends on REP value relative to pool settlement collateral.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"statoblast-glossary","heading":"Glossary","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Capacity ownership A REP-denominated, price-independent share of pool minting capacity and fee eligibility created from deposited REP and the selected target health factor. The live oracle quote converts it into ETH minting capacity; it is not the vault's ETH debt. Settlement collateral ETH held by a security pool to redeem complete sets and settle the winning outcome. Statoblast retrieves this price using OpenOracle ; see the OpenOracle integration explanation for the coordinator lifecycle. A REP withdrawal or liquidation either executes immediately with a valid cached price or stages behind a pending OpenOracle report. REP/ETH Oracle Flow The coordinator is a queue plus cache. A fresh price can execute immediately; a stale price moves operations into a pending-settlement path whose callback batch is capped at four operations but whose duration is unbounded under paid rolling disputes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["Zoltar","universes","questions","REP"],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Draft Zoltar is a generalized branching oracle and coordination layer for binary, categorical, and scalar questions. Rather than forcing a single answer when disagreement persists, it represents unresolved outcomes as explicit child universes. After a fork, REP holders can convert parent REP into a migration balance and use that balance to mint REP in one or more child universes. The design closely follows the Colored Coins model: the protocol branches state first, while applications and their users later coordinate which branch ultimately carries economic value. Zoltar is not itself a prediction market or an outcome-reporting oracle. Instead, it provides the shared infrastructure for questions, REP tokens, and universe forks. Any application can build on top of it and define its own local dispute process. If disagreement cannot be resolved locally, anyone able to commit the required REP threshold may fork an unforked universe using an eligible ended global question. The resulting child universes become the new foundations from which applications may choose to continue.","title":"Zoltar and branching truth","topic":"Protocol","weight":1},{"fragment":"timeline","heading":"Lifecycle Timeline","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar's lifecycle has six conceptual steps, from question registration through post-fork coordination. 1. Register question ZoltarQuestionData stores the question and defines its valid answer space. 2. A fork is triggered After the question end date, any address able to supply the threshold REP can call forkUniverse . Zoltar does not verify that a disagreement exists; applications decide when a fork is justified. 3. Parent universe forks Zoltar records the fork question and deterministically defines every valid child branch. 4. Child universes deploy lazily Branches exist by deterministic id first; contracts are deployed only when needed. 5. REP holders split Migration balances mint child REP into one or more selected branches. 6. Applications and users choose where to continue The protocol does not pick a winner. Users and applications decide where durable activity continues.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar has one responsibility: define and account for branching universes. It does not implement the market, collateral, or underwriting system built on top of it elsewhere in the repo. Its role is narrower and more fundamental: register questions encode valid answer spaces represent forks as child universes mint and burn child-universe REP turn migration balances into selected child REP In practice, an application can register a question, trigger a fork after that question ends, and use the child-universe structure that Zoltar defines. ZoltarQuestionData stores question state and answer encoding. Only Zoltar can mint the child-universe ReputationToken ; genesis REP is separately deployed and supplied to Zoltar. Zoltar can never mint more REP into a single universe than its parent has REP.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"Zoltar Contracts","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Zoltar : universe forks and REP splitting ZoltarQuestionData : question registry and outcome encoding ReputationToken : child-universe REP minted and burned by Zoltar ScalarOutcomes : scalar formatting and interpolation logic","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"universe-model","heading":"2. Universe Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# A universe is one branch of protocol state. The Zoltar.Universe struct stores the minimum data needed to identify that branch and connect it to its parent: forkTime : when the universe forked forkQuestionId : the question id recorded by forkUniverse and used to define child branches forkingOutcomeIndex : the outcome index represented by the universe when it is a child reputationToken : the REP token used inside that universe parentUniverseId : the parent branch Universe 0 is the genesis universe. Its network-specific REP token is supplied to the Zoltar constructor. Mainnet uses the existing REP deployment; Sepolia deterministically deploys a genesis REP token from the configured initial-holder allocations before deploying Zoltar . Child universes are identified deterministically as: childUniverseId = uint248 ( keccak256 ( abi.encode ( parentUniverseId , outcomeIndex ) ) ) Child universe ids are deterministic hashes of the parent universe and outcome index. Deterministic child ids make each branch reproducible from parent universe and outcome index alone. Child universes are deployed lazily when a forked branch is actually needed. Concrete balance example. Alice converts 100 parent-universe REP into a migration balance and mints 100 REP in the Yes child and 100 REP in the No child. This reproduces the parent REP claim across explicitly selected branches. Genesis ├── Yes: Alice holds 100 Yes-REP │ ├── Option A: Alice may later mint 100 Yes/A-REP │ └── Option B: Alice may later mint 100 Yes/B-REP └── No: Alice holds 100 No-REP Universe Tree Universe tree. A fork creates the full valid branch set; no branch is privileged by the contract. Forking parent Invalid branch Valid outcome branches Universe tree A fork creates the full valid branch set; no branch is privileged by the contract. Important distinction Zoltar defines the branch set. It does not select one canonical child universe or delete the others.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"global-question-scope","heading":"Global Question Scope","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Questions are global protocol objects in Zoltar. forkUniverse checks that the target universe exists, still has supply, has not already forked, and that the supplied question exists and has ended. It does not require the question to have been created for that universe. Applications that need a stricter universe/question relationship enforce it above Zoltar. Accepted eligibility boundary Zoltar intentionally accepts any existing ended global question. It does not require a minimum creation age, prior universe registration, or proof that the fork's disruption is smaller than its economic benefit. The fork-threshold REP commitment and configured haircut are the protocol's admission mechanism. A stricter relevance, aging, or cost-benefit rule is not a Zoltar invariant.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"3. Fork Thresholds and REP Economics","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# To trigger a Zoltar fork, the caller must commit the forkThresholdAttoRep : the parent universe's theoretical REP supply divided by the configured threshold divisor, rounded down. The REP threshold and haircut are the intended admission cost for a fork, including unnecessary forks that impose migration work on applications using that universe. Both the threshold and the haircut are intended to make unnecessary forks economically costly. Forking affects applications and users relying on that parent universe; each application chooses which child branch, if any, to continue using. forkThresholdAttoRep = ⌊ totalTheoreticalRepSupplyAttoRep forkThresholdDivisor ⌋ With the default divisor of 20 , this is approximately 5% of theoretical REP supply, subject to integer flooring. Triggering a fork removes the full fork threshold from the parent universe. `forkUniverse` removes `forkThresholdAttoRep` of parent REP and reduces the parent universe's theoretical REP supply by the same amount. Only part of that removed REP is credited toward REP in the child universe. forkBurnDivisor determines the haircut. With forkBurnDivisor = 5 : 20% is burnt: ⌊forkThresholdAttoRep / 5⌋ 80% becomes migration credit: ⌈4 × forkThresholdAttoRep / 5⌉ The constructor rejects forkBurnDivisor < 5 uncreditedForkHaircutAttoRep = ⌊ forkThresholdAttoRep forkBurnDivisor ⌋ forkInitiatorMigrationBalanceAttoRep = forkThresholdAttoRep - uncreditedForkHaircutAttoRep Threshold Deposit Split Threshold Deposit Split. The full parent-REP threshold leaves parent supply. Most is re-expressed as migratable child-REP credit; the haircut is not credited. Genesis REP cannot be burned natively, so the contract transfers it to the configured burn address. Child-universe REP is minted and burned directly by ReputationToken under Zoltar’s control. A child's theoretical supply is a maximum for REP that can be minted in that branch. It starts from the parent's pre-fork theoretical supply and subtracts only the uncredited haircut. Later REP added to a migration balance converts 1:1; only fork initiation pays this admission cost. A holder can voluntarily burn REP and reduce theoretical supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"Repeated-fork threshold decay","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Along one lineage, each fork subtracts its floored haircut from child theoretical supply. With threshold divisor 20 and burn divisor 5 , supply is close to 99% of the previous generation, subject to Solidity integer flooring. Repeated-fork economics. Each default fork leaves approximately 99% of the previous generation's theoretical REP supply, and the next fork threshold remains 5% of that declining supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"splitting","heading":"4. Child Universes and REP Splitting","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Once a universe forks, child universes can be deployed lazily through deployChild . A user can also add more REP into the migration balance with addRepToMigrationBalance . The core post-fork action is splitMigrationRep , which lets a holder mint child-universe REP for valid outcome indices. Supplying no outcome indices is accepted as a no-op at the Zoltar layer. For categorical questions, Invalid and any in-range categorical outcome are allowed, while out-of-range values are rejected. For scalar questions, only well-formed scalar encodings are allowed. The fork question defines the child-branch shape for the whole universe. A child branch may therefore be keyed by Invalid , a categorical outcome index, or a scalar encoding depending on which parent-universe question forkUniverse used for the fork. Colored Coins-style property The same migration balance can mint child REP in each selected child. Later value concentration determines which branch or branches matters economically. At the implementation level, splitMigrationRep validates each selected outcome against the parent universe's fork question, lazily deploys any missing child universe, and records how much of the caller's migration balance has already been minted into each child. A caller cannot mint more into one child than the source migration balance available to that caller, but the same source balance can be reproduced into multiple valid children.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"security","heading":"5. Assumptions and Security Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar is a Colored Coins -style system, so its security argument depends on user behavior and value concentration rather than on the contract being able to identify one objectively correct branch onchain. users can choose which child universe (or child universes) to continue using after a fork users prefer to continue in the universes they regard as truthful the protocol itself does not know which universe is truthful and treats all valid branches symmetrically most durable economic activity concentrates in the branch that users expect other users to keep using dishonest or abandoned branches may continue to exist, but are assumed to retain little long-term value compared with the branch that market participants keep coordinating around","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"6. Questions and Outcome Encoding","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# ZoltarQuestionData.QuestionData stores title, description, start and end time, scalar metadata such as numTicks , displayValueMin , displayValueMax , and answerUnit . Question ids are deterministic hashes of the question data. For categorical questions, that hash path also includes the sorted categorical outcome options. For scalar questions, there are no categorical labels to include, so the id is determined from the scalar question fields alone. Every call to createQuestion also requires endTime >= startTime . The contract rejects any question whose end time is earlier than its start time before it reaches scalar or categorical validation.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Categorical Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Categorical questions store sorted outcome labels. The implementation requires labels to be non-empty and strictly ordered by hash: each label's keccak256(abi.encode(label)) value must be lower than the previous label's hash, so callers provide labels in descending hash order. The contract stores the labels in outcomeLabels[questionId] . Any number of categorical outcomes can exist at the Zoltar level as long as those conditions hold.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Scalar Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Scalar questions store no categorical labels. Instead, numTicks , displayValueMin , displayValueMax , and answerUnit define the answer space. At creation time, scalar questions must satisfy numTicks > 0 and displayValueMax > displayValueMin . Onchain, each scalar answer is encoded into a single uint256 that packs: the highest bit as an invalid flag a 120-bit first payout numerator a 120-bit second payout numerator Packed Scalar Answer Packed Scalar Answer. A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks. Namespace bit First payout Second payout Packed Scalar Answer A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks . The all-zero encoding is the canonical Invalid answer for scalar questions. For a valid scalar answer, the two payout numerators must sum exactly to numTicks : firstPayoutNumerator + secondPayoutNumerator = numTicks A valid scalar answer must allocate all ticks across the two payout numerators. The all-zero word is the only canonical scalar Invalid value. Any other word with the highest bit clear is malformed, and any word with a nonzero reserved bit in 254...240 is malformed even if the payout fields would otherwise decode cleanly. At the helper and UI level, a scalar tick index named tickIndex is encoded as: firstPayoutNumerator = numTicks - tickIndex secondPayoutNumerator = tickIndex highest bit = 1 ( numTicks - tickIndex , tickIndex ) The helper encodes a tick as left and right payout numerators. ScalarOutcomes interprets secondPayoutNumerator as the position along the scalar range: displayedAtomic = displayValueMin + ⌊ secondPayoutNumerator ⋅ ( displayValueMax - displayValueMin ) numTicks ⌋ The contract interpolates the scalar answer with mulDiv , so uneven divisions round down before the value is formatted. displayValueMin and displayValueMax are stored as 18-decimal fixed-point display bounds. Formatting divides the atomic result by 1e18 and trims trailing zeroes. For example, with displayValueMin = 0 , displayValueMax = 10e18 , numTicks = 6 , and secondPayoutNumerator = 1 , the atomic value is ⌊10e18 / 6⌋ , which displays as 1.666666666666666666 rather than an unrounded real number.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"question-types","heading":"7. Question Types Supported by Zoltar","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# At the Zoltar layer, market type support comes from how questions and outcomes are encoded in ZoltarQuestionData . categorical questions, implemented as an ordered array of non-empty outcome labels binary questions, implemented as categorical questions scalar questions, implemented as a tick-based numeric range with no categorical labels","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"invalid","heading":"8. Invalid vs Malformed","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar distinguishes Invalid answers from Malformed answers. Malformed answers are rejected, while Invalid remains a valid branch and a valid final outcome. Term Meaning Effect Invalid A legitimate resolution state. Can be a valid branch and final outcome. Malformed A submitted outcome index or scalar encoding that does not fit the question’s answer space. Rejected during child-universe REP splitting and fork-aware asset branching.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["contracts","architecture","calls","assets"],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Documentation index Mental model: actors enter through pools, oracle coordination, Zoltar universes, and child repair; custody boundaries explain the call graph. This map connects the primary deployed Zoltar registry and Statoblast market contracts. It emphasizes contract-to-contract calls and asset movement, not every public user entrypoint. Factory-only deployment workers, delegate modules, event emitters, and compatibility contracts are summarized separately so the runtime path stays legible. Related reading: the generated transaction reference for exact caller and prerequisite rules, the Statoblast whitepaper for lifecycle context, and the invariant catalog for cross-contract safety properties.","title":"Contract architecture","topic":"Architecture","weight":1},{"fragment":"conceptual-model","heading":"Conceptual Model Before the Graph","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Statoblast separates identity, market accounting, price-sensitive execution, dispute escrow, and fork repair: ZoltarQuestionData stores question state and answer encoding; Zoltar manages universes, forks, and REP branches. A security pool tracks market collateral, vault accounting, open interest, and lifecycle state. The share token tracks outcome-share balances. The price coordinator stages operations that require a fresh REP/ETH price. The escalation game escrows reporting REP and tracks local dispute state. The pool forker creates and repairs child pools after a fork. One complete-set path is: a trader calls SecurityPool , the pool accepts ETH collateral, and the pool calls ShareToken to mint one share for every outcome. The event stream records the supply and accounting changes. The detailed graph below shows primary protocol interactions; supporting factories, delegates, emitters, callbacks, and compatibility contracts are summarized rather than expanded.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"overview","heading":"Primary Interaction Flow","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Arrows point from the contract initiating an interaction to the contract receiving it. The map repeats a contract when it participates in more than one phase, separating construction-time wiring, recurring runtime calls, and fork repair into three readable panels. Users and keepers enter through several of these contracts, but are omitted so the plot can focus on contract-to-contract boundaries. Contract interactions are separated into three phases. During deployment, the pool factory validates question and universe data before deploying the pool, share token, and price coordinator. During runtime, the pool manages claims and escalation escrow while guarded actions travel through the price coordinator and OpenOracle. During a fork, the share token enters the pool forker, which snapshots escalation, migrates REP through a proxy, creates and migrates child pools, and repairs backing through the truth auction. Contract Interaction Map Each panel is a distinct protocol phase; repeated contracts preserve local reading order and prevent unrelated lifecycle edges from crossing. Blue nodes are registries or oracle infrastructure, green nodes hold market state and claims, red marks local resolution, and gold marks deployment or fork coordination. Short edge labels state the action; the table below supplies its exact contract-level meaning.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"edges","heading":"What Each Arrow Means","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Initiator Receiver Phase Interaction SecurityPoolFactory ZoltarQuestionData Deployment Confirms that the question exists and is the required Yes/No categorical shape before reserving the canonical pool identity. SecurityPoolFactory Zoltar Deployment Checks the universe and resolves its REP token for the new pool and price coordinator. SecurityPoolFactory SecurityPool Deployment Deploys and records the canonical origin pool. Forker-requested child deployment copies the lineage and fork state. SecurityPoolFactory ShareToken Deployment Deploys the origin lineage token and authorizes the canonical pool; child pools reuse that same token. SecurityPoolFactory OpenOraclePriceCoordinator Deployment Deploys the REP/WETH price coordinator and wires it to the newly created pool. Zoltar ReputationToken Universe lifecycle Deploys child-universe REP, sets its theoretical supply, and exclusively mints or burns that child REP during migration and voluntary burns. SecurityPool ShareToken Market runtime Mints complete sets, burns complete sets or winning shares, and authorizes canonical child pools to continue the same lineage claims. SecurityPool EscalationGame Resolution Deploys the local game on first use and transfers vault REP into outcome-specific escrow. Settlement returns winning value or carries proofs across a fork. SecurityPool OpenOraclePriceCoordinator Risk operations Reads the coordinator's last valid REP/ETH price. Vault owners and liquidation operators stage withdrawals and liquidations directly on the coordinator before restricted pool execution; target-health deposits create capacity ownership directly at the pool. OpenOraclePriceCoordinator OpenOracle Price discovery Funds a REP/WETH report and later withdraws the settled reporter balances that it validates and returns to the report sponsor. OpenOracle OpenOraclePriceCoordinator Price settlement Calls openOracleCallback after settlement so the coordinator can validate the final report, update its cached price, and attempt bounded staged-operation execution. OpenOraclePriceCoordinator SecurityPool Risk execution After price and snapshot checks, calls the pool-only liquidation or REP-withdrawal entrypoint. Those operations update capacity ownership internally when REP backing or liquidation ownership moves. ShareToken SecurityPoolForker Share migration Initiates a source-pool fork when needed and asks the forker to create a missing canonical child before migrating a holder's shares. SecurityPoolForker EscalationGame Fork snapshot Reads carry peaks, roots, outcome balances, timing, and fork parameters so unresolved escalation state can continue in child pools. SecurityPoolForker SecurityPoolMigrationProxy Fork migration Deploys and controls the pool-specific adapter, transfers parent REP to it, and instructs it to lock, fork, split, and sweep child REP. SecurityPoolMigrationProxy Zoltar Fork migration Acts as the literal Zoltar caller whose stable address holds the migration ledger balance while parent REP is locked and split into child-universe REP. SecurityPoolForker SecurityPoolFactory Fork migration Requests canonical child-pool deployment for selected child universes. SecurityPoolForker SecurityPool Fork migration Copies or recomputes financial state, migrates vaults and collateral, and activates the selected child lifecycle. SecurityPoolForker UniformPriceDualCapBatchAuction Backing repair Starts, finalizes, and settles the child truth auction when migrated REP and inherited ETH collateral do not arrive in the required proportions.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"paths","heading":"Four Useful Reading Paths","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Deploy a market: ZoltarQuestionData and Zoltar provide the question and universe context; SecurityPoolFactory creates the pool, share token, and price coordinator. Trade and resolve locally: SecurityPool accepts ETH collateral and mints one share for every outcome through ShareToken , then escrows reporting REP in EscalationGame . Reprice and liquidate: OpenOraclePriceCoordinator obtains a report from OpenOracle , then calls a restricted execution method on SecurityPool . Continue after a fork: SecurityPoolForker migrates REP through Zoltar , asks SecurityPoolFactory for child pools, transfers the inherited state, and uses UniformPriceDualCapBatchAuction when backing must be repaired.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"supporting-contracts","heading":"Supporting Contracts Not Expanded in the Plot","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The diagram groups construction helpers and delegate modules behind the deployed component whose behavior they implement: ShareTokenFactory , EscalationGameFactory , PriceOracleManagerAndOperatorQueuerFactory , UniformPriceDualCapBatchAuctionFactory , and SecurityPoolDeployer construct the displayed runtime contracts. EscalationGameDepositDelegate , EscalationGameClaimDelegate , and the SecurityPoolForker* delegate modules execute against their owning contract's storage; they are implementation boundaries, not independent user-facing protocol hubs. SecurityPoolMigrationProxy isolates a pool's Zoltar migration balance, while SecurityPoolEventEmitter preserves pool-address event attribution under delegated fork work. WETH9 , Multicall3 , and the imported OpenOracle implementation are compatibility or external boundaries; only OpenOracle's protocol-facing relationship is shown.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"sources","heading":"Source Contracts","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"ZoltarQuestionData.sol Zoltar.sol and ReputationToken.sol SecurityPoolFactory.sol SecurityPool.sol and ShareToken.sol EscalationGame.sol and SecurityPoolForker.sol OpenOraclePriceCoordinator.sol and OpenOracle.sol UniformPriceDualCapBatchAuction.sol","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"actors-and-boundaries","heading":"Actors, custody, and one operation","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The protocol consists of several participants with distinct responsibilities. Market creators configure security pools, traders mint and hold complete sets and outcome shares, vault owners provide REP backing, liquidators repair under-collateralized vaults, auction bidders recapitalize child pools after forks, keepers execute permissionless maintenance operations, and indexers reconstruct protocol state from emitted events. Each contract handles a specific part of the protocol. Security pools hold user funds and manage market state. The Oracle Coordinator runs price-sensitive operations that need a REP/ETH price. Zoltar manages universes, questions, REP, and forks. An operation may involve several contracts, but that does not move every asset or piece of state between them. For example, when a trader mints a complete set, they send ETH to the security pool. The pool accepts the deposit, mints one outcome share for every possible outcome, and updates its accounting. Each contract changes only the state it manages, while emitted events let external indexers reconstruct the operation. The deployment graph above illustrates the primary contract interactions in this release. Supporting factories, delegates, callbacks, emitters, and compatibility contracts are omitted for clarity. Contract addresses and call relationships are deployment details, while the custody and responsibility boundaries described above remain stable across deployments.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["EscalationGame","modules","authority","accounting"],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The Escalation Game's deposit, timing, outcome-selection, and continuation rules fit into the wider prediction-market lifecycle described in the Statoblast white paper. When a Statoblast market ends, the security pool sends a valid deposit through its escalation-deposit delegate. The deposit must pass the pool's vault-backing and lifecycle checks and usually meet the configured starting bond. A smaller deposit is allowed only when it fills an outcome to the non-decision threshold . The first deposit does not resolve the market by itself. A deposit moves the local deadline only when it raises the median of the three outcome balances. A strict leader identifies the local result, but the pool can finalize it only after the deadline. If competing outcomes reach the non-decision threshold, the pool follows the fork path. Lifecycle: tentative claim → conflicting deposit → accepted amount and new deadline → local settlement or non-decision → child continuation after a fork → proof-backed claims.","title":"Escalation game architecture","topic":"Architecture","weight":1},{"fragment":"escalation","heading":"Escalation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"A vault owner with enough backing can add REP to an outcome with a valid deposit that meets the configured start bond. The deposit adds to that outcome's cumulative balance; it does not have to exceed the leader. The deadline moves only when the deposit raises the median outcome balance. Separately, the cumulative binding-capital threshold rises over time: Attrition cost is zero on days 0–2, equals the start bond on day 3, and reaches the non-decision threshold on day 52. Required support threshold A(d) = 0 for 0 ≤ d < 3; S for d = 3; S × exp(ln(T / S) × (d − 3) / 49) for 3 < d < 52; T for d ≥ 52 Here S is the configured start bond and T is the non-decision threshold. The rendered chart uses the contract's fixed-point arithmetic; this exponential expression is its readable idealization. The contract waits three days after start() before the escalation clock begins. The chart labels day 0 (game start), day 3 (activation), and day 52 (the end of the seven-week escalation interval). Try a simplified escalation round This simulator uses the contract model: a three-day activation delay, a 49-day escalation interval, and the fixed-point attrition curve. Change the configuration, then choose an outcome and requested deposit to see clipping, tie adjustment, acceptance, or rejection. Start bond 1 REP Non-decision threshold 10 REP Current Yes balance 1 REP Current No balance 1 REP Current Invalid balance 0 REP Deposit outcome Yes No Invalid Requested deposit 1 REP Days since start 0 days Configured start bond 1 REP Configured threshold 10 REP Leading outcome Yes / No Median balance 1 REP Deposit before → after Yes 1 / 2 REP; No 1 / 1 REP; Invalid 0 / 0 REP Deposit result accepted: 1 REP Activation 3 days Scheduled deadline (before → after) day 3.0 → day 3.0 Contract-model state deadline pending","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"The game end","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The game can end two ways The required escalation game increased high enough that only one side was able to keep up with it: That side wins and Statoblast finalizes on that outcome Two sides have reached the maximal deposit amount: Statoblast triggers a fork After the game ends, the payout uses binding capital , the safety boundary , and the reward-eligible cap . A winning deposit returns its principal and may receive a bonus. Any excess above the reward cap returns principal without a bonus. The game burns the winning-deposit haircut ; forked payouts can also be scaled by the fork threshold. Exact example. Let binding capital be 10 REP , so the reward-eligible cap is 10 + 10 / 2 = 15 REP . The reward pool is 10 × 3 / 5 = 6 REP , and the haircut pool is 10 × 2 / 5 = 4 REP . If the winning outcome has 15 REP and a deposit contributes 5 REP within the eligible range: Principal returned: 5 REP . Bonus: 5 × 6 / 15 = 2 REP . Winning payout: 5 + 2 = 7 REP . Haircut burned: 5 × 4 / 15 = 1.333333333333333333 REP at attoREP precision. At local settlement, a deposit position above 15 REP returns its principal but receives no bonus. If the fork threshold is 8 REP instead of the 10 REP non-decision threshold, the whole local withdrawal is scaled: 7 × 8 / 10 = 5.6 REP , rounded down to attoREP. Fork scaling can therefore reduce the final transfer below principal.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"glossary","heading":"Glossary","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"Binding capital The median REP balance across Invalid, Yes, and No. It is the amount the winning result must support and the value that sets the scheduled deadline. Non-decision threshold The configured REP balance at which two outcomes have enough support to stop local resolution and use the fork path. Safety boundary The interval above binding capital where a winning deposit can still earn a bonus. In this model it is (binding capital, reward-eligible cap] . Reward-eligible cap Binding capital plus half of binding capital. Only the portion of winning deposits up to this cap participates in the bonus calculation. Principal The REP originally deposited. A winning deposit returns its principal before any bonus is added. Haircut The REP amount calculated for burning from the reward-eligible portion of a winning deposit. It is separate from the payout amount.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"Escalation game continuation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"In AugurV2, when a fork happens, it cancels all currently running escalation games, and the players are refunded. This opens an attack vector for the oracle, discussed more in Zoltar Forks Should Not Cancel Escalation Games . The fix to the issue is to fork escalation games. This is called continuation. Escalation game continuation uses Merkle Mountain Range carry proofs and nullifier roots to migrate user game deposits, without requiring expensive data copying efforts onchain.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["liquidation","capacity","operator","receiver vault","approval","reservation","bad debt"],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Liquidation moves live ETH-denominated open interest and the corresponding REP-denominated capacity ownership away from an unhealthy vault. The receiver accepts the liability and receives the funded REP award. A separate operator may submit the transaction and pay its gas and oracle costs.","title":"Capacity and delegated liquidation","topic":"Economics","weight":1},{"fragment":"position-model","heading":"Three Distinct Roles","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The operator requests and stages the liquidation, paying that transaction's gas and any oracle cost. The receiver vault accepts moved security-bond debt and receives the capacity ownership and REP award. The target vault is the unhealthy vault whose position is reduced. If the liquidation remains staged, any account may execute it later and pay the execution transaction's gas. That permissionless executor does not replace the staged operator named by a delegated approval. The receiver and target must differ. The operator may be either one, but identity alone is not a health or Sybil-resistance boundary. For the backward-compatible path, omitting a delegated receiver makes the operator the receiver and requires no signed approval. The operator receives no liquidation ownership or REP merely for submitting the transaction. The first release assumes the operator and receiver coordinate compensation externally; the protocol neither creates a keeper marketplace nor takes a fee from the award.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"capacity-and-health","heading":"Capacity and Live Health","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A vault deposit creates REP-denominated capacity ownership from the deposited REP and its selected target health factor. The pool totals capacity ownership in constant time. The live REP/ETH oracle price and the pool Statoblast security multiplier convert that total into current ETH minting capacity. A REP price change therefore changes capacity without rewriting vaults, and it never deletes already-open interest. Vault health uses the vault's live REP backing, locally attributed dispute-staked REP, proportional live open interest, capacity ownership, the settled REP/ETH price, and the pool multiplier. The ordinary associated-REP branch counts pool-held and locally staked REP. The free-REP branch counts only pool-held REP because locked claims may lose. Required REP is rounded upward. A delegated approval may require a higher post-liquidation health factor. 10,000 means exactly the protocol minimum. The signed factor is applied to both backing branches and is checked again against live post-liquidation state at execution; a queue-time preview is never a guarantee.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"punitive-liquidation","heading":"Funded Transfer and Bad Debt","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The requested amount is ETH-denominated debt. Execution first caps a nominal quote by the target's live open interest and by the debt whose complete 5% REP bonus the target can fund. Capacity ownership is selected from that quote and rounded downward. The debt actually moved is then the exact increase in the receiver's live, upward-rounded open-interest allocation, which cannot exceed the nominal quote. On a delegated route, the coordinator additionally requires it not to exceed the staged approval reservation; the self-receiving path has no approval reservation. A positive quote that produces no receiver debt reverts. The target and receiver fee positions are checkpointed before their ownership changes; the operator is not checkpointed unless it is also one of those vaults. Delegated Liquidation Transfer Floor-rounded proportional capacity ownership and ceiling-rounded REP backing units leave the target. The authorized receiver incurs the exact reported debt increase and receives the ownership and backing units; target claims, fees, surplus, and unmatched ownership remain. Only a full-target request records residual target debt as bad debt. Liquidation accounting The operator submits the transaction, while the authorized receiver accepts the funded liability and receives the award. grossRepAwardAttoRep = ⌈ debtMoved ⋅ repPerEthPrice ⋅ 10,500 / ( PRICE_PRECISION ⋅ 10,000 ) ⌉ The gross award is converted into REP backing units with upward rounding. Because backing units are a proportional claim on the pool's current REP balance, the credited units can convert back to more current REP than grossRepAwardAttoRep ; the excess is less than the current REP value of one backing unit. On a full-target request, target-local bad debt is the target's live debt minus the receiver's exact settled increase. The residual may include debt excluded by the REP-award funding cap and a small amount left by integer ownership and open-interest allocation rounding. It is not an ETH collateral-shortfall test. Capacity ownership is conserved between the target and receiver; any ownership not transferred remains with the target. After a funded transfer, the receiver's resulting debt must meet the configured debt minimum and its REP backing must meet the configured vault REP minimum. A transfer smaller than the debt floor can still succeed when the receiver's existing position keeps its resulting debt above the floor. The target's resulting debt must be zero or meet the debt floor; when target debt remains, its REP backing must also meet the vault REP floor. These receiver or target dust checks revert instead of converting otherwise funded debt into bad debt.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"receiver-approvals","heading":"Bounded Receiver Approval","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A delegated route needs a pool-specific approval for the exact receiver and operator. It may name one target or use the zero-address wildcard for any target in that pool. Each approval defines a cumulative ETH debt limit, a per-liquidation limit, a minimum post-liquidation health factor, an activation time, an expiration time, and a nonce. The receiver may install an approval directly or sign EIP-712 typed data. The domain binds the stable name and version, chain ID, and approval registry; the typed message explicitly binds the security pool. EOA signatures use ECDSA and contract-wallet signatures use ERC-1271. A contract-wallet signature is validated when installed and becomes explicit onchain state rather than an opaque signature retained until execution. Each receiver-scoped nonce can install only one approval, and replaying it is rejected. The receiver can revoke unused approval capacity or advance its nonce floor to invalidate older nonces. Both actions prevent new reservations without cancelling reservations already attached to staged operations. Approval state exposes available, pending reserved, and permanently consumed amounts so indexers can reconstruct every transition.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"queue-semantics","heading":"Reservation and Queued Execution","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The coordinator reserves approval quota when it stages a liquidation. The reservation is no greater than the requested debt, the snapshotted target debt, the approval's remaining cumulative quota, or its per-operation limit. The approval must remain valid through the operation's latest possible execution time. Successful execution permanently consumes exactly the debt moved and releases unused reservation. A bad-debt-only result consumes no receiver quota. Expiration and every terminal failure release the full reservation, including stale target snapshots, target rescue, price-distance failure, inactive or forked pools, caught pool reverts, panics, unknown failures, and pending-report recovery. Cleanup is permissionless and recognizes expiration before requiring a valid oracle price. A terminal operation cannot release or consume twice. The coordinator snapshots target backing and ownership to prevent a stale quote from becoming a keeper windfall, then uses the settled price and live state at execution. Receiver state changes after staging can therefore cause a safe failure and reservation release.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"fork-behavior","heading":"Fork Isolation","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Approvals and reservations belong to one pool and its coordinator. They are not copied into child pools or universes. A receiver approving liabilities in one universe has not approved them in a child. Pending parent operations remain permissionlessly cleanable after the parent becomes unable to execute, releasing their reservations.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["fees","markets","vaults","accounting"],"path":"explanation/fees.html","sectionTitle":"Explanation","summary":"Understand fee accounting across markets, vaults, and protocol operations.","text":"Vault holders have a lot responsibilities, they need to maintain enough REP to cover their commitments for open interest holders, they need to participate in escalation games, fork migrations etc. The reward for this service is fees generated from open interest. The security pool sets a side fees for vault holders to claim when they like. Earned fees are not ever taken away from the vault holders. REP becomes valuable only when its used in the ecosystem. Complete-set collateral gradually decays into an unallocated fee reserve. Fee-eligible vaults accrue fees in proportion to their assigned capacity ownership. The retention rate falls as utilization rises, so security becomes more expensive as open interest consumes underwriting slack. Retention follows a piecewise heuristic. It starts high, declines linearly until 80% utilization, and then stays at the minimum retention rate. The intent is to charge more aggressively for security as utilization rises and underwriting slack falls. At zero utilization, the constants imply roughly a 10% yearly fee. Once utilization reaches the 80% dip, the annualized fee is roughly 50% and then stays flat because the on-chain retention rate is already pinned to its minimum. The UI annualizes the rate as annualFee = 1 - (retentionRate / PRICE_PRECISION)^SECONDS_PER_YEAR . Retention and utilization curve. The annualized fee rises from roughly 10% at zero utilization to roughly 50% at 80% utilization, then remains flat because the retention rate has reached its minimum. Piecewise retention formula The 80% rule marks the point where the minimum per-second retention rate begins to apply. Fee accrual is lazy: state-changing pool operations checkpoint the global fee index, and vault operations checkpoint each vault against that index. Fractional amounts remain in explicit remainders until they accumulate into whole attoETH, so callers do not need to iterate over every vault when time advances. Fee accrual is time-clamped. The pool extracts collateral into fees only until the question end time while the universe remains unforked; after the universe forks, the fee accumulator is instead clamped to the fork time. Retention-rate updates no-op outside Operational mode or when the recalculated rate is unchanged. While operational, utilization is tracked collateral divided by live ETH minting capacity, and that capacity uses total capacity ownership. An unclaimed truth-auction allocation is already part of total ownership, so it can provide minting headroom and affect utilization before the vault owner claims it. Claiming the allocation makes it fee-eligible without adding it to total ownership again, so that claim alone does not change live capacity or the retention curve. A child pool begins a new fee epoch when migration and any truth auction finalize. Only capacity ownership already assigned to a vault enters the fee denominator. Auctioned capacity ownership becomes fee-eligible when the winning bid is claimed, after that vault first checkpoints at the current index. Each delayed claim adds only its newly assigned amount to the live eligible total; it does not reconstruct that total from fork-time migration counters, so intervening capacity ownership changes and liquidations remain intact. Per-vault fractional remainders survive public checkpoints, while whole fees move from the pool reserve into totalClaimableVaultFeesAttoEth ; this keeps aggregate claimable fees equal to redeemable vault balances. The totalAccruedFeesAttoEth() view adds those assigned claimable fees to the unallocated reserve when callers need to reconcile all fee-adjusted collateral. A fork permanently closes the parent's fee epoch. The pool tracks how much eligible capacity ownership still has not checkpointed the final index. After every eligible vault syncs, any whole reserve attoETH left solely from aggregating individually sub-attoETH vault remainders cannot become redeemable, so it returns to complete-set collateral. Until the last checkpoint, that reserve remains protected by totalAccruedFeesAttoEth() . REP deposits mint proportional REP backing units, settlement collateral decay increments the fee index, and vault capacity ownerships claim fees from that index. Pool Accounting REP backing units and fee accounting use separate proportional ledgers: REP backing units track vault REP backing, which is not automatically withdrawable REP, while the fee index tracks decayed ETH settlement collateral owed to vaults. attoRepToBackingUnits ( attoRepAmount ) = attoRepAmount ⋅ PRICE_PRECISION if the ledger or pool-held REP balance is empty = ⌊ attoRepAmount ⋅ totalRepBackingUnits totalPoolHeldRepBalanceAttoRep ⌋ otherwise backingUnitsToAttoRep ( repBackingUnits ) = 0 if the backing ledger is empty = ⌊ repBackingUnits ⋅ totalPoolHeldRepBalanceAttoRep totalRepBackingUnits ⌋ otherwise The bootstrap branch establishes PRICE_PRECISION backing units per attoREP. Once both totals are nonzero, each conversion floors integer division exactly as the contract does.","title":"Protocol fees","topic":"Economics","weight":1},{"fragment":"","heading":"","keywords":["truth auction","clearing","settlement","repair"],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When a pool's migration deadline has passed, if a child pool lacks the ETH needed to fully collateralize its open interest, the child may start a truth auction. The fork may have been triggered either by a direct Zoltar fork or by the Statoblast Escalation Game. Because collateral follows migration rather than being copied in full, a child pool retains its obligations while lacking enough ETH to reopen cleanly. The truth auction sells child-universe REP to raise as much repair ETH as demand supports. A truth auction does not determine which outcome is true. It sells REP belonging to one already-defined child universe to repair that child pool's collateral. The auction has an ETH raise target and a cap on how much child-universe REP it may sell. That cap depends on migrated and dispute-staked REP. Statoblast writes winning claims back into vault state rather than paying simple token transfers, so bidders buy into the child pool and its matching capacity ownership instead of merely receiving REP.","title":"Truth auction economics","topic":"Economics","weight":1},{"fragment":"lifecycle","heading":"Operational Lifecycle","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Migration deadline passes; the child may start the auction later Bidding Bidding ends and finalization occurs one week after the auction actually starts Auction Lifecycle After migration closes, startTruthAuction takes one of two paths. When no sale is required, TruthAuctionFinalized activates the child immediately and bypasses AuctionStarted, bidding, and bid settlement. Otherwise AuctionStarted opens bidding; finalizeTruthAuction later computes clearing and activates the child, and paged calls then settle individual claims and refunds. Auction Lifecycle The start transition activates a child immediately when no sale is needed. On the repair branch, ETH bids restore collateral, auction finalization activates the child, and later calls settle winning claims and refunds. REP doesn't leave the system. Finalization accounts the ETH actually raised and rejects contribution-only ETH, so bidder settlement never depends on a donor.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Starting the auction","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The start call is permissionless but not automatic. It is valid strictly after the eight-week migration deadline and records the actual auction start time. That timestamp starts the one-week bidding window.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Bidders submit bids at discrete price ticks , where each tick represents a maximum price in ETH per REP that the bidder is willing to pay. Tick 0 corresponds to a price of 1 ETH/REP . Each increase of one tick multiplies the price by approximately 1.0001 , while each decrease divides it by the same factor. The price represented by tick tickIndex is therefore approximately 1.0001 tickIndex ETH/REP . Uniswap uses a similar tick system. Losing bids can be refunded before finalization once current demand is enough to find a clearing tick. Only ticks below that clearing tick can be withdrawn through the pre-finalization refund path; binding or potentially winning bids stay in the auction. Once a clearing tick is found, it cannot be lowered, so currently losing bids remain losing bids.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding end","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When bidding ends, anyone may finalize the auction. Finalization clears the batch auction, sends the raised ETH into the respective security pool, and computes the accounting rates used to settle bidders. The child activates with legitimate migration settlement collateral plus retained bid ETH, even when that total is below the original fork snapshot. Full repair is not guaranteed, and the remaining impairment is borne by the affected child's open-interest holders. After finalization, winning bids are written into child-pool vault accounting. Winners receive REP backing units plus their proportional share of auctioned capacity ownership, rather than a direct REP token transfer. The same capacity share carries its cumulative portion of auctioned bad debt into the winner's vault. Intermediate claims round down, and the final capacity claim receives the exact residual, so settlement order cannot change the total bad debt assigned. The forker tracks collateral received through migration and the auction separately from the child contract's raw ETH balance. ETH forced into the child does not count toward the repair target. Finalization activates the child with migrated collateral plus accepted auction ETH and rejects nonzero finalizer contribution ETH.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"How Clearing Works","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The clearing process walks demand from the highest price downward. A bid above the clearing tick wins in full, a bid below it loses in full, and bids at the clearing tick share the last fill. In the uniform-clearing branch that last fill is First in First Out within the tick, so earlier same-tick bids are consumed before later ones. Higher ticks represent higher bid prices and therefore receive priority during auction clearing. The clearing algorithm matches bids starting from the highest tick and works downward until all available REP has been allocated. The auction has two caps ETH raise cap. The auction only wants to raise ETH required to make open interest holders whole REP selling Cap. The auction can at most sell all the rep it has As soon as cumulative demand is enough to hit either cap at some price, that price becomes the clearing tick and all winning bids settle from it at one price. That is a valid uniform clearing even when the REP cap binds before the ETH repair target is fully met.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"Underfunded Clearing","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When demand never reaches a full clearing price, the auction switches into the underfunded branch. Finalization computes underfundedThreshold , stores the lowest tick whose price reaches that qualification threshold as clearingTick . Only bids at or above the cap-implied qualification threshold are retained; lower ticks refund. Qualifying bidders collectively purchase maxRepBeingSold for underfundedWinningEth , so the common effective price is their retained ETH divided by the REP cap. That effective price can be lower than the qualification threshold; the threshold is not an execution-price floor. If no ETH qualifies, every bid refunds. Sold REP is allocated between them proportionally with integer floors: “them” means the pool-held and unresolved dispute-staked REP buckets. The dispute-staked bucket receives ⌊repPurchased * disputeStakedRepBefore / combinedRepBefore⌋ , and the pool-held bucket receives the complementary remainder. This bucket split happens before REP is allocated among qualifying bidders, and each bucket keeps separate backing and retention accounting. underfundedThreshold = ⌈ attoEthRaiseCap ⋅ PRICE_PRECISION maxAttoRepBeingSold ⌉ PRICE_PRECISION = 1e18 . The ceiling tick is the bid-eligibility boundary, not an execution-price floor. Before a winner claims, its reserved auction capacity ownership remains part of the child's total capacity ownership but is not assigned to a responsible vault. It therefore provides live ETH minting headroom and affects retention utilization, but it does not accrue vault fees. Claim settlement assigns that already-counted ownership to the bidder vault and adds it to feeEligibleCapacityOwnershipAttoRep atomically without increasing total ownership again. Zero-migration full-cap REP backing units. When no vault REP migrated into the child and the auction sells every auctionable REP, the parent's inherited denominator has no migrated child-vault backing-unit attribution. The forker therefore sets auctionRepBackingUnitsPerAttoRep to PRICE_PRECISION and sets the final denominator to repAvailable * PRICE_PRECISION . Each winner's REP backing units then round-trips through backingUnitsToAttoRep to its full purchased REP, and later direct REP deposits join the same live scale. When REP remains unsold behind an inherited denominator, the forker instead derives the backing-unit rate from that residual REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"avl-tree","heading":"AVL tree","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The auction indexes bids in an AVL tree keyed by tick. The valid tick range is finite: [-524288, 524288] , or 1,048,577 possible price levels. The tree stores only submitted ticks and keeps each subtree's demand summary. Because a full tree over the range has maximum height 28 , finalize() follows bounded paths and prunes subtrees instead of looping over every possible price level.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"interactive-example","heading":"Interactive Clearing Example","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The slider example shows the uniform-clearing path, the underfunded path, and how the same bid ladder reacts when the ETH cap, REP cap, and bid sizes change. Try a simple auction clearing run The simplified calculator visualizes both finalized branches with three fixed price levels and real-number arithmetic. The clearing section specifies the contract's exact tick and cumulative-floor behavior. ETH raise cap 12 ETH REP inventory 4 REP Alice bid near 5 ETH/REP 3 ETH Bob bid near 4 ETH/REP 4 ETH Carol bid near 3 ETH/REP 6 ETH High-price bids are considered first. If cumulative demand reaches the ETH raise cap or REP sale cap, the first binding tick sets normal uniform clearing. If neither cap is reached, finalization selects bids at or above the cap-implied qualification tick and allocates the complete REP sale cap among them at one effective price. With no qualifying bids, every bid refunds. Interactive Demand Curve The stepped curve accumulates bids from the highest limit price to the lowest. The vertical rule is available REP, the horizontal rule is the current clearing or qualification price, and colored bid points distinguish accepted demand from demand below the boundary. The chart updates from the auction controls directly above it. Mode uniform clearing near 3 ETH/REP Binding condition both caps ETH retained 12 ETH Winning ETH kept not underfunded Threshold not underfunded Approximate Alice REP 1.00 REP Approximate Bob REP 1.33 REP Approximate Carol REP 1.67 REP Total REP allocated 4 REP Refunds 1.00 ETH","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"three-auction-outcomes","heading":"Three outcomes to keep separate","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"No auction is required when migrated REP already repairs the child pool. A funded auction means a clearing cap bound, not necessarily full ETH repair: compare accepted ETH with the repair cap. An escalation-allocation haircut can still apply when purchased REP is allocated to positive dispute-staked REP. An underfunded auction clears qualifying demand at or above the reserve threshold and refunds lower bids; it records the ETH shortfall, may apply the conditional escalation-allocation haircut, and cannot create missing ETH or duplicate REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["OpenOracle","price","coordinator","callback"],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle is a source for REP/ETH price for Statoblast. Open oracle supplies a fresh REP/ETH price for solvency-sensitive operations. It does not decide market truth; local escalation games and Zoltar remain responsible for truth and branching.","title":"OpenOracle integration","topic":"Architecture","weight":1},{"fragment":"openoracle-role","heading":"Why Statoblast uses OpenOracle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Statoblast needs a REP/ETH price to enforce live backing and convert price-independent REP capacity ownership into ETH minting capacity. Price-gated operations include REP withdrawals, liquidations, and certain Escalation Game deposits. A stale or easily manipulated price could allow an unsafe withdrawal or make a healthy vault appear liquidatable. A price change updates aggregate ETH capacity arithmetically and never requires a vault scan. Statoblast therefore uses OpenOracle to obtain a fresh, economically contestable REP/ETH price. OpenOracle does not prove a price by consulting a trusted source. Instead, a reporter posts a WETH/REP position, and other participants can replace an inaccurate report through an, economically motivated correction trade. Statoblast configures WETH as token1 and REP as token2 . Keeping WETH on the exact side lets the coordinator size the report directly from ETH-denominated gas costs without first assuming a REP/ETH conversion price. When a report settles, the coordinator converts the settled token amounts into REP per ETH: lastPrice = amount2 ⋅ 10 18 amount1 The settled REP amount ( amount2 ) is scaled by the fixed price precision and divided by the settled WETH amount ( amount1 ). This raw-amount ratio is valid because the configured WETH and REP contracts both use 18 decimals. The coordinator does not normalize token decimals in the settlement callback. An accepted report becomes the coordinator's cached REP/ETH price. It is reusable only while it remains inside PRICE_VALID_FOR_SECONDS , currently five minutes. The coordinator does not meter how much operation volume uses that price during the freshness window.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"statoblast-integration","heading":"Integration architecture","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Every security pool has its own OpenOraclePriceCoordinator . The coordinator connects users, the pool, and the shared OpenOracle instance. It keeps the pool's cached REP/ETH price, pending report state, staged-operation queue, and settlement callback state. Keeping this state per pool prevents one pool's pending report or freshness window from becoming a protocol-wide queue. OpenOraclePriceCoordinator is deployed with an immutable OpenOracle address. For each origin or child security pool, SecurityPoolFactory asks PriceOracleManagerAndOperatorQueuerFactory for a fresh coordinator wired to the shared OpenOracle instance, shared WETH, and that universe's REP token. SecurityPoolDeployer passes the coordinator to the pool, after which the factory calls setSecurityPool once with the nonzero pool address. Only the configured pool may seed the coordinator's inherited lastPrice . A child pool inherits the parent's numeric price as a starting value, but its settlement timestamp remains zero, so that number is unusable until the child accepts its own report. The coordinator's validity window then determines whether the child price can be used. Statoblast queues solvency-sensitive operations behind a bounded OpenOracle REP/ETH report and executes them only with a fresh accepted price. Integration Flow The coordinator is the trust boundary between Statoblast operations and OpenOracle reports. It applies guardrails before request and staging, then again after callback before execution.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Price request lifecycle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A liquidation or REP withdrawal begins at the pool's coordinator. REP deposits create capacity ownership directly from the selected target health factor and do not use this staged oracle path. The coordinator first validates and stages a withdrawal or liquidation, then either uses a cached price or opens an OpenOracle report. Validate and stage the operation. The coordinator checks the caller, target vault, requested amount, and validity window before any report is requested. Check the cached price. If the cached REP/ETH price is fresh, the coordinator attempts the operation immediately. Open a report when the price is stale. The refresh sponsor supplies the request bounty and initial WETH/REP position, and the coordinator creates the report as its initial reporter. Allow corrections. Other participants may replace the report by posting the position required by OpenOracle. Every valid replacement installs a new reporter and restarts the settlement window. Settle the current report. When its settlement deadline is reached without another dispute, OpenOracle settles the report and calls the coordinator. Accept or reject the price. The coordinator validates the report identity, history, economics, amounts, and settlement-time base fee before updating the cache. Execute queued operations. An accepted fresh price allows the coordinator to attempt the queued pool operations, each of which still performs its own current safety checks. The complete flow is operation requested → cached price checked → immediate execution or report opened → report corrected or left unchanged → report settled → coordinator accepts or rejects → operation safety checks → execution .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Staging rules","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Liquidation and withdrawal amounts must be nonzero. validForSeconds must be positive and no greater than five minutes. A liquidation's receiver vault must differ from its target vault. The operator may be the target; operator identity is not used as a Sybil-resistant boundary. Non-liquidation operations must target the caller's own vault. Staging is disabled after the security pool's local Escalation Game has resolved. A liquidation whose receiver equals its target is rejected with Receiver is target before the coordinator requests or consumes an oracle report. A self-receiving operator uses a zero approval ID; a distinct receiver requires a bounded approval.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Execution batches","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"If a cached price is valid, the coordinator stages the operation record and then attempts immediate execution. Liquidation gates, a stale queue-time snapshot, a zero-effect withdrawal, or a downstream pool check may still cause the staged operation to fail and be consumed. When a new report is needed, one settlement callback can automatically attempt up to MAX_PENDING_SETTLEMENT_OPERATIONS = 4 operations. Additional staged operations remain active outside the callback batch. If they have not expired and the accepted price remains fresh, they can be executed later with executeStagedOperation .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Escalation Game deposits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Escalation Game REP deposits do not use the staged callback path. When a pool has active capacity ownership, depositToEscalationGame requires an already-fresh coordinator price and reverts when the price is stale. After previewing the accepted REP deposit, the pool uses lastPrice for the post-transfer vault and pool coverage checks.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"request-funding","heading":"Funding and report ownership","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Report funding is caller supplied. The coordinator's getRequestPriceCostAttoEth() getter calculates the request bounty from the current base fee, callback gas limit, and the coordinator's own report processing budget: requestPriceCostAttoEth = block.basefee ⋅ 4 ⋅ ( callbackGasLimit + gasConsumedOpenOracleReportPrice ) + 101 callbackGasLimit is the gas reserved for the settlement callback, gasConsumedOpenOracleReportPrice is the coordinator's own report-price callback work, and the small 101 offset keeps the forwarded bounty strictly above the computed gas product so the OpenOracle-funded reward path has a positive buffer instead of landing exactly on the boundary. If a cached price is usable, no new oracle request cost is retained and unused ETH is refunded. If a report is needed, only the first pending settlement slot retains the request cost. requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) forwards exactly the bounty to OpenOracle and refunds any excess. The same transaction submits the initial WETH/REP position, so the refresh sponsor funds both the ETH bounty and report position up front. Both public request paths refund only a positive unused or excess amount by making a low-level native-ETH call to the caller. A contract caller must accept that callback. If it rejects the refund, the whole transaction reverts, including operation staging, immediate execution, and any report opened in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"rolling-dispute-exclusivity","heading":"Sponsor lane","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator refuses a direct requestPrice call while the cached price is still fresh, preventing a redundant report from being opened over a usable price. Once the price is stale and a report is pending, only the original pendingReportSponsor may add operations to that in-flight settlement. Those follow-up operations pay no additional join fee; other callers must wait until the report finishes. Every successful dispute replaces the current report and restarts its settlement window. The resulting liveness consequences are described under Economic tradeoffs and limits .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Report sizing and correction incentives","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle's correctness depends on profitable arbitrage correcting inaccurate prices. The coordinator sizes each report so that correcting an error at or above the target remains profitable after modeled dispute costs and OpenOracle fees, with the configured security margin. In the conservative correction direction, the true REP/ETH price is (1 + targetError) times the reported price. Before gas, correcting that report releases the fraction (targetError - fees) / (1 + targetError) of the WETH position. The sizing formula therefore multiplies the modeled gas cost of one dispute by the security multiplier and divides by that correction-profit fraction. The coordinator sizes the initial WETH position to make correction economically profitable when the price deviates enough. Every dispute resets the oracle's reporting clock, so repeated disputes can delay settlement indefinitely. The position therefore also accounts for pool open interest. The minimum initial WETH position combines three inputs: Request-block base fee: a report sized from the current block.basefee . Lineage priority-fee assumption: a separate report sized from the immutable initialReportPriorityFeeAttoEthPerGas . Pool open interest: a floor equal to one percent of the pool's current settlement collateral. The priority-fee report is added to the larger of the base-fee report and the open-interest report. The sponsor may use this minimum or request a larger initial WETH position. reportAttoEth ( gasPriceAttoEthPerGas ) = ⌈ gasPriceAttoEthPerGas ⋅ gasUnitsForOneDispute ⋅ openOracleSecurityMultiplierBps ⋅ ( percentagePrecision + targetPriceErrorForDispute ) 10000 ⋅ ( targetPriceErrorForDispute - protocolFee - reporterFee ) ⌉ openInterestReportAttoEth = ⌈ settlementCollateralAttoEth 100 ⌉ minimumToken1ReportAttoEth = reportAttoEth ( initialReportPriorityFeeAttoEthPerGas ) + max ( reportAttoEth ( block.basefee ) , openInterestReportAttoEth ) escalationHaltAttoEth = max ( ⌊ initialWethReportAttoEth ⋅ escalationHaltMultiplierBps 10000 ⌋ , openInterestReportAttoEth ) The initial-report minimum adds a report derived from the lineage's immutable initialReportPriorityFeeAttoEthPerGas to the larger of a report derived from the request block's block.basefee and one percent of current pool open interest. The multiplicative-escalation halt is the greater of the selected initial report times the configured halt multiplier and one percent of the pool's stored ETH collateral backing complete sets. The open-interest division rounds up. No REP price, expected REP price movement, or individual protected-operation notional enters the minimum WETH calculation. The immutable priority fee is selected when the origin pool is created and inherited unchanged by its child pools. Open interest contributes ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ , with OPEN_INTEREST_DIVIDER = 100 , before the priority-derived report is added. The coordinator sets initialWethReportAttoEth = max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) , uses that value as OpenOracle's currentAmount1 , and derives amount2 = ⌈initialWethReportAttoEth * proposedRepPerEthPrice / 1e18⌉ in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Formula inputs","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Formula input Current value Source block.basefee Current request block EVM block context initialReportPriorityFeeAttoEthPerGas Origin-pool parameter; the UI defaults to 10 gwei Immutable lineage configuration inherited by child pools settlementCollateralAttoEth Current pool open interest in ETH units Configured security pool OPEN_INTEREST_DIVIDER 100 (a 1% floor) Coordinator constant gasUnitsForOneDispute 300,000 gas ORACLE_GAS_UNITS_FOR_ONE_DISPUTE openOracleSecurityMultiplierBps 100,000 bps ( 10x ) Factory constructor; initial default OPEN_ORACLE_SECURITY_MULTIPLIER_BPS targetPriceErrorForDispute 500,000 / 10,000,000 ( 5% ) Factory constructor; initial default ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE protocolFee 1% Coordinator/OpenOracle report parameters reporterFee 0.1% Coordinator/OpenOracle report parameters","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Construction bounds and parameter effects","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Coordinator construction requires: positive dispute gas units a target error no greater than 100% an Open Oracle Security multiplier of at least 1x combined OpenOracle fees below the target error It also rejects a priority fee whose constant-derived report would consume the reserved uint128 report or escalation-halt capacity; half of that capacity remains available for the dynamic base-fee or open-interest component. The factory stores its deployment configuration, so an invalid parameter domain causes coordinator deployment to revert. Moving the target error toward the fee floor increases the required WETH sharply. Increasing the Open Oracle Security multiplier increases the required WETH linearly. A larger target error reduces the required position, but permits a wider error before the modeled correction incentive applies. The immutable priority fee is an operator-selected transaction-inclusion assumption, not a tip observed from the request block. Setting it too low weakens the modeled incentive for a disputer when prevailing priority fees are higher. Setting it too high raises the sponsor's initial WETH and REP requirements and the initial-report-derived escalation halt, and can make requests impractical. Child pools inherit the origin's value. At a 30 gwei base fee and a configured 10 gwei priority fee, the modeled gas cost is 0.012 ETH . After applying the 10x Open Oracle Security multiplier and the worst-direction five-percent correction fraction after fees, the minimum report with 100 WETH of open interest is 3.230769230769230770 WETH . With the configured 10x halt multiplier, the initial-report-derived escalation halt is 32.307692307692307700 WETH . Dynamic WETH report estimator This calculator mirrors the onchain formula. The deployment parameters and caller-selected amount are shown as adjustable inputs to make their effect explicit. For a deployed coordinator, the base fee and pool open interest can change from one request block to another. Block base fee 30 gwei Initial-report priority fee 10 gwei Pool open interest 100 WETH Gas units for one dispute 300,000 gas Open Oracle Security multiplier 10.0x Target wrong-price error 5.0% OpenOracle protocol fee 1.0% OpenOracle reporter fee 0.1% Requested initial WETH 0.00 WETH Escalation halt multiplier 10x Initial-derived halt 32.307692307692307700 WETH Open-interest halt floor 1.000000000000000000 WETH Minimum token1 report 3.230769230769230770 WETH Selected initial WETH 3.230769230769230770 WETH Escalation halt 32.307692307692307700 WETH Modeled dispute gas cost 0.012000 ETH Buffered gas target 0.120000 ETH Correction profit fraction after fees 3.7143% Safety state fees below target error","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Settlement validation, rejection, and recovery","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Low-level callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle invokes the coordinator through a low-level callback whose success is intentionally neither stored nor emitted. A failed callback does not automatically undo an otherwise valid OpenOracle settlement. The coordinator accepts callbacks only from the configured OpenOracle and only for the current pending report id. If those checks revert inside the low-level call, the report can still settle while the coordinator remains pending. If the settlement transaction cannot retain enough gas headroom after the callback attempt, OpenOracle reverts settlement with InvalidGasLimit . In that case, the report has not settled and coordinator recovery is not yet available.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"settlement-validation-scope","heading":"Coordinator settlement validation","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A callback that successfully enters the coordinator can still produce a rejected price. The coordinator clears the pending report id, emits PriceReportRejected , and leaves the price cache unchanged when any of the following conditions holds: the settlement base fee exceeds the request-time cap; storedGame(reportId).numReports is saturated at the uint24 maximum, reported as Counter saturated ; the final history record's WETH amount is too small to support another dispute at that record's base fee plus the configured priority fee, reported as Report uneconomic ; either settled token amount is zero; or the integer REP/ETH calculation produces a zero price. When dispute tracking is enabled, OpenOracle records each report block's base fee. If the report counter is not saturated, the coordinator selects history index numReports - 1 and checks the final WETH position against the configured security-sizing formula using that record's base fee plus the configured priority fee. It also enforces the request-time settlement base-fee cap before recording the final ratio. These checks establish only that the final WETH position meets the modeled security floor and that the callback data is structurally acceptable. They do not prove that the accepted price is externally correct, that an independent corrector exists, or that a correcting transaction will obtain inclusion.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Rejected-price cleanup","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A rejected report does not replay its pending settlement operations. Instead, the coordinator terminally fails every operation attached to that report, removes it from the active and pending sets, and releases any liquidation-approval reservation exactly once. A caller must stage a new operation to try again with a later price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Recovering a settled report after callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"When OpenOracle has already settled but the coordinator remains pending because the low-level callback did not complete, anyone can call recoverSettledPendingReport . Recovery clears the pending report, sponsor, and base-fee cap; withdraws coordinator-owned OpenOracle balances; and terminally fails every pending settlement operation attached to the report. Recovery does not treat those operations as successful and does not accept the report as a new price. Each affected liquidation reservation is released during the same cleanup, so no approval quota remains locked.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Economic tradeoffs and limits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"No accepted-price notional budget","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator does not maintain a notional exposure budget. One accepted price may authorize multiple otherwise-valid operations during its freshness window. The report position is therefore an economic correction incentive, not a bond sized to the exact aggregate payoff of every operation that may use the price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Unbounded funded delay","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The stale-price refresh sponsor retains the staging lane while its report is pending. A timely valid dispute replaces the reporter and restarts OpenOracle's settlement clock. Repeated disputes can therefore delay settlement. Each extension requires a transaction and the contract-specified replacement position. Under ordinary non-dust parameters, fees and required position size also accumulate. There is intentionally no absolute coordinator deadline. The protocol does not claim liveness against a participant with unbounded capital that is willing to keep funding valid replacements. This availability model is separate from the dynamic initial WETH sizing rule and is not a bounded-liveness invariant.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Dispute escalation after the halt","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"In this integration, WETH is the escalation variable. Before escalationHalt , OpenOracle requires min(⌊oldAmount1 × multiplier / 100⌋, escalationHalt) . With the configured multiplier of 115 , integer flooring leaves token1 amounts from one through six attoETH unchanged; seven attoETH is the first amount that grows. At or above the halt, each replacement instead requires oldAmount1 + 1 , adding one attoETH of WETH. The selected initial amount is max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) . One escalation-halt candidate is that amount multiplied by the configured halt multiplier. The other is ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ . The actual halt is the greater of the two, as shown in Dynamic Report and Escalation Threshold .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"parameters","heading":"OpenOracle parameters used","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Use the coordinator reference for source constants and parameter definitions, and deployment verification for deployed getter values. The Statoblast whitepaper links to both. Parameter Current integration value Use in Statoblast token1Address / token2Address WETH / REP Defines the REP/ETH price pair used for security-pool solvency checks. minimumToken1ReportAttoEth() Dynamic; see Dynamic Minimum WETH Report and the estimator . Adds the priority-fee report to the larger base-fee or open-interest correction floor. initialReportPriorityFeeAttoEthPerGas Configured per origin pool; UI default 10 gwei Provides an immutable inclusion-fee component that every child pool inherits. OPEN_INTEREST_DIVIDER 100 Makes one percent of settlementCollateralAttoEth the open-interest-derived initial-report and escalationHalt floor. OpenOracle initial currentAmount1 The greater of minimumToken1ReportAttoEth() and the sponsor's requestedInitialAttoWeth The sponsor may request and fund more than the minimum; the coordinator submits the selected amount as currentAmount1 . escalationHalt The greater of 10x the selected initial currentAmount1 and 1% of pool open interest Raises the multiplicative-escalation threshold with pool exposure and stops multiplicative report-size escalation there; later disputes add one attoETH of WETH at a time. settlerRewardAttoEth Dynamic; see Oracle Request Cost . The full request bounty is assigned to the account that settles the report and triggers the callback. settlementTime 480 seconds (8 minutes) Sets the timestamp-based report settlement delay. The 40 * 12 derivation assumes twelve-second blocks, but the configured OpenOracle timeType uses seconds. Staged operations expire after this delay plus their operation-specific validity window. disputeDelay 0 Allows disputes immediately after each report and strictly before its settlement deadline. At the deadline, settlement is valid and a dispute is too late. callbackGasLimit Derived from settlement gas and the maximum callback batch; see Callback Gas Limit . Sets the callback gas limit for up to MAX_PENDING_SETTLEMENT_OPERATIONS staged operations; settlement still needs enough surrounding gas to satisfy OpenOracle's callback headroom check. feePercentage / protocolFee 10000 / 100000 Configures OpenOracle reporter and protocol fee accounting, and creates the fee boundary behind the honestDisputeBarrierFraction dispute barrier in the attack model. protocolFeeRecipient 0x000000000000000000000000000000000000dEaD Receives OpenOracle protocol fees for the coordinator-created report instance. multiplier 115 Before escalationHalt , requires WETH token1 equal to ⌊prior amount × 1.15⌋ , capped at the halt. Integer flooring leaves prior amounts of one through six attoETH unchanged. At or above the halt, each dispute requires one more attoETH of WETH. timeType / trackDisputes true / true Uses timestamps. Coordinator reports always enable dispute history so settlement can validate the final report's recorded base fee. Settlement rejects the price if the uint24 report counter is saturated. callbackContract OpenOraclePriceCoordinator Routes settled amounts back into Statoblast's price cache and staged-operation executor.","title":"OpenOracle integration","topic":"Architecture","weight":0}] +window.statoblastDocsSearch = [{"fragment":"","heading":"","keywords":["ABI","transactions","callers","events"],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"For developers and operators: find the contract that handles each state change, who can call it, and what the call does. Quick index ZoltarQuestionData Zoltar ReputationToken SecurityPoolFactory SecurityPool SecurityPoolForker EscalationGame LiquidationApprovalRegistry OpenOraclePriceCoordinator OpenOracleOperationBountyBoard ShareToken UniformPriceDualCapBatchAuction","title":"Contract interactions","topic":"Contracts","weight":1},{"fragment":"zoltarquestiondata","heading":"ZoltarQuestionData","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata. Source Read surface: Use getQuestionId before submission; questionCreatedTimestamp and questions for direct lookup; getQuestionCount and getQuestions for indexed or paged discovery; and getQuestionEndDate , getOutcomeLabels , splitUint256IntoTwoWithInvalid , hasNonZeroScalarReservedBits , isMalformedAnswerOption , and getAnswerOptionName when validating or displaying answers. In the QuestionData tuple, startTime and endTime are uint48 , while numTicks is uint120 ; clients must use these exact widths because they determine the getQuestionId and createQuestion selectors. Transaction Caller Main prerequisites State or asset effect Primary signals createQuestion(questionData, outcomeOptions) Anyone Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose keccak256(abi.encode(label)) values are strictly descending. Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied. QuestionCreated","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"zoltar","heading":"Zoltar","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP. Source Read surface: Use universes , forkThresholdDivisor , forkBurnDivisor , zoltarQuestionData , genesisReputationToken , getForkTime , forkQuestionMatches , getRepToken , getForkThresholdAttoRep , getNonDecisionThresholdAttoRep , getUniverseTheoreticalSupplyAttoRep , getChildUniverseId , getDeployedChildUniverses , and getMigrationRepBalanceAttoRep to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and forkBurnDivisor >= 5 , which caps the uncredited fork haircut at 20% of the threshold. Security boundaries for these calls are A15 intended question selection and A25 safe immutable parameters . Transaction Caller Main prerequisites State or asset effect Primary signals forkUniverse(universeId, questionId) Any address able to fund the current fork threshold Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut. UniverseForked burnRep(universeId, amountAttoRep) Any REP holder; the caller can burn only its own balance Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance. Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork. RepBurned and the token burn or transfer event deployChild(universeId, outcomeIndex) Anyone Parent forked; outcome is well formed; child is not already deployed. Deploys the deterministic child REP token and initializes the child universe. DeployChild addRepToMigrationBalance(universeId, amountAttoRep) Parent REP holder Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance. Burns or sinks additional parent REP and increases the caller's reusable migration balance. MigrationRepAdded splitMigrationRep(universeId, amountAttoRep, outcomeIndexes) Migration-balance holder Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance. Mints amount of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch. TheoreticalSupplySet and DeployChild when needed; child REP Transfer and Mint , then MigrationRepSplit , per selected branch, including at zero amount; no event for an empty list","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"reputationtoken","heading":"ReputationToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar. Source Read surface: Use getTotalTheoreticalSupplyAttoRep , zoltar , and the standard ERC-20 name , symbol , decimals , totalSupply , balanceOf , and allowance reads. Transaction Caller Main prerequisites State or asset effect Primary signals setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep) Zoltar only Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP. Sets the child token theoretical-supply ceiling used to bound subsequent migration mints. TheoreticalSupplySet mint(account, valueAttoRep) Zoltar only account is nonzero; resulting ERC-20 supply does not exceed theoretical supply. Mints branch REP to an account. Mint and ERC-20 Transfer burn(account, valueAttoRep) Zoltar only account is nonzero and has sufficient REP; theoretical supply covers the burn. Burns account REP and reduces both actual and theoretical supply by the same amount. Burn and ERC-20 Transfer transfer(to, value) REP holder Destination is nonzero; caller has sufficient balance. Moves REP from the caller without changing actual or theoretical supply. Transfer approve(spender, value) Any REP account setting its own allowance Spender is nonzero. Replaces the named spender allowance without moving REP. Approval transferFrom(from, to, value) A spender with sufficient allowance from from Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source. Moves REP from from ; a finite allowance decreases by value , while an infinite allowance remains unchanged. Neither allowance path emits Approval . Transfer only","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolfactory","heading":"SecurityPoolFactory","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction. Source Read surface: Use initialEscalationGameDepositAttoRep , minimumSecurityBondDebtAttoEth , and minimumVaultRepDepositAttoRep for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly max(1 REP, theoretical REP supply / 10,000,000) . A zero configured vault REP floor selects the default theoretical REP supply / 100,000 ; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use securityPoolDeploymentCount with the strict securityPoolDeploymentsRange(startIndex, count) pager, which reverts rather than truncating when the requested range exceeds the array. Use getOriginId , getPoolId , getSecurityPool , getSecurityPoolOriginId , and getSecurityPoolHasInheritedForkOutcome for canonical lookup. Transaction Caller Main prerequisites State or asset effect Primary signals deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas) Anyone statoblastSecurityMultiplierBps > 10_001 , which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the liquidation design . initialReportPriorityFeeAttoEthPerGas > 0 and remains within the coordinator-computed OpenOracle uint128 report/escalation-halt capacity bound; question exists and has exactly the categorical labels Yes , then No ; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit max(1 REP, theoretical REP supply / 10,000,000) ; the origin/universe/priority-fee slot has not already been claimed. Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically. SecurityPoolRegistered , then DeploySecurityPool deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth) SecurityPoolForker only Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring. Creates and registers a canonical child pool with a coordinator that inherits initialReportPriorityFeeAttoEthPerGas from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token. SecurityPoolRegistered , then DeploySecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypool","heading":"SecurityPool","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation. Source Read surface: Immutable relationship and configuration getters are questionId , universeId , initialEscalationGameDepositAttoRep , zoltar , parent , shareToken , repToken , priceOracleManagerAndOperatorQueuer , openOracle , escalationGameFactory , questionData , securityPoolForker , truthAuction , securityPoolFactory , and statoblastSecurityMultiplierBps ; the current game is escalationGame . Accounting getters include totalCapacityOwnershipAttoRep , settlementCollateralAttoEth , totalRepBackingUnits , shareTokenSupplyAttoShares , securityVaults , minimumSecurityBondDebtAttoEth , minimumVaultRepDepositAttoRep , vaultTargetHealthFactorBps , totalBadDebtAttoEth , and vaultBadDebtAttoEth . Use getCurrentMintingCapacityAttoEth for price-converted aggregate capacity and getVaultOpenInterestAttoEth for a vault’s live proportional obligation. Other derived and paged reads are getVaultCount , getVaults , attoSharesToAttoEth , attoEthToAttoShares , attoRepToBackingUnits , backingUnitsToAttoRep , getTotalPoolHeldAttoRep , totalAccruedFeesAttoEth , getPoolAccountingSnapshot , getVaultFeeRemainder , and isEscalationResolved . The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from securityVaults , escalation stake, and bad debt. isEscalationResolved() is true only when a local escalation game is configured and the forker routes a non- None outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are totalClaimableVaultFeesAttoEth , lastUpdatedFeeAccumulator , feeIndex , currentRetentionRate , awaitingForkContinuation , and systemState . Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on A16 timely inclusion , A21 genesis REP and WETH behavior , A19 observable correctable price , and A06 lifecycle executors . User-initiated pool calls additionally depend on A28 account authority . Transaction Caller Main prerequisites State or asset effect Primary signals burnEscalationWinnerHaircut(amountAttoRep) This pool's EscalationGame only Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool. Burns the winning-deposit haircut from REP already escrowed in the game. RepBurned and ERC-20 Transfer ; child REP also emits Burn depositRepToVault(attoRepAmount, targetHealthFactorBps) Vault owner Operational and unforked; isEscalationResolved() is false; target health factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum. Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from the deposit and selected target health factor. RepDepositedToVault , the vault target-health-factor event, and accounting checkpoints redeemFees(vault) Anyone; any nonzero ETH payment is always sent to vault A nonzero payment path requires vault to accept ETH. First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount. Accrual checkpoints only when accrual state changes; both VaultAccountingCheckpoint and PoolAccountingCheckpoint for a nonzero redemption; no event when fees and accrual state are unchanged createCompleteSet() with ETH Trader Operational and unforked; isEscalationResolved() is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; under A22 asset-recipient compatibility , a contract trader accepts onERC1155BatchReceived . Adds collateral and mints one Invalid , Yes , and No share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint. CompleteSetCreated , PoolAccountingCheckpoint , then ERC-1155 TransferBatch on a successful callback redeemCompleteSet(amountAttoShares) Anyone; positive redemption requires the caller to hold the complete set Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance. Burns equal balances of all three outcomes and pays amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. CompleteSetRedeemed and PoolAccountingCheckpoint redeemShares() Anyone; a positive payout requires the caller to hold winning shares Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value. Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction. SharesRedeemed and PoolAccountingCheckpoint redeemRepFromVault(vault) Anyone; REP is always sent to vault Operational pool with a final outcome; the specified vault has no escalation escrow and has redeemable REP. Burns the vault's REP backing units and returns its proportional vault REP backing. RepRedeemedFromVault depositToEscalationGame(outcome, maxAmount) Vault owner Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero. Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses nonDecisionThresholdAttoRep - 1 instead. Repeat deposits use the existing game's stored startBondAttoRep and nonDecisionThresholdAttoRep . Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome. EscalationGameSet on first deposit; DepositToEscalationGame withdrawFromEscalationGame(outcome, depositIndexes) Anyone; a nonempty list must select deposits belonging to one original depositor Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor. A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event. Per processed deposit, escalation-game CarryDepositConsumed ; additionally ClaimDeposit for a winning payout. No event for an empty list withdrawForkedEscalationDeposits(outcome, proofs) Anyone; a nonempty list must name one original depositor across all proofs Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor. A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event. Per processed proof, escalation-game CarryDepositConsumed and ClaimDeposit . No event for an empty list updateSettlementCollateral() Anyone No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp. Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees. PoolAccountingCheckpoint whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp updateRetentionRate() Anyone No caller restriction. It returns unchanged when the pool is not Operational or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate. Recalculates the retention rate from current collateral and live oracle-priced minting capacity. PoolAccountingCheckpoint only when the stored retention rate changes; no event for a no-op updateVaultFees(vault) Anyone for any address No caller, nonzero-vault, or lifecycle restriction. First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership. Accrual PoolAccountingCheckpoint when due; VaultAccountingCheckpoint when the vault index, remainder, or claimable fee balance changes; an additional PoolAccountingCheckpoint when pool accounting changes; no event when neither accrual nor vault or pool accounting changes withdrawRepFromVault(vault, attoRepAmount) This pool's OpenOraclePriceCoordinator only Fresh coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy. Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to vault . VaultTargetHealthFactorSet ; REP Transfer ; RepWithdrawnFromVault ; VaultAccountingCheckpoint ; and applicable fee-accrual or retention PoolAccountingCheckpoint events, including a zero-value transfer/event path if the trusted coordinator supplies zero performLiquidation(operationId, operator, receiverVault, targetVault, requestedDebtAttoEth, snapshotTargetBackingUnits, snapshotTargetCapacityOwnershipAttoRep, snapshotTotalPoolHeldAttoRep, snapshotTotalRepBackingUnits, minimumReceiverHealthFactorBps, minLiquidationPriceDistanceBps) This pool's OpenOraclePriceCoordinator only Fresh settled coordinator price; operational pool in an unforked universe; isEscalationResolved() is false; receiver differs from target. Target snapshots must match. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least minLiquidationPriceDistanceBps beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor. Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt. Fee-accrual and target or receiver VaultAccountingCheckpoint events as needed; VaultLiquidated identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; VaultBadDebtRecorded records residual target debt on a full-target request; final pool accounting checkpoint setStartingParams(...) SecurityPoolFactory only Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring. Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization. Coordinator RepEthPriceSet and CoordinatorStateCheckpoint , then pool PoolAccountingCheckpoint , even for zero or repeated values if the factory were to call again activateForkMode() SecurityPoolForker only The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data. Sets PoolForked , accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints. Pool-held REP Transfer always, including at zero; configured-game REP Transfer only for a positive game balance; accrual checkpoint when due; always PoolForkModeActivated and fork-activation PoolAccountingCheckpoint initializeForkedEscalationGame(...) SecurityPoolForker only No game is configured; downstream startFromFork parameters are valid. Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome. Escalation GameContinuedFromFork , then pool EscalationGameSet initializeForkCarrySnapshotWithResolutionBalances(...) SecurityPoolForker only A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data. Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots. ForkCarryCheckpoint resumeForkedEscalationGame() Anyone Pool is operational, awaiting a configured fork continuation, and the game has not resumed. Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call. ForkContinuationResumed and AwaitingForkContinuationSet(false) setAwaitingForkContinuation(shouldAwait) SecurityPoolForker only No lifecycle or value-change guard. Stores whether complete-set minting must wait for continuation initialization. AwaitingForkContinuationSet , including for a repeated value setSystemState(newState) SecurityPoolForker only No transition or value-change guard. Replaces the pool lifecycle state directly. SystemStateSet , including for a repeated state configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, targetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth) SecurityPoolForker only vault is nonzero; no lifecycle or value-change guard. Replaces the vault REP backing units, price-independent capacity ownership, fee index, target health factor, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state. Always VaultAccountingCheckpoint and PoolAccountingCheckpoint , including when all supplied values repeat current state setTotalRepBackingUnits(newDenominator) SecurityPoolForker only No lifecycle or value-change guard. Replaces the REP backing units denominator. TotalRepBackingUnitsSet , including for zero or a repeated value setTotalSharesAttoShares(newTotalSharesAttoShares) SecurityPoolForker only No lifecycle or value-change guard. Replaces stored shareTokenSupplyAttoShares , the denominator used by attoSharesToAttoEth and complete-set redemption. ShareTokenSupplySet , including for zero or a repeated value setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth) SecurityPoolForker only Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard. Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry. PoolAccountingCheckpoint , including for repeated financial values authorizeChildPool(pool) SecurityPoolForker only This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard. Asks the lineage share token to establish pool as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op. AuthorizationUpdated only on first authorization; no event when already authorized transferEth(receiver, amountAttoEth) SecurityPoolForker only Fee liabilities are covered; amount fits both unreserved pool ETH and tracked settlement collateral; receiver accepts the ETH call, including zero value. Reduces tracked settlement collateral by amount , checkpoints the reconciliation, and calls receiver with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint. PoolAccountingCheckpoint , including at zero amount; no dedicated ETH-transfer event addFeeEligibleCapacityOwnershipAttoRep(vault, amountAttoRep) SecurityPoolForker only The resulting fee-eligible capacity ownership cannot exceed total capacity ownership; no lifecycle, vault, positive-amount, or value-change guard. Adds newly auction-claimed capacity ownership to the live fee denominator, clears the pooled fee-index rounding remainder, then checkpoints elapsed fees and recalculates retention from collateral and unchanged total capacity ownership. The assignment itself does not change live minting capacity. Retention-rate PoolAccountingCheckpoint first when the rate changes, then VaultAccountingCheckpoint and auction-claim PoolAccountingCheckpoint , including the latter two at zero amount; the calling forker emits ClaimAuctionProceeds only after the broader credit workflow completes Direct ETH transfer to receive() Forker, this pool's truth auction, or parent pool only Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard. Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees. No dedicated receive event; the calling protocol step emits its own event","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"securitypoolforker","heading":"SecurityPoolForker","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions. Source Read surface: Use zoltar , forkData , getMigratedAttoRep , getForkActivationTime , isEscalationDepositClaimedDirectly , getEscalationDepositId , getDirectlyClaimedEscalationPrincipal , isEscalationWinnerHaircutPaidByFork , getEscalationMigrationEntitlementStatus , getOwnForkRepBuckets , getOwnForkMigrationStatus , getMigrationProxyAddress , getQuestionOutcome , attoRepToBackingUnits , and backingUnitsToAttoRep to reconstruct fork progress and preview migration conversions.","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"child-game-trust-boundary","heading":"Child-game trust boundary","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured SecurityPoolFactory registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from securityPool() when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed EscalationGame instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do. Transaction Caller Main prerequisites State or asset effect Primary signals initiateSecurityPoolFork(securityPool) Anyone Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from securityPool() when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the child-game trust boundary . Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot and ParentRepLocked ; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists forkZoltarWithOwnEscalationGame(securityPool) Anyone Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from securityPool() when validated and canTriggerOwnFork() is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the child-game trust boundary . Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured SecurityPoolFactory . SecurityPoolForkSnapshot , ParentRepLocked , and Zoltar fork events; additionally DisputeStakedRepDrainedAtFork when unresolved escalation exists migrateRepToZoltar(securityPool, outcomeIndices) Anyone Migration proxy exists and the pool is PoolForked . Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child ForkMigration state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied. For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events. MigrationRepSplit and ChildRepSplit when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch createChildUniverse(securityPool, outcomeIndex) Anyone Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the child-game trust boundary . These relationship checks do not independently prove configured-factory registration. Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game. DeployChild only when child REP was absent; always SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , and TotalRepBackingUnitsSet ; AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as continuation and backing state requires migrateVault(securityPool, outcomeIndex) Vault owner for their non-escrowed position Migration window open; the selected child's reported nonzero escalation game passes the child-game trust boundary . The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state. Transfers the caller's REP backing units, REP-denominated capacity ownership, target health factor, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move. VaultBadDebtMigrated and VaultMigrationCheckpoint migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex) The named vault owner Migration window open; caller equals vault ; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the child-game trust boundary . First runs ordinary migration for the same vault, which may transfer REP backing units, capacity ownership, target health factor, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs. Vault migration events, including VaultBadDebtMigrated , plus EscalationMigrationEntitlementInitialized on first export and EscalationMigrationEntitlementMaterialized for the selected child claimForkedEscalationDeposits(...) The named vault owner Caller equals vault ; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies canTriggerOwnFork() by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in ForkMigration , has a continuation game that passes the child-game trust boundary , and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit vault as its immutable depositor. First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary. DeployChild , SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , ChildPoolLinked , TotalRepBackingUnitsSet , AwaitingForkContinuationSet , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , MigrationRepSplit , ChildDisputeStakedRepMaterialized , and PoolHeldRepSweptToChild as setup requires; per claimed deposit, CarryDepositConsumed and ClaimDeposit ; escrow record/export events when REP is paid; always ClaimForkedEscalationDepositsToWallet , including for an empty list startTruthAuction(securityPool) Anyone Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the child-game trust boundary . Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction. ShareTokenSupplySet and TruthAuctionStarted ; immediate no-auction completion also emits TruthAuctionFinalized , pool accounting checkpoints, and ForkContinuationResumed for an unresolved continuation finalizeTruthAuction(securityPool) Anyone Truth auction started, its one-week window has passed, msg.value is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the child-game trust boundary . Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, activates the child at that settlement-collateral level, and fixes bidder REP-backing-unit and capacity-ownership rates. A nonzero repair contribution is rejected. TruthAuctionFinalized , auction AuctionFinalized , and pool accounting checkpoints; TruthAuctionHaircutApplied when purchased REP removes a positive escalation allocation; ForkContinuationResumed for an unresolved continuation settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices) Anyone on behalf of the named bidder vault At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled. Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and credits each fixed-position REP backing and capacity-ownership result. It also assigns the bidder vault its cumulative share of auctioned bad debt: intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion. Underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited claimAuctionProceeds(securityPool, vault, tickIndices) Anyone on behalf of the named bidder vault Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled. For a nonempty list, withdraws finalized bid settlements, converts purchased REP into child REP backing units, independently credits the bid positional capacity-ownership allocation, and assigns the bidder vault its cumulative share of auctioned bad debt. Intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns three zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events. For processed bids, underlying auction BidSettled ; EthRefundDeferred when the named bidder rejects a positive refund; ClaimAuctionProceeds with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame) This SecurityPoolForker contract only, through its migration delegate callback External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the child-game trust boundary . Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use. ChildDisputeStakedRepMaterialized and escalation-continuation events when initialization is required Direct ETH transfer to receive() A child-pool truth auction trusted by this forker during ChildPoolLinked trustedAuctionAddresses[msg.sender] was set when the forker linked the child and emitted ChildPoolLinked ; configured-factory registration determines whether that lineage is canonical. Accepts auction ETH during forker-controlled auction finalization. No dedicated receive event; auction AuctionFinalized is followed by forker TruthAuctionFinalized and pool accounting checkpoints","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"escalationgame","heading":"EscalationGame","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits. Source Read surface: Base getters are securityPool , repToken , activationTime , nonDecisionThresholdAttoRep , startBondAttoRep , nonDecisionTimestamp , nonDecisionState , forkContinuation , forkElapsedAtStart , forkResumedAt , fixedQuestionOutcome , nodes , disputeStakedRepByVaultAttoRep , totalDisputeStakedAttoRep , truthAuctionRepBeforeAttoRep , truthAuctionRepRemainingAttoRep , cumulativeClaimRetention , and cumulativeClaimRetentionExponent . The claim delegate fallback exposes rootClaimSourceGame , applyInheritedClaimRetention , and applyInheritedSourceStorageBasis . The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. disputeStakedRepByVaultAttoRep is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use previewDepositOnOutcome , computeIterativeAttritionCostAttoRep , computeTimeSinceStartFromAttritionCostAttoRep , totalCostAttoRep , getEscalationGameEndDate , getQuestionResolution , getFinalQuestionResolution , hasReachedNonDecision , canTriggerOwnFork , getBindingCapitalAttoRep , getOutcomeBalancesAttoRep , getDepositsByOutcome , getDepositsByOutcomeLength , forkCarrySnapshotInitialized , getOutcomeState , getForkCarrySnapshot , getForkCarryRoots , isForkCarryFundingComplete , getCarryLeafPageByOutcome , getProofConsumedCarriedDepositIndexesByOutcome , getLocalUnresolvedPrincipalByVaultAndOutcome , and getForkedEscrowByVaultAndOutcome for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through SecurityPool . Transaction Caller Main prerequisites State or asset effect Primary signals start(startBondAttoRep, nonDecisionThresholdAttoRep) EscalationGameFactory contract during atomic deployment Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid. Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to nonDecisionThresholdAttoRep - 1 before this call. GameStarted startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep) Immutable owner ( EscalationGameFactory ) during atomic continuation deployment Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid. Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until resumeFromFork . GameContinuedFromFork resumeFromFork() Owning SecurityPool only Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , where sourcePrincipalAtForkAttoRep is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims. Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is max(rebasedCurveEnd, forkResumedAt + 3 days) , so even an exhausted inherited clock receives a fresh response period. After that deadline, getFinalQuestionResolution returns the fixed outcome when the continuation has one. ForkContinuationResumed applyTruthAuctionHaircut(repToRemove) The child pool's SecurityPoolForker only Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance. Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it. TruthAuctionHaircutApplied and REP Transfer recordDepositFromSecurityPool(...) Owning SecurityPool only Explicit non-decision state is None ; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold. Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf. LocalDepositAppended , DepositOnOutcome , optionally NonDecisionReached withdrawDeposit(uint256 depositIndex, outcome) Owning SecurityPool only Explicit non-decision state is None ; non- None supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index. Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting. CarryDepositConsumed and VaultEscrowUpdated ; for a winner, ClaimDeposit , positive REP payout Transfer , and haircut burn signals when nonzero initializeForkCarrySnapshotWithResolutionBalances(...) Owning SecurityPool only Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data. Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set nonDecisionState to InheritedThresholdTie without creating a local timestamp. ForkCarryCheckpoint ; additionally InheritedThresholdTie when the installed balances meet the non-decision threshold claimDepositForWinning(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path. Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor. CarryDepositConsumed , VaultEscrowUpdated , ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when their amounts are positive claimDepositForWinningWithoutTransfer(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non- None guard; neither form checks final resolution or that the outcome won. Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or ⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉ after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut. CarryDepositConsumed , VaultEscrowUpdated , and ClaimDeposit with transferredRep = false ; no REP transfer or haircut burn exportUnresolvedDeposit(depositIndex, outcome) Owning SecurityPool or its SecurityPoolForker Non- None outcome and a valid unsettled local deposit. Final resolution is not required. Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP. CarryDepositConsumed and VaultEscrowUpdated ; no ClaimDeposit or REP transfer withdrawDeposit(CarriedDepositProof proof, outcome) Owning SecurityPool or its SecurityPoolForker Non- None supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof. Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it. CarryDepositConsumed and ClaimDeposit with transferredRep = true ; REP payout Transfer and haircut burn signals only when positive exportVaultUnresolvedTotals(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to repReceiver . Always VaultUnresolvedTotalsExported , including when every amount is zero; VaultEscrowUpdated and REP Transfer only for a positive total exportVaultUnresolvedTotalsWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero and has not exported before. Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller. Always VaultUnresolvedTotalsExported with transferredRep = false , including when every amount is zero; VaultEscrowUpdated only for a positive total; no REP transfer drainAllRep(receiver) Owning SecurityPool only receiver is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after activateForkMode enters PoolForked . Transfers the game's full REP balance to receiver . A zero balance returns zero without a transfer or event. REP Transfer for a positive balance; no event at zero balance recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep) Owning SecurityPool or its SecurityPoolForker Outcome is not None ; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op. Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event. ForkedEscrowRecorded for a nonzero record; no event when both amounts are zero exportForkedEscrowByOutcome(vault, repReceiver) Owning SecurityPool or its SecurityPoolForker vault and repReceiver are nonzero. Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event. ForkedEscrowExported when any source principal or child REP remains; REP Transfer when positive child REP is transferred; no event for an already-empty export exportForkedEscrowByOutcomeWithoutTransfer(vault) Owning SecurityPool or its SecurityPoolForker vault is nonzero. Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event. ForkedEscrowExported with transferredRep = false when any source principal or child REP remains; no REP transfer; no event for an already-empty export sweepResidualRepToSecurityPool() Anyone Final outcome; no unresolved principal; no vault escrow; positive residual balance. Returns otherwise stranded residual REP to the owning pool. ResidualRepSweptToSecurityPool","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"liquidationapprovalregistry","heading":"LiquidationApprovalRegistry","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator. Source Read surface: Use coordinator to identify the validating coordinator and implied security pool. LIQUIDATION_APPROVAL_TYPEHASH , DOMAIN_SEPARATOR , and liquidationApprovalDigest define the chain- and registry-bound EIP-712 message. getLiquidationApproval reports parameters plus available, reserved, consumed, and revoked state; minimumLiquidationApprovalNonce reports receiver invalidation state; liquidationReservations and minimumHealthFactorBps expose operation reservation state and its execution-time health floor. Transaction Caller Main prerequisites State or asset effect Primary signals initialize(coordinator) Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically Coordinator is nonzero and the registry has not been initialized. Binds this registry clone to one coordinator and therefore one security pool. No event; the public coordinator getter records the binding. setLiquidationApproval(params) The receiver vault named by params Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce. Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce. LiquidationApprovalSet permitLiquidationApproval(params, signature) Anyone relaying the receiver vault signature Signature is valid for params.receiverVault ; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass. Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce. LiquidationApprovalSet revokeLiquidationApproval(approvalId) Approval receiver vault only Approval exists and is not already revoked. Prevents new reservations while leaving reservations already attached to staged operations intact. LiquidationApprovalRevoked with available, reserved, and consumed totals invalidateLiquidationApprovalNonce(newNonce) Receiver vault invalidating its own older nonce range New nonce is greater than the receiver current minimum. Raises the minimum nonce accepted for new approval installation or reservation. LiquidationApprovalNonceInvalidated reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp) Bound coordinator only Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota. Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota. LiquidationApprovalReserved release(operationId) Bound coordinator only Coordinator terminal cleanup path. Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op. LiquidationApprovalReleased when quota is returned consume(operationId, debtMovedAttoEth) Bound coordinator only For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op. Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once. LiquidationApprovalConsumed","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"openoraclepricecoordinator","heading":"OpenOraclePriceCoordinator","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup. Source Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS , OPEN_INTEREST_DIVIDER , reputationToken , securityPool , openOracle , weth , liquidationApprovalRegistry , operationBountyBoard , gasConsumedOpenOracleReportPrice , gasConsumedSettlement , gasUnitsForOneDispute , initialReportPriorityFeeAttoEthPerGas , targetPriceErrorForDispute , openOracleSecurityMultiplierBps , settlementTime , disputeDelay , protocolFee , feePercentage , multiplier , timeType , trackDisputes , protocolFeeRecipient , escalationHaltMultiplierBps , maxSettlementBaseFeeMultiplierBps , and minLiquidationPriceDistanceBps . Current report and operation getters are pendingReportId , pendingReportSponsor , pendingOperationSlotId , lastSettlementTimestamp , lastPrice , pendingReportMaxSettlementBaseFeeAttoEthPerGas , stagedOperationCounter , and stagedOperations . Use isPriceValid , minimumToken1ReportAttoEth , getRequestPriceCostAttoEth , getQueuedOperationCostAttoEth , getSettlementCallbackGasLimit , getPendingOperationSlot , getActiveStagedOperationCount , getActiveStagedOperations , getPendingSettlementOperationCount , and getPendingSettlementOperationIds for derived or paged state. Report and staged-operation liveness depends on A16 timely inclusion , A17 corrector capability , A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . When lastPrice is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path. Transaction Caller Main prerequisites State or asset effect Primary signals requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...) Liquidation operator; a delegated receiver must have approved this exact operator Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution. Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID. LiquidationRouteStaged ; LiquidationApprovalReserved on a delegated route; staged-operation lifecycle events requestPriceIfNeededAndStageOperation(...) with funding when stale Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations. securityPool.isEscalationResolved() is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund. Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report. StagedOperationQueued , possibly PriceRequested , then ExecutedStagedOperation ; authoritative CoordinatorStateCheckpoint records requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding Anyone when no fresh price or report is pending Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund. Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position. PriceRequested and CoordinatorStateCheckpoint stageAndRequestOperationBounty(bountyId, sponsor, creator, operation, targetVault, ...) The coordinator’s configured operationBountyBoard only No nested bounty staging call is active; the creator and target satisfy the normal operation route; and a stale price has room in the four-operation pending settlement batch. Stages a self-receiving creator operation while assigning initial-report token funding, report sponsorship, and unused ETH refunds to the accepting operator. The operation result is returned to the board before the coordinator emits its execution event. StagedOperationQueued , possibly PriceRequested , then ExecutedStagedOperation ; authoritative CoordinatorStateCheckpoint records executeStagedOperation(operationId) Anyone Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried. Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds. ExecutedStagedOperation , either LiquidationApprovalConsumed or LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint expireStagedOperation(operationId) Anyone Operation exists and its settlement-plus-validity window has elapsed. Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price. ExecutedStagedOperation , LiquidationApprovalReleased for a delegated liquidation, and CoordinatorStateCheckpoint recoverSettledPendingReport() Anyone A pending report ID exists and its stored OpenOracle storedGame(reportId).settlementTimestamp is nonzero. Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active. PendingReportRecovered , failed ExecutedStagedOperation for each live attached operation, LiquidationApprovalReleased for each attached delegated liquidation, and CoordinatorStateCheckpoint openOracleCallback(...) Configured OpenOracle only Callback report matches the pending report; excessive settlement basefee, a saturated uint24 report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state. A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation. PriceReported or PriceReportRejected ; operation execution events; authoritative CoordinatorStateCheckpoint records setOperationBountyBoard(board) Coordinator deployment factory only Board has deployed code and no board was previously installed. Binds the coordinator-local operation bounty board once. OperationBountyBoardSet setLiquidationApprovalRegistry(registry) Coordinator deployment factory only Registry is nonzero and no registry was previously installed. Binds the coordinator-local approval registry once. No event; deterministic factory deployment and the public getter identify the registry. setSecurityPool(pool) Anyone while securityPool remains zero; normal factory deployment calls atomically Current securityPool is zero; the argument itself is not required to be nonzero. A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator. SecurityPoolSet and CoordinatorStateCheckpoint setRepEthPrice(price) Configured nonzero SecurityPool only Caller equals the configured pool. Seeds the coordinator's price value, including zero, for inherited child state. RepEthPriceSet and CoordinatorStateCheckpoint","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"openoracleoperationbountyboard","heading":"OpenOracleOperationBountyBoard","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Escrows REP or WETH rewards for creator-defined coordinator operations and pays the operator only after successful staged execution. Source Read surface: Use coordinator , reputationToken , and weth to identify the bound coordinator and token contracts. nextOperationBountyId , operationBounties , and operationExecutionStatuses expose bounty identity, escrow terms, assignment, staged operation and report IDs, terminal state, and execution outcome. getOperationBounties pages forward from an explicit ID. The co-located deployment factory exposes its immutable owner and shared implementation . Transaction Caller Main prerequisites State or asset effect Primary signals initialize(coordinator, reputationToken, weth) Anyone while uninitialized; the canonical factory initializes new clones atomically The proxy is not initialized and every supplied address is nonzero. The shared implementation is constructor-locked against initialization. Binds the proxy to its coordinator and token pair and starts bounty IDs at one. No event; the factory completes initialization before returning the proxy. postOperationBounty(operation, targetVault, amount, validForSeconds, rewardToken, rewardAmount, acceptanceDeadline, minimumInitialAttoWeth, maximumInitialAttoWeth) Any creator with sufficient REP or WETH allowance and balance amount is positive attoREP for a withdrawal or positive maximum requested debt in attoETH for a liquidation. The reward is positive coordinator REP or WETH; its token amount uses that token's 18-decimal base unit. The acceptance deadline is strictly in the future; execution validity is from 1 second through 5 minutes. A zero maximumInitialAttoWeth means no maximum; otherwise the minimum cannot exceed it. Withdrawals target the creator and liquidations target another vault. Creates an open bounty and escrows the full reward on the board. OperationBountyPosted acceptOperationBounty(bountyId, proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding when stale Any operator at or before the acceptance deadline Bounty is open; its initial WETH bounds admit the new or existing report; the settlement batch has room; and an existing pending report is sponsored by this operator. Assigns the bounty, stages its operation, and either executes against a fresh price or attaches to a pending report. When a report must be opened, the operator supplies its REP, WETH, and ETH funding and becomes the report sponsor. OperationBountyAccepted ; coordinator staging and reporting events claimOperationBounty(bountyId) Assigned operator only Bounty remains assigned and its recorded execution status is succeeded. Marks the bounty paid and transfers its escrowed reward to the operator. OperationBountyClaimed refundOperationBounty(bountyId) Bounty creator only Bounty is open, has failed, or remains assigned and pending strictly after queuedAt + settlementTime + validForSeconds ; equality is still active. That fixed cancellation deadline does not move when disputes extend report settlement. Successful execution cannot be refunded. Cancels an open bounty immediately, refunds a failed bounty, or expires an overdue staged operation before refunding its full escrow. OperationBountyRefunded ; overdue assigned bounties also produce ExecutedStagedOperation recordOperationResult(bountyId, success) Bound OpenOraclePriceCoordinator only Bounty remains assigned. Records the assigned bounty as succeeded or failed so its escrow can be claimed or refunded. The coordinator emits ExecutedStagedOperation with the same result and any failure detail. deploy(coordinator, reputationToken, weth, salt) Owning price-coordinator factory only Caller equals the factory owner ; the derived CREATE2 address is unused. Lazily deploys one shared board implementation, then deterministically deploys and initializes a minimal proxy for the coordinator and token pair. No dedicated event; the coordinator subsequently emits OperationBountyBoardSet .","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"sharetoken","heading":"ShareToken","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches. Source Read surface: Base and relationship getters are name , symbol , zoltar , canonicalPoolByUniverse , _balances , _supplies , and _operatorApprovals . Standard ERC-1155 reads are supportsInterface , balanceOf , totalSupply , balanceOfBatch , and isApprovedForAll ; protocol-specific reads are isAuthorized , totalSupplyForOutcome , maximumOutcomeSupply , balanceOfOutcome , balanceOfShares , getMigratedShareAmountAttoShares , getTokenId , getTokenIds , and unpackTokenId . Transaction Caller Main prerequisites State or asset effect Primary signals setApprovalForAll(operator, approved) Any token account setting its own operator approval The operator differs from the caller. Sets or clears the operator's authority over all of the caller's outcome-token balances. ApprovalForAll Both safeTransferFrom(...) overloads Share holder or approved ERC-1155 operator Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under A22 asset-recipient compatibility , a contract recipient accepts the ERC-1155 callback. Transfers one outcome-token balance without changing supply. TransferSingle Both safeBatchTransferFrom(...) overloads Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under A22 asset-recipient compatibility , an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks. A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event. TransferBatch for a nonempty batch; no event for an empty batch migrate(fromId, targetOutcomeIndexes) Holder of the source token ID Source universe forked; canonical source pool is Operational or PoolForked , and an Operational source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under A22 asset-recipient compatibility , a contract holder accepts onERC1155Received for every target mint. If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup. PoolForkModeActivated , PoolAccountingCheckpoint , SecurityPoolForkSnapshot , ParentRepLocked , and optionally DisputeStakedRepDrainedAtFork when auto-forking; SecurityPoolRegistered , DeploySecurityPool , AuthorizationUpdated , and ChildPoolLinked when lazily deploying, plus DeployChild , ChildRepSplit , PoolHeldRepSweptToChild , EscalationGameSet , GameContinuedFromFork , ForkCarryCheckpoint , and ChildDisputeStakedRepMaterialized as applicable; then one ERC-1155 mint TransferSingle and Migrate per materialized target on successful callbacks authorize(securityPoolCandidate) Initially authorized SecurityPoolFactory for an origin pool; an authorized parent SecurityPool for a child pool Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool. Establishes the candidate as canonicalPoolByUniverse for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op. AuthorizationUpdated on first authorization; no event when the same candidate is already authorized mintCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero; amount is positive; under A22 asset-recipient compatibility , a contract account accepts onERC1155BatchReceived . Mints amount each of Invalid, Yes, and No to account , then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction. TransferBatch on a successful callback burnCompleteSets(universeId, account, amountAttoShares) An authorized SecurityPool Caller is authorized; account is nonzero and has at least amount of every outcome. Burns amount each of Invalid, Yes, and No from account ; global outcome supplies may differ. TransferBatch burnTokenIdAndGetRemainingSupply(tokenId, account) An authorized SecurityPool account is nonzero; caller is authorized. Burns account 's full balance of tokenId and returns the burned amount and that token ID's remaining supply. TransferSingle , including when the burned balance is zero","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"uniformpricedualcapbatchauction","heading":"UniformPriceDualCapBatchAuction","keywords":[],"path":"reference/contracts.html","sectionTitle":"Reference","summary":"State-changing entrypoints, callers, prerequisites, effects, and events.","text":"Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. Source Read surface: Auction summary getters are maxAttoRepBeingSold , attoEthRaiseCap , finalized , clearingTick , ethFilledAtClearingAttoEth , attoEthRaised , totalAttoRepPurchased , auctionStarted , minBidSizeAttoEth , owner , underfunded , underfundedThreshold , underfundedWinningAttoEth , and activeTickCount . pendingEthRefundsAttoEth reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use computeClearing , previewFinalization , tickToPrice , getTickSummary , getTickCount , getTickPage , getActiveTickPage , getBidCountAtTick , getBidPageAtTick , getBidderBidCount , and getBidderBidPage before finalizing or submitting settlement indexes. Transaction Caller Main prerequisites State or asset effect Primary signals startAuction(attoEthRaiseCap, maxAttoRepBeingSold) Auction owner ( SecurityPoolForker ) only Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in uint128 ; the block timestamp fits in uint48 . Starts the one-week auction and fixes its two caps and minimum bid. AuctionStarted submitBid(tick) with ETH Any bidder Auction active and unfinalized; before one-week deadline; bid meets minBidSizeAttoEth ; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in uint128 . Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again. BidSubmitted refundLosingBids(tickIndices) Bidder for its own bids Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded. A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails refundLosingBidsFor(bidder, tickIndices) Auction owner ( SecurityPoolForker ) only; public callers use settleAuctionBids Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded. A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in pendingEthRefundsAttoEth without restoring the bid. An empty list changes no bids and makes no external call. BidSettled per refunded bid; EthRefundDeferred when a positive push fails finalize() Auction owner ( SecurityPoolForker ) only; users reach it through finalizeTruthAuction Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value. Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event. AuctionFinalized withdrawBids(withdrawFor, tickIndices, proRataTotal) Auction owner only Auction finalized; caller is owner. Nonempty indexes belong to withdrawFor and remain unsettled. For a nonempty list, returns refunds, purchased REP, and a companion pro-rata allocation for the selected beneficiary bids so the forker can credit REP backing units and capacity ownership. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP and capacity-ownership settlement. An empty list returns three zeros without changing bids, emitting events, or calling the beneficiary. BidSettled per processed bid; EthRefundDeferred when a positive push fails withdrawPendingEthRefund() Bidder with deferred ETH Caller has a positive pendingEthRefundsAttoEth balance and currently accepts ETH. Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event. PendingEthRefundWithdrawn","title":"Contract interactions","topic":"Contracts","weight":0},{"fragment":"","heading":"","keywords":["invariants","safety","liveness","properties"],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"An invariant is a statement that must remain true at every successful external-call boundary, across every legal lifecycle transition, or under the economic assumptions named beside it. Reverts may interrupt a transition, but they must roll back all of its state and asset effects. The catalog separates local contract guards from cross-contract properties and economic assumptions. It also records launch-critical properties that the current contracts do not preserve. A documented implementation choice is not automatically a safe invariant. See the Protocol Security Model for assumptions A01–A28. Funded and underfunded clearing are explained in the Truth Auction clearing guide ; oracle notional and residual-loss analysis are explained in OpenOracle Integration . Four connected layers run from local authority guards through cross-contract conservation and lifecycle liveness to economic security. A failure in an earlier layer invalidates the safety claims built above it. Invariant Dependency Correct callers are necessary but insufficient. Their actions must conserve assets, every mandatory state transition must remain reachable, and the named participation and inclusion assumptions must hold where the protocol deliberately relies on economic incentives. Search Type Any type Enforcement status Any status Subsystem Any subsystem Expand visible Collapse all Reset filters Loading invariants… No invariants match the selected filters.","title":"Protocol invariants","topic":"Safety","weight":1},{"fragment":"standing","heading":"Classification and Scope","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Each entry separates the kind of property from its current enforcement status. A status describes the strongest claim supported by the local implementation and tests; it is not a formal proof. Changes to any contract named in the evidence column must preserve the full statement, not only the check performed in one function. Type Meaning Safety A forbidden caller, state, transition, or replay cannot succeed. Conservation Assets, liabilities, supplies, and claims reconcile without duplication or loss. Liveness A required permissionless action remains executable under the stated conditions. External assumption The property depends on participant behavior, inclusion, liquidity, or token behavior outside the contracts. Enforcement status Meaning Required response Contract guard A direct authorization, bound, or state check enforces the property locally. Keep the guard and test its negative boundary. Reviewed preservation Several contracts cooperate to preserve the property in reviewed flows and tests. Protect it with cross-contract invariant and lifecycle tests. Open violation A concrete sequence reaches a state that contradicts the required property. Fix before deployment and retain a regression test. Economic or external assumption Solidity cannot guarantee the property without honest participation, liquidity, or transaction inclusion. State and monitor the assumption, and document both its failure mode and the guarantees deliberately excluded from the protocol model. Proposed guard The property closes a missing safety or verification boundary not currently represented as one assertion. Add a contract check, model assertion, or invariant harness where practical. Invariant identifiers are stable review labels. They are grouped by authority, universe, question, asset, share, vault, fork, escalation, oracle, auction, lifecycle, and observability boundaries rather than by Solidity file.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"standing","heading":"Terms used in the catalog","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Pool question The question whose outcome determines the pool's winning shares and escalation payouts. Fork question The ended question recorded by the universe fork. It may differ from the pool question. Child branch A well-formed answer encoding for the fork question, used to derive one child universe from its parent. SecurityPool's binary pool-question paths specifically use Invalid, Yes, and No. Continuation game A child escalation game initialized from an unresolved parent game's fork snapshot. Fixed payout outcome The pool-question outcome a continuation game must use after its deadline, regardless of its copied balance leader. Direct fork A caller invokes Zoltar's universe-fork entrypoint directly rather than through a pool's own non-decision path. Recursive fork A fork of a child universe created by an earlier fork. Tracked balance An amount recorded by protocol accounting; it excludes unsolicited assets unless a named transition explicitly credits them. Consumed claim A deposit, bid, or proof that has already been settled, refunded, exported, or otherwise made unusable.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"zoltar-invariants","heading":"Zoltar Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern core universe creation, REP supply, universe forks, deterministic child identities, and branch-specific REP migration.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"universes","heading":"Universes, REP Supply, and Migration","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"UNI-01 One fork per universe Required property An initialized universe records at most one fork time and one fork question. Example: After a universe forks on question 17, a second call attempting to fork it on question 22 reverts. Type Safety Enforcement status Contract guard Primary evidence Zoltar.forkUniverse UNI-02 Valid fork questions Required property forkUniverse accepts only a question that exists in ZoltarQuestionData and whose end time has been reached ( block.timestamp >= endTime ). A pool may separately require the fork question to equal its pool question. Zoltar intentionally imposes no question-creation age, universe registration, or pool-relevance condition. Example: At exactly a known question's end time, a caller may fork a universe even when that question came from another universe or was created with an already-past end time; an unknown or still-open question is rejected. Requiring a universe-relevant or sufficiently aged question is not a Zoltar invariant. Type Safety Enforcement status Contract guard Primary evidence forkUniverse , question model Related external boundaries A15 intended question selection UNI-03 Fork threshold burn and credit Required property The fork threshold is ⌊parent theoretical supply / forkThresholdDivisor⌋ . Starting a fork burns that amount of the initiator's parent REP, reduces the parent theoretical supply by the same amount, and credits the threshold minus ⌊threshold / forkBurnDivisor⌋ for migration. The configured forkBurnDivisor is at least 5 , so the uncredited haircut cannot exceed 20%. Example: With a 1,000 REP theoretical supply, threshold divisor 20, and burn divisor 5, the initiator commits 50 parent REP, pays a 10 REP haircut, and receives 40 REP of migration credit. Type Conservation Enforcement status Contract guard Primary evidence getForkThresholdAttoRep and forkUniverse UNI-04 Deterministic child universe IDs Required property A child universe identity is exactly the truncated hash of its parent universe and fork outcome; one parent-outcome pair maps to one child. Example: Repeatedly deriving the Yes child of the same parent returns the same universe ID, while deriving its No child returns a different ID. Type Safety Enforcement status Contract guard Primary evidence getChildUniverseId UNI-05 Well-formed child outcomes Required property Only well-formed outcomes of the recorded fork question can deploy child universes or receive migrated REP. Example: For a binary categorical fork question whose valid indexes are 0, 1, and 2, passing index 3 cannot deploy a child or receive child REP. Type Safety Enforcement status Contract guard Primary evidence deployChild and splitRepInternal UNI-06 Per-child migration mint limit Required property For one migrator and one child, cumulative child REP minted never exceeds that migrator's parent migration balance. Example: A migrator with 40 REP of migration credit cannot mint 25 REP and later mint another 20 REP into the same child. Type Safety Enforcement status Contract guard Primary evidence childMigrationRepAmounts UNI-07 Branch-specific child REP Required property One burned parent migration balance may mint the same credited amount into several selected children. Each minted balance belongs to a different child REP token, and migration never restores the burned parent REP. Example: A 40 REP migration credit may mint 40 Yes-child REP and 40 No-child REP, but neither balance is parent REP and the burned parent balance remains zero. Type Conservation Enforcement status Reviewed preservation Primary evidence splitRepInternal , ReputationToken UNI-08 Child theoretical supply snapshot Required property The child theoretical-supply snapshot equals the parent's theoretical supply after the threshold burn plus the initiator's post-haircut migration credit. Each child therefore excludes the uncredited fork haircut. Example: If a fork removes 50 parent REP and credits 40 migration REP, each child's theoretical snapshot is 10 REP below the pre-fork supply. Type Conservation Enforcement status Reviewed preservation Primary evidence childUniverseTheoreticalSupplySnapshotsAttoRep UNI-09 Child REP supply coherence Required property For each child REP token, total minted supply equals the sum of holder balances, never exceeds the child's theoretical-supply snapshot, and uses the same theoretical maximum recorded by the child universe. Example: If two migrators mint 12 and 7 child REP, the child token supply is 19 REP, both balances sum to 19 REP, and minting does not change the child's theoretical maximum. Type Conservation Enforcement status Reviewed preservation Primary evidence splitMigrationRep and child universe accounting , ReputationToken supply , aggregate child-supply invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"questions","heading":"Questions and Answer Classification","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"QST-01 Question registry and classifier coherence Required property Every successful question creation appends its deterministic ID exactly once and leaves prior question data and labels immutable. For every stored question and answer, the malformed classifier is true exactly when the public answer name is Malformed . Example: Appending a scalar question after two categorical questions preserves the first two records and their order; a scalar answer with reserved bits is both classified and named malformed. Type Safety Enforcement status Reviewed preservation Primary evidence createQuestion , getQuestions , and answer classification , registry and classifier invariant test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"statoblast-invariants","heading":"Augur Statoblast Invariants","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"These properties govern Statoblast deployment, SecurityPool accounting, fork migration, escalation, oracle operations, truth auctions, and the cross-contract lifecycle built on Zoltar universes.","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"authority","heading":"Authority and Deployment Identity","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A23 verified deployments and A27 cryptographic identity . AUTH-01 Coordinator and forker-only operations Required property Only the immutable coordinator may execute price-sensitive pool operations, and only the immutable forker may mutate fork accounting or transfer migration assets. Example: A wallet calling withdrawRepFromVault directly is rejected because only the coordinator may call it. Type Safety Enforcement status Contract guard Primary evidence SecurityPool.onlyValidOracle and onlyForker AUTH-02 Atomic pool and coordinator binding Required property The pool and its coordinator bind once during atomic factory deployment; no external caller gets a transaction boundary in which it can install a substitute pool. Example: A caller cannot front-run factory deployment and make the coordinator point to an attacker-controlled pool. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , setSecurityPool AUTH-03 Immutable protocol logic Required property No mutable owner, governance address, upgrade implementation, or emergency operator can replace protocol logic after deployment. Example: After deployment, no administrator can point a pool proxy at different bytecode because the pool has no upgrade slot. Type Safety Enforcement status Reviewed preservation Primary evidence Immutable release posture AUTH-04 Deterministic deployment identity Required property Every canonical CREATE2 address commits to the correct factory, salt, constructor arguments, parent identity, universe, question, and multiplier. Example: Changing a child's question ID changes its derived address instead of deploying different semantics at the expected address. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory , addressDerivation.ts AUTH-05 Delegate storage compatibility Required property Delegatecall targets are constructor-installed protocol modules, and every target interprets the forker's storage with the same layout. Example: The vault-migration delegate reads forkDataByPool from the same slot used by the forker rather than corrupting adjacent state. Type Safety Enforcement status Reviewed preservation Primary evidence SecurityPoolForker , interface and storage-layout regression tests AUTH-06 Permissionless settlement beneficiaries Required property Anyone may trigger permissionless settlement, but the caller cannot choose its beneficiary. Each transfer or REP-backing-unit credit goes only to the vault, bidder, depositor, or share holder recorded for that claim. Example: Alice may settle Bob's auction bid, but the purchased REP remains pool-held while the corresponding REP backing units and capacity ownership are credited to Bob's vault. Type Safety Enforcement status Contract guard Primary evidence Contract interaction reference AUTH-07 Published deployment verification Required property For every published deployment address, the runtime-bytecode hash and constructor-installed dependencies must match the reviewed release manifest. Example: A manifest check fails when the published coordinator address contains code built with a different OpenOracle dependency. Type Safety Enforcement status Proposed guard Primary evidence Deployment Status Oracle limits , mainnet manifest , Sepolia manifest AUTH-08 Factory registry and lineage bijection Required property Every factory deployment record names one unique pool whose runtime parent, share token, universe, and question match the record. Its pool-to-origin reverse lookup and origin/universe forward lookup are mutual inverses, and its recorded share token authorizes that pool. Example: Looking up a child pool's origin and then resolving that origin with the child's universe returns the same child, not a pool from an independent origin lineage in that universe. Type Safety Enforcement status Reviewed preservation Primary evidence factory registry and canonical lookups , share-token authorization , stateful registry-bijection test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"pool-accounting","heading":"Pool Assets, Shares, and Vaults","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"The raw ETH balance needs an explicit surplus term. Without it, forced ETH is indistinguishable from collateral and can change an exchange rate or lifecycle precondition. rawEthBalanceAttoEth = settlementCollateralAttoEth + unallocatedAccruedFeesAttoEth + totalClaimableVaultFeesAttoEth + explicitSurplusAttoEth Every successful external call should reconcile raw ETH into named liabilities or an explicit surplus bucket. All equation terms are denominated in attoETH; raw balance alone must not define collateral. BAL-01 ETH conservation Required property At every successful external-call boundary, raw ETH = tracked collateral + unallocated fee reserve + total claimable vault fees + unaccounted surplus . Surplus is the derived remainder and is never a protocol liability. Example: If the pool holds 12 ETH, owes 2 ETH in fees, and tracks 9 ETH of collateral, the remaining 1 ETH is surplus rather than additional collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence Pool ETH accounting , stateful exact-accounting invariant tests BAL-02 Unsolicited ETH isolation Required property Unsolicited ETH cannot change collateral, initialize an exchange rate, increase liabilities, or block a required transition. Example: ETH forced into an empty pool does not let the first complete-set mint use that ETH as its collateral base. Type Conservation Enforcement status Reviewed preservation Primary evidence receive , tracked collateral, and fee redemption , auction finalization BAL-03 Collateral capacity Required property At a complete-set mint boundary, the resulting tracked collateral must fit the current ETH minting capacity derived from total REP-denominated capacity ownership, the live REP-per-ETH price, and the pool security multiplier. Total ownership includes any unclaimed truth-auction allocation, so that allocation can provide minting headroom before it becomes fee-eligible. Existing open interest is not deleted when later repricing lowers live capacity, so this is not a continuous collateral-below-capacity invariant. Fee checkpointing and redemption cannot reclassify unsolicited ETH as collateral, and a price change reprices capacity without iterating through vaults. Example: If current oracle-priced capacity is 10 ETH, a mint that would raise tracked collateral from 9 ETH to 11 ETH reverts even if the raw balance contains surplus ETH. A later REP price change can lower capacity below 9 ETH without changing the existing open interest or ownership records. Type Conservation Enforcement status Reviewed preservation Primary evidence _requireCapacityNotExceeded , setPoolFinancials , and fee redemption , fork finalization BAL-04 REP coverage boundaries Required property A REP withdrawal or escalation deposit must leave the affected vault and aggregate pool totals independently passing both live open-interest health branches. A delegated liquidation receiver must pass both branches after accepting debt; liquidation does not require the unhealthy target to become healthy. Subsequent repricing may make an existing position unhealthy without deleting its open interest, enabling liquidation rather than retroactively reverting the price change. The associated-REP branch may count locally dispute-staked REP; the free-REP branch counts only pool-held vault REP and uses at least the 10,500-BPS liquidation-award reserve. Required backing rounds upward. A delegated receiver's factor of 10,000 is exactly the protocol minimum, and any higher approved factor multiplies both branches at execution. Example: A receiver approved at 12,000 BPS must retain 1.2× the upward-rounded associated-REP requirement and 1.2× the upward-rounded free-REP requirement after accepting live debt. Passing only one branch is insufficient. Type Safety Enforcement status Contract guard Primary evidence Capacity ownership and withdrawal checks BAL-05 REP backing units conversion consistency Required property After deposits, withdrawals, liquidations, escalation transfers, migration, and auction claims, the same current pool-held REP balance and total REP backing units govern both REP-to-backing-unit and backing-units-to-REP conversion. Example: Depositing REP and immediately converting the resulting REP backing units back to REP uses the same denominator rather than a stale pre-deposit balance. Type Conservation Enforcement status Reviewed preservation Primary evidence attoRepToBackingUnits and backingUnitsToAttoRep BAL-06 Fee liability accounting Required property Every attoETH of accrued fees is represented once as unallocated reserve or claimable vault fees, and redemption clears the claimable fee balance before ETH is sent. Example: Assigning 3 attoETH of reserve to a vault decreases unallocated reserve by 3 and increases that vault's claimable fees by 3, without creating a second claim. Type Conservation Enforcement status Reviewed preservation Primary evidence Fee accumulator and redeemFees BAL-07 Fee reserve protection Required property Fork transfers and redemptions cannot spend ETH reserved for unpaid or unallocated fees. Example: A fork transfer may move 8 ETH of collateral from a 10 ETH balance that also contains 2 ETH of fee liabilities, but may not move all 10 ETH. Type Conservation Enforcement status Contract guard Primary evidence transferEth BAL-08 Aggregate pool-ledger coherence Required property totalClaimableVaultFeesAttoEth equals summed unpaid vault fees. In Operational , fee-eligible capacity ownership equals summed live vault capacity ownership, and uncheckpointed capacity ownership equals capacity ownership whose vault fee index trails the global index. During ForkMigration , configured migrated capacity ownerships remain pending outside those pool aggregates. A PoolForked parent retains its fork-time total and fee-eligible snapshots as individual vaults migrate. After a positive-purchase truth-auction activation, total capacity ownership equals fee-eligible capacity ownership plus the outstanding unclaimed auction allocation. If the auction purchases zero REP, no auction capacity ownership exists: the child still records the parent's fork-time total, while the unmigrated remainder stays outside fee eligibility. The auction settlement specification defines that zero-purchase allocation rule. Example: Migrating one of two vaults that each own 3 REP of capacity leaves the parent's frozen 6 REP total unchanged and records 3 REP as pending in the child. With purchased REP, claims move the remaining 3 REP from auction allocation into fee eligibility. With zero purchased REP, auction allocation is zero and that remainder stays unassigned and ineligible. Type Conservation Enforcement status Reviewed preservation Primary evidence getPoolAccountingSnapshot and vault checkpointing , truth-auction claim accounting , stateful ledger and claim-order invariant tests SHARE-01 Complete-set symmetry Required property A complete-set mint or burn changes Invalid, Yes, and No balances by the same amount in the same universe. Example: Minting five complete sets creates five Invalid, five Yes, and five No shares for that universe. Type Conservation Enforcement status Contract guard Primary evidence mintCompleteSets and burnCompleteSets SHARE-02 Complete-set redemption Required property Before outcome finalization, burning B complete sets returns ⌊B × tracked collateral / complete-set supply⌋ after the fee checkpoint, then reduces supply by B and collateral by the ETH returned. Example: With 100 sets and 10 ETH of tracked collateral, burning 10 sets returns 1 ETH before integer-rounding residue. Type Conservation Enforcement status Reviewed preservation Primary evidence redeemCompleteSet SHARE-03 Positive-output minting Required property A successful positive-value complete-set mint returns a positive share amount. Example: A 1 attoETH mint that rounds to zero shares reverts instead of accepting the ETH. Type Conservation Enforcement status Contract guard Primary evidence attoEthToAttoShares and createCompleteSet SHARE-04 Child supply denominators Required property Child setup copies the frozen parent's remaining economic claim supply as its collateral denominator. That supply includes both materialized child ERC-1155 balances and source entitlements that can materialize later. Complete-set minting adds claims, while complete-set and winning-share redemption consume them. Example: If a parent freezes with 10 claims per outcome and a child has no materialized shares yet, setup still records 10. A new minter receives only the shares purchased at that established rate, while all 10 source claims retain their reserved collateral. Type Conservation Enforcement status Reviewed preservation Primary evidence attoEthToAttoShares and redeemShares and ShareToken.migrate SHARE-05 Winning-share payout cap Required property Total ETH paid to winning shares never exceeds the collateral available when redemption begins; rounding residue remains for later winning holders. Example: When two winners redeem sequentially, the second receives its fraction of the collateral left after the first rather than a fraction of the original balance paid twice. Type Conservation Enforcement status Contract guard Primary evidence redeemShares SHARE-06 Share supply conservation Required property For every ERC-1155 share ID, total supply equals the sum of holder balances through mint, transfer, and burn operations. Per-child materialization and duplicate prevention are owned by FORK-10 . Example: Moving two Yes shares between wallets leaves Yes supply unchanged; burning one Yes share reduces both the holder-balance sum and Yes supply by one. Type Conservation Enforcement status Reviewed preservation Primary evidence ERC-1155 supply accounting , modeled holder-supply invariant test VAULT-01 Dispute-staked REP isolation Required property REP escrowed in an escalation game cannot also be withdrawn, liquidated as pool-held vault REP backing, or migrated through the ordinary non-escrowed vault path. Example: A vault initially attributed 20 REP that dispute-stakes 5 REP is left with 15 REP of vault backing plus a separate 5 REP claim. It cannot make an ordinary withdrawal while that escrow remains; ordinary vault migration moves only the 15 REP backing, while the dispute-staked REP claim follows its separate migration path. Type Conservation Enforcement status Reviewed preservation Primary evidence Escrow checks , EscalationGame VAULT-02 Liquidation conservation and freshness Required property A liquidation transfers rather than creates aggregate REP backing units and capacity ownership, and total capacity ownership across target and receiver is conserved exactly. A nominal debt quote is bounded by the request, target open interest, and the amount whose complete REP award the target can fund. Proportional ownership is rounded downward; moved debt is the receiver's exact live open-interest increase and is bounded by that quote. A delegated route additionally bounds moved debt by its staged approval reservation; the self-receiving route has no approval reservation. On a full-target request, target-local bad debt is target open interest minus exact moved debt, so it may include an award-unfunded slice and integer-allocation residue. Because vault open interest is independently derived with upward rounding, exact before-and-after vault debt deltas are not a conservation identity. Unmatched ownership, escalation claims, accrued fees, and pool-held REP surplus remain with the target. Execution rejects changed target snapshots, enforces the receiver's live post-transfer health factor, and every delegated terminal path consumes or releases its reservation exactly once. Example: If the target or receiver changes after staging, execution may fail safely and release the full reservation. The operator receives no ownership merely for submitting the transaction. Type Conservation Enforcement status Reviewed preservation Primary evidence Liquidation design , performLiquidation , executeStagedOperation VAULT-04 Escalation claims are non-transferable and migration-neutral Required property An escalation claim is permanently bound to the depositor committed in its carry leaf and has no transfer path or parent-OI migration power. Liquidation cannot read, move, or acquire it. Final settlement pays the committed depositor after proof verification and replay protection. Example: Moving half a vault's capacity ownership leaves every escalation claim unchanged, and locking that claim cannot increase the OI routed to any child. Type Safety Enforcement status Reviewed preservation Primary evidence withdrawDeposit , liquidation design VAULT-03 Vault registry coherence Required property The vault registry is append-only and contains each nonzero address at most once. Registration does not require economic state: any vault path that calls _registerVault , including a public fee checkpoint for an empty address, can append it. Pagination is newest-registered first; later activity and full exit do not reorder or remove an entry. Consumers read current vault and escalation state to decide which registered vaults to display. Example: After a vault has fully exited and its REP backing, capacity ownership, claimable fees, escalation stake, bad debt, and open interest are all zero, its address remains in registry pagination. A UI can filter the empty position without losing historical discovery. Type Safety Enforcement status Reviewed preservation Primary evidence _registerVault , registry pagination and direct-claim boundary tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"forks","heading":"Forks and Child Isolation","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"FORK-01 Parent pool finality Required property Once the parent universe forks, parent operational flows freeze and the parent pool never re-enters Operational . Example: After fork activation, the parent cannot accept another REP deposit or later return from PoolForked to Operational . Type Safety Enforcement status Contract guard Primary evidence isOperational FORK-02 Immutable fork snapshot Required property When the parent first enters fork mode, it captures fork time, collateral, REP buckets, total REP backing units, escalation state, and capacity ownership exactly once. Those immutable values are the common basis for every child of that fork. Example: REP sent unsolicited to the parent after fork mode begins cannot change the REP snapshot used to initialize a later-created No child. Type Safety Enforcement status Reviewed preservation Primary evidence Fork snapshot preparation FORK-03 Isolated migration proxy Required property Each parent pool maps to exactly one deterministic migration-proxy address, and that address is the pool's isolated identity in the Zoltar migration ledger. Example: Two parent pools using the same forker derive different proxy addresses and therefore cannot spend each other's migration credit. Type Safety Enforcement status Reviewed preservation Primary evidence getMigrationProxyAddress FORK-04 Prefunded proxy isolation Required property Preexisting or unsolicited REP at the deterministic proxy cannot block initiation or become unclassified migration principal. Example: An attacker sending 1 REP to the future proxy address cannot make fork initiation revert or count that REP as pool migration principal. Type Liveness Enforcement status Reviewed preservation Primary evidence initiateSecurityPoolFork FORK-05 Eight-week migration boundary Required property Statoblast child-pool creation, pool-local vault migration, pool-proxy REP splitting, and own-fork claims are allowed through forkActivationTime + 8 weeks , inclusive, and are closed after that timestamp. forkActivationTime is recorded when the parent pool enters PoolForked ; post-migration activation begins only after its window ends. Share materialization has no expiry, but after the deadline it can target only an already-created child. This pool-local boundary does not limit Zoltar.deployChild , addRepToMigrationBalance , or splitMigrationRep ; those Zoltar operations follow their own fork identity and balance guards without this timestamp check. Example: Vault migration succeeds exactly at the eight-week deadline and reverts one second later. Type Safety Enforcement status Reviewed preservation Primary evidence forkActivationTime recording and child guards , MIGRATION_TIME , deployChild , addRepToMigrationBalance , and splitMigrationRep , migration deadline tests FORK-06 Child deployment identity Required property A deployed child must name the requesting pool as its parent and must use the child universe derived from that parent's selected branch. Its factory, forker, auction, share token, pool question, and security multiplier must equal the values supplied or inherited by the parent factory path. External-universe fork initiation requires the source pool to be authorized by its declared share token; own-game initiation does not. That relationship is not proof of configured-factory registration. The auction must have deployed code and must not already be trusted for another child. Example: On the external-universe path, the forker first requires the source to be authorized by its declared share token without treating that check as factory registration. The configured canonical factory installs the parent's share token, pool question, and multiplier in the child; the post-deployment guard then checks deployed and unique auction assignment plus parent, universe, factory, forker, and auction relationships before linking it. Type Safety Enforcement status Reviewed preservation Primary evidence _prepareForkState external-fork authorization guard , _validateChildPoolDeployment and deployChildSecurityPool FORK-07 Single-use non-escrowed vault migration Required property Vault migration credits one child and clears the corresponding parent REP backing units and capacity ownership before reuse. It checkpoints and retains claimable fees in the parent vault while routing proportional settlement collateral separately at pool level. Example: After Alice migrates her non-escrowed vault accounting to the Yes child, a second migration call cannot credit the same parent REP backing units to the No child. Type Conservation Enforcement status Reviewed preservation Primary evidence _migrateNonEscrowedVaultAccounting FORK-08 One-time aggregate continuation backing Required property Each selected child initializes the canonical carry snapshot and receives the complete aggregate continuation backing at most once. Optional vault cleanup only clears unresolved parent escalation-deposit accounting; it creates no child escrow and cannot change carried-proof eligibility. The continuation cannot resume until its game balance covers all effective unrelated-fork principal. For an own fork, initial backing must cover sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋ , the rounded-up 80% minimum, and the live balance must cover the unexported remainder after valid direct pre-resume claims. sourcePrincipalAtForkAttoRep is the raw aggregate stored by the fork snapshot before effective direct-claim deductions. Example: Creating the Yes child installs its snapshot and aggregate backing before Alice acts. Alice may later clear her unresolved parent escalation-deposit accounting, but that cleanup neither funds the child nor enables or disables her winning proof. Type Conservation Enforcement status Contract guard Primary evidence _ensureChildEscalationBacking , resumeFromFork , isForkCarryFundingComplete , _exportForkedEscrowByOutcome , and fork-backing regression tests FORK-09 Cumulative child collateral Required property Cumulative parent ETH transferred to children never exceeds the fork collateral snapshot and is derived from cumulative migrated REP, not per-call rounding. Example: If several calls collectively migrate R child REP, their collateral target is ⌈fork collateral × R / denominator⌉ , independent of how that same R is partitioned. The denominator is vaultRepAtForkAttoRep for an own fork and auctionableAttoRepAtFork otherwise. Type Conservation Enforcement status Contract guard Primary evidence _transferForkMigratedCollateralToChild FORK-10 Share migration integrity Required property Share migration preserves the holder's parent token-ID balance as a persistent entitlement, rejects duplicate or malformed target outcomes, and mints only the unmaterialized balance into each selected child. A used source balance cannot transfer. Example: Materializing seven parent Yes shares in one child leaves all seven parent entitlements visible and locked. A later call can mint seven into another existing child, but cannot mint the first child twice. Type Conservation Enforcement status Contract guard Primary evidence ShareToken.migrate FORK-11 Unequal child share supplies Required property Child pricing uses the fork-time economic claim supply rather than currently materialized outcome supplies. Unequal ERC-1155 supplies therefore do not block complete-set minting or proportional complete-set redemption. The denominator lifecycle and payout mechanics are owned by SHARE-04 . Example: If the economic supply is 12 while materialized supplies are 10, 8, and 6, a new complete set is priced against 12 and adds the same newly purchased balance to all three materialized supplies. Type Conservation Enforcement status Reviewed preservation Primary evidence setTotalSharesAttoShares during auction preparation , attoSharesToAttoEth , redeemCompleteSet , and redeemShares , mintCompleteSets and persistent migration accounting FORK-12 Child activation after settlement Required property Value-free truth-auction settlement activates a child with its tracked migrated collateral plus retained auction ETH, which cannot exceed the parent fork snapshot. Fork-time economic claim accounting keeps later minting and proportional burns independent from materialization order. Example: A child with 9 ETH against a 10 ETH fork snapshot activates after value-free finalization with 9 ETH of tracked collateral; its later operations remain subject to the ordinary share-supply and minting guards. Type Safety Enforcement status Reviewed preservation Primary evidence Truth-auction start and finalization","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"escalation","heading":"Escalation Games and Carried Claims","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"ESC-01 Total escrow reconciliation Required property totalDisputeStakedAttoRep equals effective live vault-denominated escrow plus effective aggregate continuation backing in forkCarryDisputeStakedAttoRep . Continuation backing is not assigned to child-vault health or migration power, but it must enter the aggregate before the truth auction so physically held REP cannot escape the repair haircut. Auction retention floors the aggregate total and the continuation bucket independently; do not reconstruct either aggregate by summing independently rounded vault views. Example: If vault-denominated escrow contributes 4 and 6 REP and an external-fork continuation carries 12 REP of aggregate proof backing, the total is 22 REP, represented as 22 × 10^18 attoREP in totalDisputeStakedAttoRep , before the auction. A 75% retention records ⌊22 × 75%⌋ = 16 total effective REP and ⌊12 × 75%⌋ = 9 aggregate continuation REP. This makes the carried claims absorb the same repair loss without pretending that their backing belongs to a particular child vault. Type Conservation Enforcement status Reviewed preservation Primary evidence Continuation initialization , truth-auction retention accounting ESC-02 Local unresolved REP reconciliation Required property totalLocalUnresolvedAttoRep equals the sum of unresolved local deposits, and each vault-local counter is its exact component. Example: If Alice has 3 unresolved REP and Bob has 5, their counters are 3 and 5 and the global local-unresolved total is 8. Type Conservation Enforcement status Reviewed preservation Primary evidence Unresolved counters ESC-03 Outcome carry reconciliation Required property For each outcome, currentCarryTotalAttoRep equals effective inherited unresolved principal plus unresolved local deposits. Effective inherited principal subtracts immediate-parent direct claims and becomes zero for an inherited losing outcome after finalization. Example: A child snapshots 7 Yes REP, the parent directly claims 2 REP, and the child receives a new unresolved 2 REP Yes deposit. Its current Yes carry is (7 - 2) + 2 = 7 REP . Type Conservation Enforcement status Reviewed preservation Primary evidence Escalation accounting invariants ESC-04 Stable deposit identifiers Required property An ordinary game's stable deposit identifier is its local deposit index. A continuation game derives the identifier from its own address, outcome, and local index, so two different continuation deposits cannot share a carry or nullifier key merely because their local indexes match. Example: Deposit zero on Yes in a child game and deposit zero on Yes in its grandchild produce different stable identifiers. Type Safety Enforcement status Reviewed preservation Primary evidence Stable deposit identity ESC-05 Single-use claims per branch Required property Along any single root-to-descendant fork path, each local or inherited deposit may be claimed, settled, refunded, or exported at most once. Child nullifiers are branch-local, but a direct ancestor claim invalidates the matching proof in every descendant and reduces their effective inherited principal. Otherwise, selected sibling branches maintain separate claim authorization. Example: Consuming a carried proof in a Yes grandchild prevents replay farther down that branch while a sibling remains independent. If the deposit was instead claimed directly from their ancestor, both descendants reject it. Type Safety Enforcement status Reviewed preservation Primary evidence Carry nullifiers , MMR proofs ESC-06 Aggregate export accounting Required property Aggregate unresolved export clears the exporting vault's three outcome totals, unresolved counter, and parent escrow exactly once. It retains the deposit rows and carry commitment solely as immutable proof material for selected children. Example: After Alice exports her aggregate claim, her parent escrow is zero even though her historical deposit row remains available to prove a child claim. Type Conservation Enforcement status Reviewed preservation Primary evidence exportVaultUnresolvedTotalsWithoutTransfer ESC-07 Authenticated carried winner payout Required property A carried claim must authenticate an unconsumed proof for the final winning outcome. The proof fixes the original claim identity and its committed depositor, who receives the normal winning payout even when another account relays the transaction. Liquidation cannot change that recipient. Example: Bob may relay Alice's valid winning proof, but the payout still goes entirely to Alice as the committed depositor. A losing proof cannot consume backing, and reward math may make a winning payout greater than its principal. Type Conservation Enforcement status Contract guard Primary evidence withdrawDeposit ESC-08 Game REP settlement conservation Required property Every REP unit held for a game is accounted for exactly once as winning principal, reward funding, losing principal, unsettled residual, or REP swept back to the pool. Settlement cannot create an additional REP claim. Example: Paying winners and sweeping the final residual reduces the game balance to zero without total payouts exceeding the REP previously held. Type Conservation Enforcement status Reviewed preservation Primary evidence Settlement ESC-09 Terminal game outcomes Required property Once resolution or non-decision closes a game path, later deposits cannot reopen or change that terminal result. Example: After Yes becomes final, a later attempt to deposit on No reverts rather than changing the winner. Type Safety Enforcement status Contract guard Primary evidence EscalationGame ESC-10 Ancestor claim replay protection Required property If a deposit is claimed directly from an ancestor during an own fork, every existing or later descendant treats the corresponding proof as spent even if that descendant has not written its own nullifier. Example: Claiming deposit 4 from the parent blocks proof 4 in an already-created child and in a grandchild created later. Type Safety Enforcement status Reviewed preservation Primary evidence Forked claims ESC-11 Residual sweep preconditions Required property Residual REP cannot be swept until effective unresolved principal is zero for every outcome and totalDisputeStakedAttoRep is zero. Inherited losing principal retires at finalization without a proof; winning inherited proofs and every local unresolved deposit still require their applicable terminal transition. Example: After Yes finalizes, an unclaimed inherited No leaf does not block sweeping. An unclaimed inherited Yes proof, an unsettled local deposit, or a live vault escrow record still does. Type Conservation Enforcement status Contract guard Primary evidence sweepResidualRepToSecurityPool ESC-12 Pool and continuation payout agreement Required property When a universe forks on a pool's question, each child pool and its continuation game use that child's Invalid, Yes, or No branch as the final pool-question outcome. Once a pool inherits that fixed outcome, new local escalation deposits and every later fork transition revert, whether the new universe fork uses the same question or another one. Deposit withdrawal reverts unless the pool and game report the same outcome. Example: A Yes child created by a fork on the pool question pays carried Yes deposits even if copied game balances favored No. It rejects new local dispute-staked REP before escrow, so a later local non-decision cannot lock vault redemption. A later matching or unrelated universe fork leaves the stored SystemState.Operational and fixed Yes outcome unchanged, and eligible share, vault REP, and carried-proof redemption paths remain available, while universe-fork guards still freeze normal operating calls. Type Safety Enforcement status Reviewed preservation Primary evidence activateForkMode , getQuestionResolution , fixed-outcome propagation , and pool/game payout check ESC-13 Non-decision threshold and live start-bond arithmetic Required property For a positive fork threshold F , non-decision requires ⌈F / 2⌉ REP on two outcomes. Therefore the two threshold balances total at least F , while one attoREP less on both outcomes totals strictly less than F . At pool construction, the configured start bond is max(1 REP, theoretical REP supply / 10,000,000) , and a new origin requires the live non-decision threshold to exceed it. If an existing pool's live threshold later falls to or below that configured bond, ordinary game deployment clamps the live start bond to nonDecisionThresholdAttoRep - 1 , provided the threshold exceeds one attoREP. Example: If F = 5 , non-decision requires 3 REP on two outcomes, so the two balances hold 6 REP; balances of 2 and 2 remain below the 5 REP fork threshold. Type Safety Enforcement status Contract guard Primary evidence getNonDecisionThresholdAttoRep , origin threshold admission , deployEscalationGame , and start-liveness regression tests ESC-14 Carry commitment structural integrity Required property For every outcome, exported carry peaks, leaf count, unresolved total, and nullifier root equal the corresponding outcome state. Only leaf-count-selected peak heights are occupied, independently bagging those peaks yields the exported root, and consumed proof indexes are unique. The accounting definition of unresolved total is owned by ESC-03 . Example: Three Yes leaves occupy the height-zero and height-one peaks; bagging those two peaks reproduces the exported Yes root before and after one inherited proof is consumed. Type Conservation Enforcement status Reviewed preservation Primary evidence getForkCarrySnapshot and getForkCarryRoots , bagCarryPeaks , independent carry-structure invariant tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"oracle","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A18 independent correction incentive , A19 observable correctable price , and A06 lifecycle executors . Accepted oracle design properties—not security findings. Statoblast uses profitable correction, not operation-value collateralization. Report liquidity is intentionally not required to equal or bound the value of withdrawals, liquidations, capacity ownership changes, pool assets, or cumulative operations that use an accepted price. The protocol also makes no bounded-liveness claim for a pending sponsor lane. Every valid dispute restarts settlement, incurs transaction execution, and must fund the contract-specified replacement position. Under ordinary non-dust parameters, protocol fees and the required position also accumulate; before the escalation halt, integer flooring can leave dust-sized rounds unchanged. A participant willing and able to keep submitting and funding disputes may therefore delay unrelated users without a coordinator-level deadline. This paid, capital-unbounded pause is intentional and is not a violation of a Zoltar liveness invariant. Security review should verify the correction-profit calculation, report accounting, dispute access, and the stated inclusion assumption. A concrete bypass of the required position or fee, capital reuse, invalid deadline transition, or cache-conformance failure remains a finding. The mere absence of a report-notional bound or absolute pending-report duration does not. ORA-01 Authorized settlement callbacks Required property Only the configured OpenOracle may supply a settlement callback, and its report identifier must equal the coordinator's pending report. Example: A callback from another contract or for an older report ID cannot update the cached REP/ETH price. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback ORA-02 REP-per-ETH price direction Required property An accepted price is positive REP per ETH: amount2 REP × 1e18 / amount1 WETH . Higher values require more REP for the same ETH obligation. Example: A report of 200 REP for 2 WETH stores 100 REP per ETH, not 0.01 ETH per REP. Type Safety Enforcement status Contract guard Primary evidence openOracleCallback , ratio direction ORA-03 Five-minute price validity Required property A cached price is usable only while nonzero and strictly younger than the five-minute validity window. Example: A price settled exactly five minutes ago is stale; one settled four minutes and 59 seconds ago remains usable. Type Safety Enforcement status Contract guard Primary evidence isPriceValid ORA-04 Single-use staged operations Required property Every staged operation has one immutable identifier. Once execution is attempted with a usable price, success or a caught failure consumes that identifier, emits the result, and prevents a retry. An uncaught transaction revert instead rolls back the entire attempt. Example: A pool-level withdrawal rejection is caught and emitted as failed, after which the same operation ID cannot be executed again. Type Safety Enforcement status Contract guard Primary evidence executeStagedOperation ORA-05 Staged operation targets Required property Withdrawals target the initiating vault. Liquidations name separate operator, receiver, and target roles; the receiver must differ from the target, while the operator may equal either. A liquidation fails when the protected target REP-backing-unit or capacity-ownership snapshot becomes stale. Example: Alice can stage her own REP withdrawal. A liquidation operator can target Bob, but its receiver cannot be Bob; execution fails if Bob changes his REP backing units or capacity ownership after staging. Type Safety Enforcement status Contract guard Primary evidence Staging and snapshot checks ORA-06 Consumed execution failures Required property An expired operation, a liquidation with a stale target snapshot, a withdrawal that would move zero REP, or a liquidation inside the configured minimum price distance is consumed with an observable failure result rather than remaining executable forever. Example: If an earlier withdrawal empties a vault, its second queued withdrawal is consumed as a zero-effect failure rather than retried after every price update. Type Liveness Enforcement status Contract guard Primary evidence executeStagedOperation ORA-07 Staged-operation queue and index bijection Required property Operation IDs are append-only. Active enumeration contains each and only each operation with a live initiator; the bounded pending settlement queue contains unique active IDs; and pendingOperationSlotId is zero for an empty queue or equals its head. Example: Settling four queued operations removes those four IDs from both indexes, leaves an overflow operation active for manual execution, and does not reuse any consumed ID. Type Safety Enforcement status Reviewed preservation Primary evidence active and pending staged-operation indexes , queue/index invariant test ORA-08 Dispute and settlement boundary Required property The settlement deadline is exclusive: disputes require the current clock to be strictly before the deadline, while settlement is available at and after it. Example: At timestamp deadline , a new dispute is rejected and settlement is allowed. Type Safety Enforcement status Contract guard Primary evidence settle and dispute validation ORA-09 Oracle balance-domain boundary Required property Direct REP or WETH held by the coordinator is outside OpenOracle and remains untouched by settlement or recovery. OpenOracle credits balances by beneficiary rather than by report: the callback sends the coordinator's entire withdrawable internal REP and WETH credit to the recorded pending sponsor, including any third-party deposit that names the coordinator as beneficiary. Example: REP transferred directly to the coordinator remains there, while REP deposited into OpenOracle for the coordinator is swept with the report proceeds to the current sponsor and leaves only OpenOracle's balance sentinel. Type Conservation Enforcement status Reviewed preservation Primary evidence pendingReportSponsor and _withdrawOpenOracleReporterBalances , deposit and withdrawTo , coordinator/OpenOracle balance-domain test","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"auctions","heading":"Truth Auctions","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"AUC-01 Bid admission Required property Bids are accepted only during the one-week window, at a supported tick whose computed ETH-per-REP price is positive, and at or above the auction's minimum bid size. Example: A bid submitted one second after the one-week window or below the minimum bid size reverts without being recorded. Type Safety Enforcement status Contract guard Primary evidence submitBid AUC-02 Auction caps Required property Final retained ETH never exceeds the ETH raise cap, and total purchased REP never exceeds the REP sale cap. Example: If bids offer 12 ETH against a 10 ETH cap, finalization retains at most 10 ETH and refunds the rest. Type Conservation Enforcement status Contract guard Primary evidence computeClearing and finalize AUC-03 Single-use bid settlement Required property Each bid is settled or refunded at most once; claimed state is set before an external ETH refund. The best-effort push is gas-bounded, and any rejected, reverted, or gas-exhausted positive refund is moved to bidder-specific pull escrow without reverting the bid's REP or capacity ownership settlement. Example: A bidder whose refund callback reenters, rejects ETH, or consumes its complete callback budget cannot withdraw the same bid a second time or starve the remaining settlement because the bid is already marked claimed and the push is gas-bounded; deferred ETH remains available through withdrawPendingEthRefund . Type Safety Enforcement status Contract guard Primary evidence withdrawBids and refund functions AUC-04 Claim-order independence Required property Cumulative allocation math makes per-bid REP, refund, and capacity ownership results independent of post-finalization claim order. Example: Alice claiming before Bob gives each vault the same REP, ETH refund, and capacity ownership as Bob claiming before Alice. Type Conservation Enforcement status Reviewed preservation Primary evidence _allocateFromCumulativePosition AUC-05 Allocation and refund reconciliation Required property A zero-demand auction records zero purchased REP. Otherwise, the sum of all winning-bid REP allocations equals the stored aggregate purchase, and each bid's unretained ETH remains refundable. Example: If finalization stores 30 purchased REP, all winning claims together receive exactly 30 REP and every unretained attoETH is refunded. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction settlement AUC-06 Bidder-vault credits Required property A bid claim keeps purchased REP pool-held and credits corresponding REP backing units plus the bid's fixed-position share of the complete unmigrated capacity ownership to the bid's recorded vault. These credits add to rather than replace that vault's migrated position, and cumulative allocation keeps capacity ownership rounding independent of claim order. A winning dust bid still receives a positive fixed-position capacity ownership share when its REP allocation rounds to zero. Example: A vault with migrated REP backing units keeps them when settling a winning auction bid; the claim adds the REP backing units and deterministic capacity ownership share assigned to that bid while the purchased REP remains pool-held. If a dust bid's REP share is zero but its capacity ownership share is positive, claiming that bid alone still credits the capacity ownership. Type Conservation Enforcement status Reviewed preservation Primary evidence Auction-claim settlement AUC-07 Uniform weak-demand allocation Required property In underfunded clearing, qualifying ETH is retained bid ETH offered at or above the cap-implied qualification threshold. The threshold is not an execution-price floor. Qualifying bidders collectively receive maxAttoRepBeingSold in proportion to that ETH at the aggregate effective price underfundedWinningAttoEth / maxAttoRepBeingSold . Per-bid cumulative floors then assign indivisible attoETH and attoREP, so a dust winner can round to zero REP without changing the aggregate price. With no qualifying ETH, every bid refunds. Example: If Alice supplies 60% and Bob 40% of qualifying ETH, they receive 60% and 40% of the complete REP sale cap subject to deterministic attoREP rounding. Type Conservation Enforcement status Reviewed preservation Primary evidence Truth Auction clearing design AUC-08 Forced-ETH-resistant finalization Required property Auction finalization succeeds regardless of unsolicited ETH already present in the child pool or forker. Example: One attoETH forced into the child before finalization remains surplus and does not change protocol-accounted collateral. Type Liveness Enforcement status Reviewed preservation Primary evidence finalizeTruthAuctionRepair AUC-09 Bounded bid settlement Required property After the auction deadline, value-free finalization succeeds using migration-routed collateral plus retained auction ETH. Qualifying bids settle for REP, non-qualifying bids refund, and nonzero finalizer ETH reverts. Example: For a 10 ETH target with 6 ETH migrated and 3 ETH retained from bids, the child activates with 9 ETH of accounted collateral and every bid can then settle. Type Liveness Enforcement status Contract guard Primary evidence Weak-demand behavior , finalizeTruthAuctionRepair AUC-10 Persistent tick bid history Required property Bid cumulative ETH remains append-only for a tick even when the active AVL node is deleted and later recreated. Refunded history is subtracted exactly once, so every later accepted bid retains a valid allocation or refund path. Example: Deleting an empty price tick and later bidding at that price continues cumulative ETH after the old history instead of making the new bid unclaimable. Type Conservation Enforcement status Reviewed preservation Primary evidence _appendBid and refund-prefix accounting and repeated tick deletion/recreation tests AUC-11 Auction tree and public-model equivalence Required property Before finalization, active tick pages contain exactly the nonzero ticks not removed by pre-finalization refunds, in descending order, and clearing computed from the AVL tree equals an independent calculation over those bids. Finalization freezes that tree and clearing result: later claims change bid flags and ETH liabilities but not active tick pages. Post-finalization liabilities are owned by AUC-12 . Historical insertion and refund-prefix obligations are owned by AUC-10 . Example: Before finalization, an independent descending-tick scan returns the same clearing result as the contract. After a winner claims, its bid is marked claimed and its refund liability leaves, while the final clearing page and result remain unchanged. Type Safety Enforcement status Reviewed preservation Primary evidence AVL tree, enumeration, and clearing , seeded independent-model and frozen-snapshot tests AUC-12 Auction ETH liability conservation Required property Before finalization, auction ETH equals active unrefunded bids plus aggregate pendingEthRefundsAttoEth and explicit surplus. After finalization, the remaining balance equals refunds still attached to unclaimed bids plus deferred pendingEthRefundsAttoEth and explicit surplus. Per-bid settlement partitions and aggregate REP allocation are owned by AUC-05 . Example: If a rejected pre-finalization push removes a 2 ETH losing bid from active clearing, raw ETH still equals the remaining active bids plus the 2 ETH deferred refund. After finalization, the same deferred bucket composes with unclaimed-bid refunds and any forced surplus. Type Conservation Enforcement status Reviewed preservation Primary evidence finalize , withdrawBids , and deferred refunds , seeded and deferred-refund ETH-liability tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"lifecycle","heading":"Lifecycle, Liveness, Observability, and External Calls","keywords":[],"path":"reference/invariants.html","sectionTitle":"Reference","summary":"Contract, lifecycle, cross-contract, liveness, and economic properties.","text":"Security assumptions: A02 viable operating costs , A16 timely inclusion , A06 lifecycle executors , and A26 Ethereum execution . LIFE-01 Pool state transitions Required property Pool state topology permits only Operational → PoolForked for a parent and ForkMigration → ForkTruthAuction → Operational for a child. Example: A child cannot skip directly from ForkMigration to Operational while repair auction work remains required. Type Safety Enforcement status Reviewed preservation Primary evidence SystemState , fork transitions ; see FORK-12 for child accounting consistency. LIFE-02 Permissionless transition liveness Required property Once a resolution, migration, auction-completion, or withdrawal function's documented semantic preconditions hold, dust, unsolicited balances, empty collections, or earlier permissionless calls cannot permanently prevent some caller from completing it. Example: Forced ETH and an empty losing-bid list do not prevent a caller from finalizing an otherwise complete truth auction. Type Liveness Enforcement status Proposed guard Primary evidence FORK-04 , BAL-02 , and AUC-08 LIFE-03 Atomic failure behavior Required property An uncaught revert rolls back every state mutation and transfer in the transition; deliberate consumed failures emit an explicit result and cannot be retried. Example: If a child migration reverts after an attempted token transfer, neither the transfer nor its accounting update remains committed. Type Safety Enforcement status Reviewed preservation Primary evidence Solidity atomicity, coordinator consumed-failure paths LIFE-04 Deadline equality rules Required property Every deadline assigns equality to one documented side of the boundary: migration remains open at equality; auction bidding and public pool finalization are both closed at equality, and finalization opens immediately afterward; oracle disputes are closed and settlement is open at equality; staged operations expire only after equality; and an escalation result is final only after its end timestamp. Example: Exactly at an auction deadline, both a bid and public pool finalization revert; one second later, finalization succeeds. Type Safety Enforcement status Proposed guard Primary evidence Boundary tests across fork migration, auctions, escalation, and OpenOracle LIFE-05 Bounded transition work Required property No mandatory transition loops over an attacker-unbounded history in one transaction; callers use bounded pages, fixed outcome sets, proofs, or balanced-tree traversal. Example: Settling one carried deposit verifies its bounded proof instead of iterating over every deposit ever made in its ancestors. Type Liveness Enforcement status Reviewed preservation Primary evidence Vault pages, bid pages, MMR proofs, and auction AVL traversal OBS-01 Event-state replay equivalence Required property Ordered canonical events reconstruct the same universe, pool, vault, coordinator, and escalation carry state exposed by storage. Replay isolates contract-local counters and recognized emitters, and every replay-supported state transition emits the checkpoint or delta needed to reach its resulting storage value. Example: Replaying seeded pool and vault operations matches their accounting storage, while replaying a deployed parent deposit, fork snapshot, and child carry checkpoint matches the child's roots, peaks, leaf counts, unresolved totals, and nullifier roots. Type Safety Enforcement status Reviewed preservation Primary evidence contract interaction reference , event replay model , actual-log storage-equivalence tests EXT-01 Genesis token behavior boundary Required property Genesis REP and configured WETH obey the exact transfer, return-value, decimal, and callback behavior assumed by pool and oracle accounting. Mainnet relies on external token behavior; Sepolia relies on verified deployments of the reviewed GenesisReputationToken and WETH9 contracts for issuance and transfer mechanics. Burn-sink inaccessibility remains an external assumption on both networks. Example: Genesis REP transfer returns and moves exactly the requested attoREP rather than charging a fee that leaves pool accounting overstated; child REP supply remains bounded by its contract-enforced theoretical ceiling. Type External assumption (mainnet); mixed safety and external assumption (Sepolia) Enforcement status External on mainnet; reviewed issuance and transfer guards under A23 and A26 on Sepolia, with burn-sink inaccessibility still assumed Primary evidence SafeERC20Ops , GenesisReputationToken , WETH9 , the network deployment manifests, and security assumption A21 Related enforced property Canonical child REP behavior is enforced by ReputationToken and its Zoltar-only mint and burn authority. EXT-02 Callback-safe accounting Required property External ETH or token callbacks observe effects already committed for that action. A required delivery that fails reverts without leaving partial accounting; an explicitly deferrable delivery instead commits the action together with an exactly accounted recipient liability. Example: A fee recipient's fallback observes its claimable fees already cleared, so reentry cannot redeem the same claimable fees twice. Type Safety Enforcement status Reviewed preservation Primary evidence Pool redemption, auction refund and deferred-credit, and oracle refund paths Related external liveness boundary A22 participant-controlled recipient compatibility EXT-03 ERC-1155 callback safety Required property ERC-1155 receiver callbacks cannot mint beyond pool capacity, alter the amount minted, or reenter the same accounting effect twice. Example: A receiver callback during complete-set minting cannot call back into the pool and reuse the same ETH capacity for a second mint. Type Safety Enforcement status Reviewed preservation Primary evidence createCompleteSet , ERC-1155 receiver checks, and receiver callback tests Related external liveness boundary A22 participant-controlled recipient compatibility EXT-04 Same-block deadline ordering Required property At a deadline timestamp, mutually exclusive before-deadline and after-deadline actions cannot both succeed. Explicit comparison operators assign equality to one phase, and transaction ordering within the block cannot change that assignment. Example: At the OpenOracle settlement deadline, ordering a dispute before settlement in the block still cannot make the dispute valid. Type Safety Enforcement status Proposed guard Primary evidence ORA-08 and same-block auction ordering analysis EXT-05 Recursive fork gas bound Required property Origin registration, inherited-fork detection during child construction, and direct-claim replay checks must not traverse universe or pool ancestry. Example: At lineage depths 1 and 32, a replay query for the same global deposit id performs the same lineage-registry and claimed-id lookups rather than 1 or 32 parent calls. Type Liveness Enforcement status Reviewed preservation Primary evidence SecurityPoolFactory.getOriginId and getPoolId , cached inherited-fork registry tests, SecurityPoolForker.getEscalationDepositId , and depth-independent replay gas tests","title":"Protocol invariants","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["assumptions","security","threats","guarantees"],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Statoblast and Zoltar enforce accounting, authorization, and lifecycle rules onchain. Their economic safety and practical liveness still depend on the participant, market, asset, deployment, client, data, Ethereum, and cryptographic assumptions below. An assumption is not a guarantee enforced by the contracts. Each entry says what must remain true, what can fail if it does not, and where the related mechanics are explained. The Protocol Invariants catalog lists contract-enforced properties and their implementation evidence. Participation Forks and markets Oracle and liveness Technical foundation Excluded guarantees","title":"Security model","topic":"Safety","weight":1},{"fragment":"orientation","heading":"Security-model orientation","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"This catalogue protects REP, ETH settlement collateral, outcome shares, vault backing, escalation deposits, child-universe accounting, and the integrity of event-derived state. Boundary Examples Trusted protocol components Zoltar, security pools, share tokens, coordinators, forkers, and their configured delegates. External dependencies OpenOracle, WETH/REP token behavior, Ethereum execution and finality, RPCs, wallets, and proof data. Untrusted actors Traders, vault owners, liquidators, disputers, bidders, settlers, keepers, and indexers. Failure outcomes Reverted calls, delayed transitions, underfunded child repair, stale prices, incorrect external data, or unreconstructable history. The assumptions below explain what must remain true for these assets and transitions to retain their intended safety or liveness properties; they are not additional contract guarantees.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-value","heading":"Participation and coordination","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions explain why someone performs the economically useful action instead of merely being permitted to perform it.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a01","heading":"Participants act on risk-adjusted economic incentives","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Enough participants prefer actions with greater expected value after gas, fees, delay, capital lockup, and risk. The model does not rely on altruistic defense. Failure boundary: A nominally profitable liquidation, dispute, bid, migration, or fork defense may not happen when its risk-adjusted return is unattractive. Mechanics: economic roles , liquidation incentives , auction clearing , and oracle incentives .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a02","heading":"Required actions remain worth their operating costs","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each time-sensitive action, transaction fees, opportunity cost, coordination cost, and capital lockup remain small enough relative to the value protected or earned. Failure boundary: An action can be technically executable but economically abandoned before its deadline. Mechanics: auction gas bounds , oracle gas parameters , and escalation deployment costs .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a03","heading":"Each economic role has enough independent participants","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Trading, liquidation, escalation, migration, auction bidding, price correction, and settlement have enough non-colluding participants for the relevant action. Secondary-market trading is external to Statoblast. Failure boundary: Thin or controlled participation can remove price discovery, competitive bidding, or an opposing outcome deposit. Mechanics: economic roles , fork coordination , and auction participation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a04","heading":"Participants can mobilize REP to raise an alarm","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Market participants can mobilize enough vault-backed REP through vault owners to fund an early competing Invalid , Yes , or No deposit and keep the local contest active while additional capital reacts. Failure boundary: A wrong local outcome can become final before better-capitalized participants enter. Mechanics: escalation resolution , escalation accounting , and operator guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a05","heading":"Participants value continued access to the supported Statoblast lineage","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The participants whose coordination determines durable use value continued access to the supported Statoblast lineage at least as highly as its configured statoblastSecurityMultiplierBps basis points of the value secured by that lineage. BPS_DENOMINATOR = 10_000 converts that stored value to its human multiplier. Value Statoblast access × BPS_DENOMINATOR ≥ statoblastSecurityMultiplierBps × Value secured Compare one lineage’s continued-use value and secured value at the same pre-attack time. Failure boundary: Participants may rationally abandon, grief, or coordinate away from a lineage whose continued-use value is too small. Mechanics: fork security , fork migration , and Statoblast security boundary .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a06","heading":"At least one executor advances every required public transition","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"An adequately funded participant or service monitors and calls each needed permissionless transition, including liquidation initiation, local escalation deposits and settlement, fork activation, child creation, vault and share migration, auction start and finalization, bid settlement, OpenOracle report settlement, settled-report callback recovery, overflow operation execution, and proof-based claims. Failure boundary: Permissionless state can remain stalled even though no contract authorization prevents progress. Mechanics: lifecycle liveness , fork operations , and oracle operations .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-forks","heading":"Forks, truth, value, and liquidity","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions connect onchain branching and accounting to offchain truth judgments, asset value, liquidity, and migration.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a07","heading":"A supported lineage preserves enough aggregate child value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For each protected Statoblast origin lineage, evaluate the parent and its child-pool continuations in one numeraire at the fork-security decision horizon, before long-run value has fully concentrated on one branch. The sum of traded child-lineage values remains large enough compared with that parent lineage’s pre-fork value under the origin’s immutable statoblastSecurityMultiplierBps . Each child asset is valued in its own branch; unrelated external collateral is not counted again. Value parent lineage ≤ ∑ child ∈ Lineage continuations Value child × BPS_DENOMINATOR statoblastSecurityMultiplierBps lineage Parent and child values use the same origin lineage, valuation source, numeraire, and decision horizon. For multiplier 20_000 BPS , aggregate child-lineage value must be at least twice parent-lineage value; multiplier 30_000 BPS requires three times that value. Failure boundary: Value destruction or fragmentation can make a malicious or unnecessary fork cheaper than the harm it causes. Mechanics: fork economics , fork coordination , and pool migration .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a08","heading":"REP market capitalization approximates economic value","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At the stated valuation time and system scope, REP market capitalization is a sufficiently accurate observable proxy for the discounted cash flow participants expect from REP. Failure boundary: Thin, manipulated, or speculative pricing can overstate the economic value securing open interest. Mechanics: REP economics and economic backing claims .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a09","heading":"Users value the universes they judge truthful","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users prefer the universe or universes they judge truthful, so economic activity and durable asset value concentrate there. Unlike A07’s fork-security decision horizon, this assumption describes the later, durable allocation of value after users coordinate. Value before fork ≈ Value truthful universe When several branches remain plausibly truthful, document the value split instead of assuming one winner. Failure boundary: Coordination on a false branch, or durable fragmentation across branches, defeats the value-concentration security argument. Mechanics: Zoltar security and child outcome resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a10","heading":"Participants can access timely outcome evidence","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users can obtain enough reliable real-world evidence before the relevant protocol deadline to judge which universe is truthfull. Failure boundary: Missing, delayed, or conflicting evidence can split honest capital or let a false local outcome become final. Mechanics: question encoding , invalid outcomes , and child resolution .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a11","heading":"Migration is operationally practical","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Users, exchanges, interfaces, custodians, and other integrations are willing and able to migrate assets and state into the child universe or universes they support during the applicable windows. Failure boundary: Supported branches can remain illiquid or undercollateralized because economic claims and backing do not move there. Mechanics: REP splitting , pool migration , and operator migration guidance .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a12","heading":"Truth auctions can convert supported child REP efficiently","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"When repair is needed, enough demand can buy some supported-child REP for ETH at an acceptably small discount and operating cost. This does not assume that the full repair target is raised. Failure boundary: Weak or strategically withheld demand leaves the child operational with impaired collateral. Mechanics: auction lifecycle , clearing , and repair accounting .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a13","heading":"REP is sufficiently liquid","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can acquire or sell the required REP quantities without price impact or delay large enough to invalidate reporting, disputing, liquidation, auction, or migration economics. Failure boundary: Capital may exist in principle but cannot be assembled at a usable price before the deadline. Mechanics: economic roles , escalation , and oracle positions .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a14","heading":"REP economic value exceeds the open interest it secures","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"For a stated pool, lineage, or system scope at one timestamp, open interest and REP discounted cash flow are valued in the same numeraire, and REP discounted cash flow remains strictly greater. For a pool-level evaluation, open interest means the pool’s protocol-accounted ETH collateral unless the analysis explicitly names a broader exposure measure. The REP supply and price basis must also be stated; market capitalization is the observable proxy in A08 . This external relationship is separate from each pool’s onchain multiplier-adjusted REP backing check. Open Interest < Discounted Cash Flow REP Record scope, timestamp, common numeraire, price source, REP supply basis, and open-interest definition when evaluating this strict inequality. Failure boundary: REP holders may rationally accept or cause losses larger than the durable REP value placed at risk. Mechanics: security pools , liquidation backing , and oracle exposure analysis .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a15","heading":"Question selection remains aligned with user intent","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces and users verify the immutable question ID and metadata when selecting a market or pool, and the question has evidence and wording from which participants can reach an outcome or intentionally choose Invalid . Failure boundary: Unverified selection can route users and capital to an unintended market or pool, while ambiguous wording or unavailable evidence can split local outcome coordination. Mechanics: UNI-02 , question identity , and pool selection .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-oracle","heading":"Oracle correction, inclusion, and operational data","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions cover truthful REP/ETH price discovery, deadline access, capital, independent correction, and the offchain information required to act.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a16","heading":"Deadline transactions can obtain Ethereum inclusion","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Honest users can submit competitively priced Ethereum transactions and obtain canonical inclusion within every relevant dispute, escalation, migration, auction, staging, and settlement window. Failure boundary: Censorship or congestion lasting through a deadline can finalize a wrong report or prevent a supported action. Mechanics: deadline invariants , auction windows , and oracle censorship model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a17","heading":"OpenOracle has an available, adequately capitalized corrector","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one OpenOracle participant continuously monitors pending REP/ETH reports, can identify candidate errors using the reference market in A19 , can fund the scenario-dependent WETH and REP replacement position plus transaction costs, and can obtain inclusion before settlement. The participant's net incentive to perform the correction is the separate boundary in A18 . Failure boundary: An incorrect report can settle and authorize price-sensitive pool actions when no capable participant detects, funds, and includes a correction. Mechanics: REP/ETH oracle , correction incentives , and attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a18","heading":"At least one available corrector has an independent net incentive","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"At least one participant satisfying A17 is not under common control with the sponsor-funded coordinator reporting path, does not share the manipulation payoff, and is not bribed or compensated. The participant's net payoff from correcting is strictly greater than its net payoff from leaving the harmful report uncorrected. Two external submissions are not required for every request. Failure boundary: A sponsor-controlled, bribed, or economically indifferent corrector can make the contestable report path behave like a single trusted reporter. Mechanics: settlement validation scope , economic tradeoffs , and oracle invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a19","heading":"A robust reference market makes harmful REP/ETH errors correctable","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Independent participants can observe a manipulation-resistant reference REP/ETH price. Every price error large enough to unlock a security-relevant pool action also leaves a correction opportunity that remains profitable after actual gas, prevailing priority fees, OpenOracle fees, price impact, and required capital lockup. Failure boundary: A harmful deviation below the profitable correction threshold, or a manipulated/illiquid reference market, can settle without an economically rational challenge. Mechanics: report sizing , external-payoff model , and settlement limits .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a20","heading":"Canonical chain data and proof construction remain available","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants can access reorg-aware Ethereum history, recognized contract addresses, OpenOracle report state, and the event data needed to construct carry and nullifier proofs before use. Failure boundary: A valid dispute, settlement, migration, or inherited claim can become practically unavailable even while its contract entrypoint remains live. Mechanics: event replay , carry proofs , and operator orientation .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumptions-technical","heading":"Assets, clients, deployment, Ethereum, and cryptography","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These assumptions define the technical foundation below the protocol’s onchain guards.","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a21","heading":"Genesis REP and configured WETH obey the required accounting model","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Genesis REP and configured WETH move exactly the requested base units, use 18 decimals, return supported ERC-20 values, and do not rebase, charge transfer fees, invoke unexpected callbacks, blacklist protocol contracts, pause required transfers, or permit unauthorized minting. WETH remains redeemable 1:1 for ETH, the genesis theoretical supply is accurate, and the configured genesis REP burn sink remains inaccessible. On mainnet these token behaviors are external assumptions. On Sepolia, the reviewed GenesisReputationToken and WETH9 enforce issuance and transfer behavior, provided A23 verifies their deployed bytecode and wiring and A26 preserves Ethereum execution. The configured burn sink's inaccessibility remains an external assumption on Sepolia as well as mainnet. Canonical child REP is likewise implemented by the reviewed ReputationToken contract rather than being an additional external-token assumption. Failure boundary: Origin-pool, genesis-fork, or oracle accounting can overstate balances, backing, burned supply, or WETH value. Mechanics: EXT-01 , genesis REP handling , and oracle token pair .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a22","heading":"Participant-controlled recipients can accept required asset deliveries","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Vaults, bidders, oracle sponsors, traders, share holders, and other caller-controlled recipients use addresses that accept native ETH when claiming fees, collateral, proceeds, or refunds. Contract recipients also return the required ERC-1155 receiver selector when complete-set creation, share migration, or an outcome-share transfer delivers tokens to them. A rejected ETH push may revert or become bidder-specific deferred credit; a rejected ERC-1155 callback reverts the mint or transfer and its surrounding protocol action. No protocol path can force a rejecting address to accept a later delivery or redirect every payout or mint. Failure boundary: A contract account that rejects ETH or the required ERC-1155 callback can strand its own payout or make its complete-set, migration, transfer, or claim action unavailable even while protocol-wide accounting remains safe. Mechanics: ETH callback safety , ERC-1155 callback safety , share-token receipt , auction refunds , oracle request refunds , and pool payout guardrails .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a23","heading":"Users select verified canonical deployments","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The reviewed release is built reproducibly with its pinned compiler and toolchain configuration. Release artifacts identify the manifest addresses and reviewed bytecode hashes. Users and integrations compare locally rebuilt and deployed runtime bytecode with those reviewed hashes, then verify constructor-installed dependencies, factory and forker provenance, pool lineage, question ID, and manifest. Constructor admission is not a safety proof. Failure boundary: Toolchain drift, unavailable or mismatched reviewed hashes, permissionless lookalike instances, wrong dependencies, or unintended lineage and question wiring can bypass the reviewed protocol even when the selected bytecode functions as configured. Mechanics: release posture , deployment status , compiler configuration , OpenOracle build provenance , deployment bytecode , and authority invariants .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a24","heading":"Clients, RPCs, and wallets preserve user intent and canonical state","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Interfaces, indexers, RPC providers, and wallet software return canonical, reorg-aware state; construct and display the intended chain, target, calldata, parameters, and ETH value; and submit only transactions the user authorizes. Users verify transaction previews rather than treating an application label as an onchain guarantee. Failure boundary: Compromised or stale offchain software can misroute signatures or capital, hide deadlines or events, or cause users to authorize valid but unintended calls without violating contract guards. Mechanics: deployment discovery , contract interactions , and operator verification .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a25","heading":"Immutable parameters preserve economic and executable security margins","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"The selected parameter relationships keep the fork threshold and haircut economically meaningful, the initial escalation deposit affordable, the pool multiplier and effective pool-held vault REP backing multiplier large enough for their distinct coverage roles, the effective pool-held vault REP backing multiplier at least the 10,500-BPS liquidation-award reserve, the oracle target error profitable to correct after fees and gas, dispute and settlement windows usable, and callback and transition gas executable. The Statoblast pool statoblastSecurityMultiplierBps is distinct from openOracleSecurityMultiplierBps . Failure boundary: Weak or internally inconsistent immutable parameters can defeat the economic or liveness argument even when the canonical contracts function exactly as configured. Mechanics: Statoblast parameters , fork economics , liquidation economics , oracle parameters , and oracle attack model .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a26","heading":"Ethereum preserves the execution environment the protocol relies on","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Ethereum continues to provide correct EVM execution and atomic rollback, sufficient canonical finality, usable timestamp and block.basefee semantics, and block gas limits and gas schedules under which every mandatory immutable transition remains executable. Failure boundary: Deep reorganizations, invalid state execution, adversarial time behavior, or gas repricing can reverse accepted state or strand required transitions. Mechanics: reorg handling , atomicity and deadlines , and bytecode and gas bounds .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a27","heading":"Ethereum cryptographic primitives remain secure","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Keccak-256 remains collision- and preimage-resistant at the truncated widths used by the protocol, and CREATE2 identity remains sound. secp256k1 ECDSA signatures remain unforgeable with correct EOA signer recovery for Ethereum transactions and EIP-712 authorizations. OpenOracle's fixed Permit2 singleton additionally validates either an EOA signature or an ERC-1271 contract-account signature correctly. OpenOracle fixes the Permit2 address, requires the exact permitted amount, sends the requested tokens to itself, and constructs the witness from the beneficiary, relayer, token owner, and intent. Permit2 defines the EIP-712 domain, deadline, permitted-amount ceiling, unordered-nonce replay protection, and EOA/ERC-1271 signature validation. Verification under A23 must cover the pinned Permit2 implementation at that fixed address. Failure boundary: Forged identities, colliding questions or universes, false carry proofs, forged EOA transactions, incorrect EOA recovery or ERC-1271 acceptance, or broken domain and nonce enforcement can violate otherwise-correct guards or authorize an unintended token transfer. Mechanics: universe identity , proof hashing , Permit2 deposit authorization , Permit2 signature interface , pinned Permit2 implementation provenance , and deterministic deployments .","title":"Security model","topic":"Safety","weight":0},{"fragment":"assumption-a28","heading":"Participants retain control of account authority","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"Participants protect the private keys, signing devices, session permissions, and delegated ERC-20 or ERC-1155 approvals that authorize their accounts. Account recovery and custody arrangements do not give an unintended party effective control during a security-sensitive window. Failure boundary: A stolen key, compromised signer, or overbroad approval can authorize valid but unwanted transfers, deposits, bids, migrations, or configuration changes without violating a contract guard. Mechanics: transaction authority , vault and pool operations , and account-authority monitoring .","title":"Security model","topic":"Safety","weight":0},{"fragment":"excluded-guarantees","heading":"Guarantees deliberately not made","keywords":[],"path":"reference/security-model.html","sectionTitle":"Reference","summary":"Normative participant, market, deployment, client, and cryptographic assumptions.","text":"These outcomes remain possible without contradicting the security model. They should be included in user, operator, and integration risk disclosures. EG01. Zoltar creates valid branches but does not enforce that the fork question is relevant to a user, establish the objectively truthful branch, obtain user consent to a fork, or force users to coordinate on one branch. EG02. A truth auction may raise less than its repair target; the child then activates with migrated collateral plus accepted auction ETH. EG03. OpenOracle report liquidity does not cap the notional value of operations using an accepted price, and settlement does not prove external price correctness. EG04. A participant with unbounded capital can keep funding valid disputes and delay the sponsor lane indefinitely; no bounded coordinator-availability claim is made. EG05. The immutable protocol has no administrator who can pause, upgrade, roll back, or rescue an unsafe deployment after launch. EG06. Escalation principal and expected rewards are not senior to open-interest repair. A child truth auction may proportionally reduce them, and a losing escalation claim pays its committed depositor nothing.","title":"Security model","topic":"Safety","weight":0},{"fragment":"","heading":"","keywords":["operators","guardrails","launch","recovery"],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Operators, indexers, reviewers, and UI maintainers consult this reference for contract-accurate guardrails, launch procedures, and edge-case behavior in one subsystem at a time. The explanations carry the full protocol story. Each section answers a different operational question: how the immutable launch posture constrains releases, what a security pool or escalation game will and will not accept, how fork migration behaves, how auction settlement works, and how the REP/ETH coordinator stages or recovers operations. Implementation guardrails map to their contract sources so operators can check each rule against the exact Solidity implementation. The Protocol Invariants catalog connects those local guardrails to cross-contract conservation, lifecycle, liveness, and economic security properties, including launch-critical properties that the current contracts do not preserve. The Protocol Security Model is normative for the protocol's external assumptions.","title":"Operator guardrails","topic":"Operations","weight":1},{"fragment":"security-review-orientation","heading":"Security Review Orientation","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before classifying an economic or liveness concern, use the security model's grouped assumptions to identify external dependencies, then follow its canonical mechanics links. The invariant catalog owns the current requirement, status, and evidence for EXT-05 recursive-fork gas behavior . Operator work relies particularly on A23 verified deployments , A06 lifecycle executors , A20 chain-data and proof availability , A26 Ethereum execution and finality , A27 cryptographic security , A15 intended question selection , A25 safe immutable parameters , A22 asset-recipient compatibility , A24 client, RPC, and wallet integrity , and A28 account authority .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"assumption-monitoring","heading":"Assumption Monitoring","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The security model is the canonical definition of each boundary. This table assigns operational evidence and response for assumptions whose truth can change with market or service conditions. Evaluate each signal at the same lineage, pool, timestamp, numeraire, and decision horizon as the protected action. Assumptions Operational evidence Review cadence Response when the boundary is weak or unknown A01, A02, A03, A06 Expected reward after gas, fees, delay, and capital lockup; number and independence of active liquidators, escalation participants, bidders, correctors, and transition executors; settle-ready OpenOracle reports, callback-recovery candidates, and overflow operations Continuously for deadline-bearing flows; before launch and after parameter or gas-regime changes Fund or arrange independent executors, reduce exposed value, or disclose that the affected transition may stall A05, A07, A09, A11 Parent and supported-child lineage value in one numeraire; observed user, custodian, exchange, interface, and proof-tool readiness to migrate assets and state within the applicable windows; documented continued-use value and secured value Before a fork-security claim and throughout migration and coordination windows Do not claim fork deterrence, practical migration, or value concentration; narrow supported lineage and exposure A08, A13, A14 REP supply basis, price source, depth and price impact; REP discounted-cash-flow methodology; protocol-accounted ETH collateral or explicitly broader open interest At launch, continuously while open interest exists, and after material price, supply, liquidity, or exposure changes Cap or stop new exposure in clients and disclosures; do not describe REP backing as economically sufficient A04, A10 Outcome evidence sources and availability; immediately mobilizable REP and transaction budget before each local-escalation deadline From question creation through local resolution and any fork window Mark the market or action impaired, surface the deadline, and avoid asserting that honest local correction remains fundable A12 Auction demand, indicative REP/ETH depth, expected discount, and bidder operating cost Before and during every repair auction Disclose likely under-repair and plan operation of the child at only migrated collateral plus accepted auction ETH A16, A26 Inclusion delay, base fee, priority fee, reorg depth, block gas limit, and gas schedule against each immutable window and mandatory transition Continuously while a deadline or mandatory transition is live Raise transaction fees where rational, use independent submission paths, and disclose missed-deadline or stranded-transition risk A17, A18, A19 Independent corrector availability, ownership and payoff independence, scenario-dependent WETH and REP replacement capital, reference-market depth, manipulation cost, correction profit after fees/gas/impact, transaction budget, and settlement inclusion Continuously while a report is pending or a cached price can authorize operations Block or discourage new price-sensitive operations and disclose that an incorrect report may settle A21, A22 Mainnet external-token code and behavior; Sepolia reviewed Genesis REP/WETH runtime bytecode, constructor allocations, and wiring; WETH redeemability; burn-sink status; recipient ETH/ERC-1155 compatibility At deployment verification and before first use of a new recipient or integration Reject the deployment or incompatible recipient; do not rely on redirected recovery A20, A23, A24, A27, A28 Pinned compiler and toolchain identity; reproducible-build output, reviewed bytecode hashes, release manifest, deployed runtime code and wiring; independent reorg-aware RPC/event replay; hash and CREATE2 verification; signer, typed-data domain, nonce, witness, session, and approval inventory At release, on every environment or account change, and continuously for indexers Stop signing or operating, reject an unverifiable deployment, revoke compromised authority where possible, and re-establish canonical state from independent sources A15 Immutable question ID and metadata, evidence plan, and pool relevance Before pool selection Reject or warn on the selection; do not represent an admitted question as relevant or resolvable A25 Deployed immutable values and every documented economic, timing, integer-width, and gas relationship Before deployment and after any change in external gas or market conditions used by the model Treat the immutable deployment as unsupported; there is no administrative parameter repair","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"immutable-protocol-release-posture","heading":"Immutable Protocol Release Posture","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Zoltar and Statoblast are intended to launch as immutable, permissionless contracts. Launch documentation should not assume an operator can pause, upgrade, roll back, or disable protocol behavior after deployment. Release work should instead publish verifiable provenance: final commit and tag, deterministic addresses, CI and local QA results, production UI artifact hash, and any known limitations. UI deployments may describe data freshness and route users to verified artifacts, but they must not be documented as emergency controls over the protocol. This is the protocol's EG05 no-pause, upgrade, rollback, or rescue boundary .","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"launch-release-checklist","heading":"Launch Release Checklist","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Before tagging a launch release, run bun run ui:build:prod from a fresh dependency install and run bun run check:mainnet-deployment . This command fails when either generated deployment manifest is stale. Refresh and review both manifests with bun ./scripts/check-mainnet-deployment.mts --write before rerunning the check. That command writes and checks both mainnet-deployment-addresses.json and sepolia-deployment-addresses.json . Use the pinned compiler configuration in solidity/ts/compile.ts , the exact tool versions in solidity/package.json , and the imported OpenOracle profile in UPSTREAM.md . Publish the reproducible-build runtime hashes with the release, keyed to the manifest addresses, and compare both a clean local rebuild and deployed runtime code with those reviewed hashes before marking the release canonical. Push the final v* tag only after the CI Gate succeeds on the same commit. The Build and Push to IPFS workflow must succeed for that tag, and the resulting GitHub release must record the IPFS hash emitted from the published artifact. Production UI release notes should state that ?simulate=1 is a browser-local sandbox. Mainnet and Sepolia quote-dependent actions use live RPC data plus available network-local Uniswap liquidity, while simulation uses its configured mock where supported. The OpenOracle integration guide owns the quote-source order and network-address details. RPC and client integrity remain covered by A24 , while quote availability and representativeness are official-client limitations rather than protocol security assumptions. Stale, unavailable, or unsupported quotes are production blockers for the affected client action, not inputs that should be replaced with simulation prices.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"genesis-rep-deployment","heading":"Genesis REP Deployment","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Sepolia genesis REP allocations are canonical source data in sepoliaRepAllocations.ts . The configured 11 million REP mint cap is divided equally among the listed holders; adding a holder automatically reduces every holder's allocation. Integer division may leave the total minted supply below the cap by less than one attoREP per holder. Review both the holder list and the computed bigint total before deployment. The GenesisReputationToken constructor requires at least one allocation, matching holder and balance array lengths, unique nonzero holders, nonzero balances, and a total supply no greater than 11 million REP. It emits the standard ERC-20 mint Transfer event once for each allocation. The sum of the constructor balances is immutable and is returned by getTotalTheoreticalSupplyAttoRep() . There is no administrator, post-deployment mint, allocation correction, or recovery entrypoint. An incorrect allocation must be fixed in the source list before deployment and the deterministic manifests must then be regenerated. Because constructor arguments are part of CREATE2 init code, any allocation change produces a new genesis REP address and new addresses for every deployment step whose constructor wiring depends on it. Never continue a deployment from a manifest generated for a different allocation list. A future release that intentionally changes starting holders must follow the same source review and full-manifest regeneration procedure.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"security-pool-guardrails","heading":"Security Pool Guardrails","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool creation, minting, withdrawal, and direct-ETH rules are collected here in contract-first form. 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, 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 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 derived from total REP capacity ownership, the current REP/ETH price, and the pool multiplier. Total ownership includes an unclaimed truth-auction allocation, so it can provide minting headroom before the vault owner claims it. Claiming later moves that already-counted ownership into fee eligibility without adding it to total ownership again. The pool updates accounting before the share-token mint call. SecurityPool.createCompleteSet , SecurityPool.depositRepToVault Fee accrual clamp Fee accrual is clamped to the question end time while the universe is unforked; after a universe fork, the accumulator is clamped to the fork time. SecurityPool.sol Fee accrual and rounding Only vault-assigned capacity ownership is fee-eligible. Settlement-collateral decay first enters an unallocated reserve; vault checkpoints 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, while capacity-ownership-scoped index carry is cleared when capacity ownership attribution changes so old-denominator dust is not reassigned. After a fork permanently ends accrual, the pool tracks capacity ownership still awaiting the final index; once every eligible vault checkpoints, any whole reserve attoETH that no vault can individually claim returns 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 auction ownership therefore already affects utilization. A later claim changes fee eligibility but not 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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"share-migration","heading":"Share Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"escalation-resolution-and-deposits","heading":"Escalation Resolution and Deposits","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Accepted deposits, edge-case resolution results, and carry-proof or residual-REP consumption all live in this section. 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 transition has occurred. 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. The game closes further deposits. With a fixed child outcome it follows the continuation clock and cannot trigger its own fork; without one, canTriggerOwnFork() returns true directly. EscalationGameCarry.initializeForkCarrySnapshotWithResolutionBalances , EscalationGameCalculations.getEscalationGameEndDate , EscalationGameCalculations.canTriggerOwnFork 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. One mantissa/exponent ratio applies every intervening auction haircut exactly once to both proof amount and cumulative reward position without walking ancestors, and excludes losses before a continuation-local deposit was created. Consumption asks the immediate source game for retained cumulative-prefix differences, so per-leaf source-basis allocations telescope exactly to the aggregate checkpoint in every proof order. 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 freezes the carry root/count and retention metadata and performs no claim or owner import. The 64 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 , EscalationGameCarry._consumeCarriedDeposit Optional unresolved parent escalation-deposit accounting cleanup The public SecurityPoolForker.migrateVaultWithUnresolvedEscalation wrapper first runs ordinary migration for that same vault: REP backing units and capacity ownership move 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, unresolved principal is cleared, and no escrow remains, residual REP in the escalation game can be swept back to the security pool. EscalationGameSettlement.sweepResidualRepToSecurityPool","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"fork-migration","heading":"Fork Migration","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Pool-level migration mechanics after a universe fork live here: 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 Fork Migration Proportion and Fork Collateral Ceiling own checkpoint order, cumulative allocation, and repair derivation. 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 the canonical fork-migration derivation and 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, retention checkpoint, 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 Proofs . 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","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"truth-auction-operations","heading":"Truth Auction Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"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. Only vault-assigned capacity ownership accrues; each claimed auction capacity ownership joins incrementally at the current fee index. A delayed claim adds to the pool’s live eligible total, so capacity ownership changes or liquidations since activation are preserved rather than replaced by fork-time counters. SecurityPool.sol , SecurityPoolForker.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"repeth-oracle-operations","heading":"REP/ETH Oracle Operations","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The coordinator quick reference below covers staging, callback recovery, stale-operation handling, and liquidation boundaries. Report sizing, request cost, and current OpenOracle parameters are canonical in OpenOracle Integration . Area Implementation behavior Source Atomic initial report The sponsor funds the ETH bounty and WETH/REP position; the coordinator 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 Delegated receiver approvals LiquidationApprovalRegistry supports direct receiver approvals and EIP-712 permits from EOAs or ERC-1271 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 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 , SecurityPoolLiquidationDelegate.sol","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"support-module-inventory","heading":"Support Module Inventory","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"The explanation focuses on protocol flow. The table below names the remaining support contracts that matter for integrators, code reviewers, and interface inventory work.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"caller-and-trust-boundaries","heading":"Caller and trust boundaries","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"Deployment helpers are not interchangeable with canonical protocol discovery. Several are intentionally permissionless and namespace CREATE2 salts by msg.sender ; an application must discover canonical instances through SecurityPoolFactory , recognized pools, and recognized forker events rather than treating every factory-created address as canonical. Module Direct caller boundary Canonical-use boundary EscalationGameFactory deployEscalationGame and deployEscalationGameFromFork have no permission check, but a successful caller must implement the ISecurityPool reads used during construction; an EOA or incompatible contract reverts. The caller is bound into the new game as its securityPool ; the factory itself becomes the game's immutable owner and immediately calls start or startFromFork . Ordinary deployment requires a threshold above one attoREP and lowers a start bond at or above that threshold to threshold - 1 . The factory has no owner role and no later resumeFromFork relay. Accept a game only after EscalationGameSet from a recognized pool. In the supported continuation path, the owning pool calls resumeFromFork , which also verifies aggregate funding. ShareTokenFactory Anyone can call deployShareToken to deploy or retrieve a token under keccak256(abi.encode(msg.sender, salt, questionId)) ; that caller is the token's initial authorized address. Origin lineage tokens are the instances created by SecurityPoolFactory and named in DeploySecurityPool ; children reuse the parent's token. UniformPriceDualCapBatchAuctionFactory Anyone can call deployUniformPriceDualCapBatchAuction . Its salt is namespaced by caller and supplied salt, and its operational owner is the caller-supplied owner address. During external-universe initiation, the source must be authorized by its declared share token; own-game initiation does not perform that check, and neither relationship proves configured-factory registration. Child linking requires a deployed auction that this forker has never trusted before and preserves the source's factory and forker. Canonical pools remain the instances registered by the configured SecurityPoolFactory . PriceOracleManagerAndOperatorQueuerFactory Anyone can call deployPriceOracleManagerAndOperatorQueuer under a caller-namespaced salt with caller-supplied OpenOracle, REP token, and positive initialReportPriorityFeeAttoEthPerGas ; coordinator construction rejects zero and values that would consume the reserved OpenOracle uint128 report and escalation-halt capacity. The caller therefore chooses this immutable gas-price assumption within that bound. A pool coordinator is the instance named by canonical DeploySecurityPool ; SecurityPoolFactory atomically binds it to that pool before returning, and canonical child deployment inherits the value from the parent coordinator. SecurityPoolDeployer and SecurityPoolDeploymentWorker Only the configured SecurityPoolFactory may call the deployer; only that deployer may call its worker. They are code-size deployment plumbing. Pools become canonical only when the factory emits SecurityPoolRegistered and DeploySecurityPool . SecurityPoolMigrationProxy Only its immutable owner , the SecurityPoolForker that created it, may call lockRep , forkUniverse , splitToChild , or sweepChildRep . Its deterministic per-parent address is exposed by getMigrationProxyAddress and recorded in fork snapshot events; users do not call it. EscalationGameDepositDelegate , EscalationGameClaimDelegate , EscalationGameForker , SecurityPoolForkerVaultMigrationDelegate , and SecurityPoolLiquidationDelegate Their public methods are delegatecall implementations. The deposit delegate implements recordDeposit , recordForkedEscrowForOutcome , applyTruthAuctionHaircut , funding-gated resumeFromFork , consumeEscrowedRepForOwner , consumeUnresolvedRepForClaimOwners , creditClaimOwners , and creditExternalClaimOwners . The shared claim delegate handles retention checkpoint reads and initialization; it has no ownership or import surface. The forker delegates implement escalation and vault migration. The pool helper implements funded REP-backing-unit, capacity-ownership, receiver-debt, and full-request bad-debt liquidation accounting while isolating fees and claims, plus permissionless continuation resume. Direct calls operate on isolated or uninitialized helper storage and normally revert; they are not protocol actions. Accept resulting state and logs only when the recognized game, pool, or forker executes the matching wrapper through delegatecall . State changes and events then occur in that recognized contract's context. Canonical liquidation enters through SecurityPool.performLiquidation ; continuation progress enters through SecurityPool.resumeForkedEscalationGame . SecurityPoolEventEmitter.sol emitPoolAccountingCheckpoint , emitVaultAccountingCheckpoint , and emitForkSnapshotEvents are externally callable and payable , but direct calls execute against the helper's own storage and emit from the helper address. Payability permits delegatecalls from value-bearing protocol flows; callers must not send ETH directly because it is not protocol collateral and the helper has no recovery surface. Pools and the forker use constructor- or factory-installed modules through delegatecall , so logs are emitted from the recognized pool or forker address. Indexers must reject matching signatures from the helper or any unrecognized emitter. External-universe fork initiation requires declared-share-token authorization; own-game initiation does not. Child linking preserves the source factory and forker and rejects undeployed or previously trusted auctions. Configured-factory registration remains the canonicality boundary. Storage-layout tests protect the delegate's fixed slots. EscalationGameProofVerifier , MerkleMountainRange , and SecurityPoolUtils Stateless or library math and proof routines have no lifecycle authority. Public verifier calls can be used for previews, but do not mutate a game or pool. A result becomes protocol state only through a recognized game, pool, or forker transaction and its events. DeploymentStatusOracle Anyone can call getDeploymentMask ; the constructor alone fixes the ordered address list and emits DeploymentAddressesSet . It reports code presence only, not canonical wiring or readiness. Decode it using its constructor event or the matching deployment manifest order.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"securitypoolutils-read-surface","heading":"SecurityPoolUtils read surface","keywords":[],"path":"reference/operator-guardrails.html","sectionTitle":"Reference","summary":"Contract-accurate subsystem boundaries and operational edge cases.","text":"These external pure previews distinguish REP-denominated capacity ownership from ETH-denominated open interest. REP-per-ETH prices use PRICE_PRECISION , and health factors and security multipliers use basis points. The previews have no lifecycle, caller, freshness, or canonical-pool authority; only a recognized pool or coordinator transaction can apply their result. Function Exact boundary behavior Conceptual owner calculateCumulativeAuctionBadDebt(auctionedBadDebtAttoEth, nextClaimedCapacityOwnershipAttoRep, auctionedCapacityOwnershipAttoRep, previouslyClaimedBadDebtAttoEth) Assigns auctioned bad debt from cumulative claimed capacity ownership, so claim order cannot change the total. Intermediate claims round down; the final claim receives the exact residual. Truth-auction settlement calculateFeeAccrual(settlementCollateralAttoEth, retentionRate, timeDelta, indexRemainder, feeEligibleCapacityOwnershipAttoRep, feesOwedRemainder) Applies fixed-point retention over elapsed time, carries the global index remainder, and returns whole credited fees plus the next fee remainder. Canonical callers avoid zero eligible capacity ownership before this division. Fee accrual calculateVaultFee(capacityOwnershipAttoRep, feeIndexDelta, remainder) Floors the vault's whole fee credit and returns the remaining fixed-point numerator for its next checkpoint. Fee accrual calculateMintingCapacityAttoEth(capacityOwnershipAttoRep, repEthPrice, securityMultiplierBps) Converts aggregate REP-denominated ownership into current ETH capacity using the live REP-per-ETH price and pool multiplier. Zero ownership or price returns zero. Dynamic capacity calculateVaultOpenInterestAttoEth(activeOpenInterestAttoEth, vaultCapacityOwnershipAttoRep, totalCapacityOwnershipAttoRep) Attributes live pool open interest pro rata to capacity ownership, rounding a positive vault share upward. Zero vault or total ownership returns zero. Dynamic capacity calculateBundledLiquidationTransfer(targetBackingUnits, targetCapacityOwnershipAttoRep, targetOpenInterestAttoEth, requestedDebtAttoEth, repEthPrice, currentPoolHeldAttoRepBalance, currentTotalRepBackingUnits, minimumRemainingAttoRep) Returns zero when the request, target open interest, capacity ownership, or price is zero. Caps a nominal debt quote by target open interest and by the complete 5%-bonus award fundable from target pool-held REP, then rounds proportional capacity ownership downward. Execution derives moved debt separately from the receiver's exact live open-interest increase. Partial requests preserve the configured REP minimum. On a full-target request, bad debt is target open interest minus exact moved debt and can include both an award-unfunded slice and integer-allocation residue. Vault liquidation isVaultHealthy(poolHeldVaultRepBackingAttoRep, disputeStakedAttoRep, openInterestAttoEth, repEthPrice, poolSecurityMultiplierBps) Checks the associated-REP and free pool-held REP requirements against live open interest. Both requirements round upward; zero open interest is healthy. isVaultHealthyAtFactor applies an approval-selected factor of at least 10,000 to both branches. Capacity and health calculateRetentionRate(settlementCollateralAttoEth, mintingCapacityAttoEth) Zero live minting capacity returns the maximum retention rate. Otherwise retention decreases linearly through the 80% utilization dip and remains at the minimum rate above it. Retention rate Area Named contracts and modules Role Deployment tracking DeploymentStatusOracle.sol Reports one code-presence bit per configured deployment-step address. See Deployment Status Oracle . Carry proof hashing MerkleMountainRange.sol , EscalationGameProofVerifier.sol , EscalationGameTypes.sol Defines the carried-deposit leaf shape, the 64-peak limit, proof-length rules, and nullifier-root replay protection. See Merkle Mountain Range carry proofs . Escalation game composition EscalationGame.sol , EscalationGameCalculations.sol , EscalationGameCarry.sol , EscalationGameClaimDelegate.sol , EscalationGameDepositDelegate.sol , EscalationGameEscrow.sol , EscalationGameSettlement.sol , EscalationGameState.sol , EscalationGameStorage.sol Splits the game across immutable ownership and storage, claim and deposit delegation, calculations, continuation proofs, escrow, and settlement without exposing those internal modules as separate canonical games. ERC-20 support ERC20.sol , IERC20.sol , IERC20Metadata.sol , Context.sol , SafeERC20Ops.sol , GenesisReputationToken.sol , ReputationToken.sol Base REP token implementation, interfaces, metadata, execution context, safe transfer wrappers, the constructor-allocated genesis token with fixed theoretical supply, and the inherited transfer , approve , and transferFrom entrypoints. ERC-1155 and token ids ERC1155.sol , ShareToken.sol , TokenId.sol , IERC1155.sol , IERC1155Receiver.sol , IERC165.sol , IShareToken.sol Outcome-share token plumbing, interface support, token-id encoding, inherited setApprovalForAll , and both single and batch safeTransferFrom forms. Factories and deployers EscalationGameFactory.sol , SecurityPoolFactory.sol , SecurityPoolDeployer.sol , ShareTokenFactory.sol , UniformPriceDualCapBatchAuctionFactory.sol , PriceOracleManagerAndOperatorQueuerFactory.sol Deterministic deployment entrypoints for pool, share-token, oracle-coordinator, escalation-game, and auction instances. Migration, liquidation, and storage modules SecurityPoolStorage.sol , SecurityPoolLiquidationDelegate.sol , SecurityPoolMigrationProxy.sol , SecurityPoolForker.sol , SecurityPoolForkerAuctionSettlementBase.sol , SecurityPoolForkerBase.sol , SecurityPoolForkerStorage.sol , SecurityPoolForkerTypes.sol , SecurityPoolForkerVaultMigrationBase.sol , SecurityPoolForkerVaultMigrationDelegate.sol , EscalationGameForker.sol , SecurityPoolEventEmitter.sol , LiquidationApprovalRegistry.sol , SignatureValidation.sol Defines shared pool storage, delegated liquidation accounting and bounded approval reservations, ECDSA/ERC-1271 signature validation, funded continuation delegatecalls, fork-time state, truth-auction settlement, vault and unresolved-escalation migration, event encoding, and stable proxy identity used while routing parent state into child pools. Protocol interfaces IEscalationGame.sol , IOperationBountyResultReceiver.sol , ISecurityPool.sol , ISecurityPoolForker.sol , ISecurityPoolForkerChildEscalationGameInitializer.sol , IUniformPriceDualCapBatchAuction.sol , IWeth9.sol , IAugur.sol Typed boundaries used by games, pools, migration delegates, auctions, WETH integration, and external Augur compatibility. Protocol utilities Constants.sol , ScalarOutcomes.sol , BinaryOutcomes.sol , SecurityPoolUtils.sol , Multicall3.sol , WETH9.sol Shared constants and math, outcome representations, Multicall3 aggregate , tryAggregate , tryBlockAndAggregate , blockAndAggregate , aggregate3 , aggregate3Value , and block-context reads, plus standard WETH9 deposit , withdraw , approval, and transfer support. Imported oracle integration UPSTREAM.md , OpenOracle.sol , Errors.sol , ISignatureTransfer.sol , interfaces/IERC20.sol , interfaces/IERC165.sol , token/ERC20/IERC20.sol , IERC1363.sol , SafeERC20.sol , Panic.sol , ReentrancyGuard.sol , StorageSlot.sol , utils/introspection/IERC165.sol , Math.sol , SafeCast.sol OpenOracle's imported source, exact SlimStorage revision, compiler profile, and dependency provenance are owned by UPSTREAM.md . The coordinator submits attoETH/attoREP reports with dispute history and full game storage enabled; OpenOracle's packed event stream remains separate from Zoltar replay.","title":"Operator guardrails","topic":"Operations","weight":0},{"fragment":"","heading":"","keywords":["deployment","bitmask","runtime bytecode","status"],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"DeploymentStatusOracle is a deployment-progress helper. It does not measure protocol readiness or initialization success. It only reports which configured deployment-step addresses currently contain runtime bytecode. The canonical mainnet and Sepolia manifests list this contract directly, so operators and frontends need a precise explanation of the returned bitmask and the built-in 256-step ceiling.","title":"Deployment status oracle","topic":"Deployment","weight":1},{"fragment":"mask","heading":"What getDeploymentMask() Returns","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor stores one ordered address[] deploymentAddresses . getDeploymentMask() loops over that array and checks deploymentAddresses[index].code.length . When code exists, the oracle sets bit index in the returned uint256 . This is a code-presence bitmap. A set bit means code exists at the configured address. A clear bit means the address currently has no code. The oracle does not verify constructor args, ownership wiring, or post-deployment setup. A row of deployment steps on the left maps by index into bit positions in a uint256 mask on the right. Each step sets its bit only when code exists at that configured address. Deployment Status Mask Each configured deployment step uses one bit position in the returned word. The oracle sets that bit only if the step's address currently contains code. deployedMask = ∑ i = 0 codePresent ( i ) ⋅ 2 i Each configured deployment step contributes one bit position to the returned word.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"ordering","heading":"How Bits Map To Deployment Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Bit positions are array positions. Bit 0 maps to the first constructor address, bit 1 to the second, and so on. Offchain decoding must use the same ordered list that seeded the oracle. The constructor emits DeploymentAddressesSet(address[] deploymentAddresses) with that exact ordered list. It is the onchain source for recovering a particular oracle instance's bit mapping; the manifest below describes the current planned deployment and must match the constructor event for that instance. The current UI and deployment helpers derive the constructor list from deploymentSteps with one exception: the oracle does not include its own address in its constructor array. The manifest still lists deploymentStatusOracle as a deployment step, but the UI tracks that step out-of-band and starts consuming mask bits from the remaining addresses. The tables below render the complete bit mappings directly from the canonical mainnet and Sepolia deployment manifests, excluding deploymentStatusOracle . Each manifest's ordered deploymentSteps array is the canonical mapping for that network. Bit Mainnet step id Label Status in entered mask Loading the canonical manifest… Bit Sepolia step id Label Loading the canonical manifest… If the manifest's constructor order changes, the rendered bit meanings change with it; this page does not maintain a second list. The manifest's derivedContracts list is separate. Those addresses are not part of the deployment-status mask unless they also appear in the constructor array. Decode a deployment mask Mask value Loading the canonical mapping… Decoder controls will be available when the mapping loads. Retry loading the canonical mapping","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"limit","heading":"Why The Cap Is 256 Steps","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"The constructor requires deploymentAddresses.length <= uint256(type(uint8).max) + 1 , which is 256 . The limit exists because the result is a single uint256 , and each tracked step consumes one bit. Indices 0...255 are supported. A deployment process with more than 256 tracked addresses must split status across multiple masks or use a different representation.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Interpretation and Verification Boundaries","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Evidence What it proves What it does not prove Bit set Runtime bytecode exists at the configured address. Correct bytecode, constructor values, wiring, permissions, or operational readiness. Bit clear No runtime bytecode was observed at that address by this call. Whether the address is intentionally empty or the manifest is wrong. Manifest match The decoder used the intended ordered address list for the network. That deployed code matches the release or that RPC responses are honest. Full deployment verification Code hashes, required wiring, permissions, and manifest provenance can be checked together. Economic safety or future operational liveness. This page documents the deployment-status bitmask only; it does not provide complete bytecode, wiring, permission, or manifest verification. The getDeploymentMask() return type is uint256 ; the constructor reverts when more than 256 addresses are supplied.","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"interpretation","heading":"Static decoder test vectors","keywords":[],"path":"reference/deployment-status.html","sectionTitle":"Reference","summary":"Deployment progress bitmask semantics and the 256-step limit.","text":"Mask Meaning 0x0 No tracked constructor address contains runtime code. 0x1 Only constructor address index 0 is present. 0x5 Constructor address indices 0 and 2 are present. 2^255 Only the highest supported bit, index 255, is present. The interactive tables require JavaScript. The ordered source lists are available in the Mainnet and Sepolia manifests; bit i always corresponds to the i th constructor address after excluding deploymentStatusOracle .","title":"Deployment status oracle","topic":"Deployment","weight":0},{"fragment":"","heading":"","keywords":["MMR","carry proof","hashing","nullifier"],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Statoblast uses a Merkle Mountain Range only for inherited escalation carry. Parent escalation games export unresolved deposits into compact snapshots, and child continuations later verify withdrawals against those snapshots without replaying the full parent history onchain. The hashing primitives live in MerkleMountainRange.sol . Snapshot storage, proof structs, peak bounds, and replay protection live in EscalationGameTypes.sol , EscalationGameCarry.sol , and EscalationGameProofVerifier.sol .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":1},{"fragment":"leaf-shape","heading":"Leaf Shape And Hash Order","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each leaf hashes one unresolved deposit with depositor , outcome , amount , parentDepositIndex , cumulativeAmount , and sourceNodeId . Leaf hashes use keccak256(abi.encode(...)) . Parent hashes use keccak256(abi.encodePacked(left, right)) . Field Why it is committed depositor Binds the proof and payout to the original depositor. This ownership is immutable: liquidation moves only pool-held vault REP backing and cannot acquire or split the committed escalation claim. outcome Separates invalid, yes, and no carry domains. amount Commits to the source principal. parentDepositIndex Provides the stable identity later consumed by nullifiers. cumulativeAmount Preserves payout-order prefix data. sourceNodeId Distinguishes otherwise identical leaves copied from different local nodes. Hash order matters. Internal nodes always hash left before right , so proofs are position-sensitive inside each peak.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Normative Encoding","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The leaf ABI types are (address,uint8,uint256,uint256,uint256,uint256) , in the order depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId . outcome is the Solidity enum ordinal. Leaf hashing uses standard ABI word padding through abi.encode ; parent hashing uses the 64 raw bytes from abi.encodePacked(left, right) . There is no additional domain separator. leafHash = keccak256(abi.encode(depositor, outcome, amountAttoRep, parentDepositIndex, cumulativeAmountAttoRep, sourceNodeId)) parentHash = keccak256(abi.encodePacked(left, right)) root = bagPeaks(occupiedPeaks, peakCount) Proof verification rejects an empty or out-of-range peak, leafIndex >= 2^peakHeight , a sibling array whose length is not peakHeight + peakCount - 1 , a nullifier path whose length is not 64 , a root mismatch, or an already-consumed nullifier.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"encoding","heading":"Two-leaf conformance vector","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Input or intermediate Value Leaf 0 ABI values 0x0000000000000000000000000000000000000001, 0, 1, 0, 1, 0 Leaf 0 hash 0xb01042d963a0c261d64893dfb7e1f221c93ef608653cb92f9ca2c8899ca4572b Leaf 1 ABI values 0x0000000000000000000000000000000000000002, 1, 2, 1, 2, 1 Leaf 1 hash 0xe0ca39e8852dc96783e8b8f0859cfbf155338f9144ba4b9fdd6af847299fc867 Peak 1 / root 0x1c9c7f3c2c8bebf92ce393deb5dccdd253f2b10084af72defb5dcd4486832b27","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"peaks","heading":"Peaks And The 64-Peak Bound","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"The carry snapshot stores one peak per set bit in the leaf count. The system fixes MERKLE_MOUNTAIN_RANGE_MAX_PEAKS = 64 , and carry initialization requires each snapshot leaf count to be less than 2^64 . That does not cap the snapshot at 64 leaves. It caps the number of peak positions. Any leaf count from 0 up to but not including 2^64 is valid because its binary decomposition fits inside 64 peak slots. A leaf count of thirteen is shown as binary one one zero one, corresponding to occupied peaks at heights zero, two, and three which are later bagged into one root. MMR Peaks The occupied peak heights are exactly the set bits in the snapshot leaf count. Those occupied peaks are later bagged into one root. To form one root, the verifier collects occupied peaks in ascending height order and then bags them from right to left with bagPeaks() . snapshotLeafCount < 2 64 The 64-peak constant means each inherited snapshot leaf count must fit below 2^64 . Local carry appends behave like binary addition with carries: a new leaf merges upward through occupied lower peaks until it finds the first empty peak slot. If that upward carry would reach height 64 , the append reverts with MMR too tall .","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"proofs","heading":"Proof Structure","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"A carried-deposit proof has two parts. First it proves membership in the inherited MMR snapshot for one outcome. Then it proves that the same parentDepositIndex has not already been consumed in this continuation's nullifier tree. Component Purpose leafIndex , merkleMountainRangePeakIndex , merkleMountainRangeSiblings Proves the carried deposit belongs to the inherited snapshot root for that outcome. nullifierSiblings Proves the nullifier leaf is still empty so the carried deposit cannot be replayed. The membership lane combines the carried deposit leaf with bottom-up siblings inside its selected peak, then with other occupied peak roots in ascending height order to reconstruct the snapshot root. The nullifier lane hashes the stable parent deposit index and combines it with a fixed-depth sibling path to prove the claim has not already been consumed. Carry Proof Anatomy The MMR sibling array first reconstructs one selected peak and then supplies every other occupied peak. The independent 64-level nullifier path prevents the same stable deposit identity from being consumed twice. The index semantics are stricter than the field names might suggest. merkleMountainRangePeakIndex is the occupied peak height, not the ordinal position of that peak among occupied peaks. leafIndex is the leaf's offset inside that selected peak, so the verifier requires leafIndex < 2^merkleMountainRangePeakIndex . For example, if the full snapshot has 13 leaves, its occupied peak heights are 0 , 2 , and 3 . A proof for a leaf inside the height- 2 peak uses merkleMountainRangePeakIndex = 2 and a peak-local leafIndex in 0...3 , not the leaf's global index across all 13 leaves. merkleMountainRangeSiblings is also ordered in two phases. The first merkleMountainRangePeakIndex entries are the bottom-up Merkle siblings inside the selected peak. The remaining entries are the other occupied peak roots in ascending peak-height order, skipping the selected peak height. That exact ordering is why the verifier checks merkleMountainRangeSiblings.length == peakHeight + peakCount - 1 . Separately, the verifier requires nullifierSiblings.length == NULLIFIER_DEPTH with NULLIFIER_DEPTH = 64 . Nullifier paths are keyed by uint256(keccak256(abi.encode(parentDepositIndex))) , so the stable parent deposit index is the replay-prevention identity. Plan a carry proof Choose a snapshot leaf count and one occupied peak. The planner reports the peak-local index bound and the exact sibling-array lengths enforced by the verifier. It does not construct hashes or replace an offchain proof builder. Snapshot leaf count Occupied peak height Height 2 Peak-local leaf index Binary leaf count 1101₂ Occupied peak heights 0, 2, 3 Selected peak capacity 4 leaves; local indexes 0…3 MMR sibling hashes required 4 Nullifier sibling hashes required 64 Selection Valid peak-local index","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"snapshots","heading":"Snapshots In Child Continuations","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"Each continuation outcome stores both an inherited snapshot baseline and a mutable current state. Field Meaning snapshotLeafCount and snapshotPeaks The immutable inherited carry commitment. currentLeafCount and currentPeaks The descendant carry snapshot after inherited snapshot initialization plus local carry appends and local carry-leaf removal. Inherited proof consumption is tracked by currentNullifierRoot and unresolved totals rather than by mutating these peak fields directly. inheritedUnresolvedTotalAttoRep and localUnresolvedTotalAttoRep The principal totals the current carry state must still represent. currentNullifierRoot The replay-protection root after any carried proof has been consumed. The continuation API exposes the current descendant carry snapshot through getForkCarrySnapshot() and pages only local unresolved leaves through getCarryLeafPageByOutcome() . The immutable inherited baseline remains in snapshotPeaks and snapshotLeafCount inside outcome state, while getForkCarrySnapshot() reports the current carry peaks, current leaf counts, current totals, and current nullifier roots after local appends and proof consumption.","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"sources","heading":"Sources","keywords":[],"path":"reference/merkle-mountain-range.html","sectionTitle":"Reference","summary":"Carry-proof hashing, snapshot peaks, bounds, and replay protection.","text":"MerkleMountainRange.sol EscalationGameProofVerifier.sol EscalationGameCarry.sol EscalationGameTypes.sol","title":"Merkle Mountain Range proofs","topic":"Proofs","weight":0},{"fragment":"","heading":"","keywords":["Statoblast","markets","security pools","overview"],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Prediction Market draft This white paper develops the Statoblast prediction-market design, its REP-backed resolution path, and its security assumptions. The documentation overview introduces the system; the sections below provide the detailed protocol model and accounting rules. Related topics: documentation overview , escalation game , Zoltar , and security model . It is designed so that: Honest reporting is economically rewarded. Dishonest reporting becomes increasingly expensive. Anyone can challenge an incorrect resolution by risking REP. Markets continue operating even when participants fundamentally disagree. Every trader can ultimately settle their position according to the outcome they believe is truthful, even if multiple realities continue to exist simultaneously. Statoblasts design is based on Artic Tern Oracle and Sisyphean Exchnge .","title":"Statoblast protocol","topic":"Protocol","weight":1},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast consists of two separate protocol identities. Zoltar is the base forking oracle layer for universes, questions, and REP branching. Augur Statoblast is the prediction market layer that adds market collateral (open interest), local disputes and fork resolution mechanisms Statoblast's lifecycle starts by creating a pool scoped to one question and universe, letting traders use it and vault owners fund its security vaults, trying to resolve disputes locally, and only then turning to fork. A question is registered in Zoltar and a Statoblast security pool is deployed for that question in one universe. Vault owners deposit REP into security vaults and select a target health factor. Their REP-denominated capacity ownership is price independent; the current REP/ETH price converts the pool's aggregate ownership into live ETH minting capacity without iterating through vaults. After the market ends, REP reporters try to resolve the market outcome locally through the escalation game. In the unfortunate case where the escalation game raises sufficient amount of REP without reaching a decision, Statoblast triggers a Zoltar fork. An unrelated external Zoltar fork can also interrupt the pool before local resolution. After a fork occurs in Zoltar, a migration period begins in each Statoblast pool. Each vault holder chooses a market outcome, migrates its backing units there, and routes a proportional share of that pool's settlement collateral . If a child pool is missing ETH collateral after migration, it sells child-universe REP through a Truth Auction to repair that collateral gap. The child pool in the branch users choose to keep using resumes operation and users settle or redeem positions there. The lifecycle is shown in the system decision-flow diagram below: operation leads to local escalation, then either settlement or fork migration, auction repair, and child settlement.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"2. Protocol participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"#","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Traders / Liquidity providers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit ETH to mint complete sets containing transferable Yes , No , and Invalid outcome shares. Pay time-based fees in ETH to vault owners for the duration that the complete sets remain outstanding. Trade by splitting complete sets into their individual outcome shares. This trading occurs outside the protocol. Burn complete sets to recover ETH before resolution, or redeem individual winning shares for ETH after the market resolves.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Vault owners","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Deposit REP into a security pool. Provide REP-backed security capacity for traders. Receive time-based fees in ETH from traders holding outstanding complete sets. Participate in the Escalation Game after the market ends. Choose where to migrate their REP backing units and allocated share of pool settlement collateral after a fork. Keep the value of their REP backing above the amount required to secure their allocated open interest. Trigger OpenOracle games when a REP/ETH price is required for a protocol operation.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"REP holders","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Decide whether to create a security pool for a question. Decide whether to participate in an existing security pool. Split their REP into outcome-specific child-universe REP during a Zoltar fork.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"OpenOracle disputers","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"OpenOracle disputers, such as arbitrage bots, monitor active OpenOracle games and challenge inaccurate REP/ETH prices when doing so offers a profitable arbitrage opportunity.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Truth auction participants","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"After a Zoltar fork, truth auction participants provide ETH to acquire REP-backed positions from an auction. Their ETH helps repair missing settlement collateral in a child security pool.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"roles","heading":"Liquidation receivers and operators","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"A receiver vault accepts moved security-bond debt and receives the REP backing-unit and capacity-ownership award. An operator monitors vault health, submits the liquidation, and pays its gas and oracle costs; the protocol does not compensate the operator merely for submitting. The same account can fill both roles through the self-receiving route, or a receiver can grant a bounded approval for a separate operator. See Capacity and delegated liquidation for the complete role, approval, and execution lifecycle. A target becomes liquidatable when it no longer satisfies the protocol's required backing condition: value of REP backing ≥ security multiplier × open interest secured","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"3. Security Pools","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# The purpose of security pools is to ensure that all open interest is supported by sufficient REP backing: value of REP backing ≥ security multiplier × secured open interest . This requirement protects outcome-share holders by making dishonest behavior economically unprofitable. If a vault owner attempts to manipulate the market's resolution, the REP they risk losing should be worth more than any potential gain. The protocol therefore rewards behavior that supports correct resolution and penalizes behavior that threatens it.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"dynamic-capacity","heading":"Target Health Factor and Dynamic Capacity","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Each REP deposit made for fee-earning capacity includes a target health factor of at least 10,000 BPS . The deposit adds ⌊deposited REP × 10,000 / target health factor⌋ of REP-denominated capacity ownership. Solidity rounds this quotient downward, so an extremely high target factor can make a valid deposit add zero attoREP of ownership. Capacity ownership aggregates at pool level in constant time and determines the vault's fee share. The pool's one Statoblast security multiplier and the current REP/ETH oracle quote convert total capacity ownership into ETH minting capacity: capacity ownership ÷ REP per ETH ÷ security multiplier . A higher REP-per-ETH quote means each REP is worth less ETH and therefore lowers live ETH capacity; a lower quote raises it. Neither change rewrites vaults or erases existing open interest. Vault health and liquidation instead compare live obligations against the vault's actual REP and proportional capacity ownership at the current price. Withdrawals burn capacity ownership in proportion to the REP removed. Vault owners maintain a chosen health target through deposits rather than continually editing an ETH-denominated allowance.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Fee Extraction and Retention Rate","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Being vault holder is valuable as it gives a permission to earn fees from traders that are holding complete sets or share tokens. Simply by holding REP, you are not entitled any fees in the system. Fee extraction and retention vary with vault capacity ownership and utilization; see Open Interest Fees for the complete fee curve and epoch accounting. read more from here on how fees work","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"pools","heading":"Shares and Complete Sets","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"ShareToken is an ERC-1155 contract. Each complete set mints one Invalid , one Yes , and one No share. The token id encodes the universe id and outcome, making positions fork-aware. ETH mints Invalid, Yes, and No shares. Operational pools redeem full sets, and finalized winning shares redeem through outcome settlement. Share Lifecycle Complete sets mint one Invalid, Yes, and No share; operational redemption burns the complete set, while finalized settlement redeems the winning outcome.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"escalation","heading":"4. Escalation Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# When a market ends, Statoblast attempts to resolve its outcome by running an Escalation Game based on The Isonzo Front . Participants escrow vault-backed REP behind Invalid , Yes , or No . Each deposit adds to that outcome's cumulative balance; it does not have to outbid the previous deposit or the current leader. The accepted deposit must normally meet the game's starting bond, unless it fills the selected outcome to the non-decision threshold. The cumulative binding-capital threshold increases with elapsed time, and the median outcome balance determines the scheduled end. Individual deposits still use the fixed start bond. After that deadline, a strict balance leader resolves the question. If two outcomes instead reach the non-decision threshold, the game enters local non-decision and Statoblast proceeds to its fork-resolution path. Why not fork immediately? A fork asks the whole universe to branch, so Statoblast first gives vault-backed REP a local path to settle the question. The rising cost curve makes late disagreement increasingly expensive, while the non-decision threshold keeps the fork path available when competing outcomes remain strongly funded. The Escalation Game system is an optimization, and in theory the system would work without it as well, in a more inefficient manner. read more from here on escalation game works","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"7. Forks and Migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# If the Escalation Game fails to reach consensus and enough REP has been committed to satisfy Zoltar's fork threshold, the game triggers a Zoltar fork and the fork resolution process begins. A pool can also enter fork resolution when another Zoltar application forks the same universe. The pool then migrates to the child universes, runs a truth auction if needed, and continues there. A pool moves from active operation through fork, child deployment, REP and collateral migration, auction repair, and child settlement. Fork Timeline The parent pool pauses, vault owners select child universes, and each child resumes only after migration and any required collateral repair. Fork Steps The parent pool enters fork mode. Anyone can deploy a child pool for a valid fork outcome. Vault owners can migrate their REP backing units and capacity ownership to one child of their choosing. Claimable fees remain checkpointed, funded, and redeemable in the parent vault. Unresolved ongoing escalation games fork into child pools and the games continue after a pause The child starts a truth auction after the migration deadline if collateral is missing. After the truth auction ends, winning bidders settle into REP backing units and capacity ownership. Vault owners claim winning own-fork escalation deposits; the own-fork path is open only during the parent migration window. A successful direct claim invalidates that stable deposit identity in every current and future child continuation. Shares migrate into new universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"8 week migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Vault holders have eight weeks to choose a child universe. Migration happens one pool at a time: a vault's backing units divided by the pool's total backing units determine its share of that pool's settlement collateral. No collateral moves from other pools or markets in the universe. During the migration period, vault owners choose which child universe to support by migrating their positions into the corresponding child security vault. This preserves disagreement by splitting the parent market into separate branches. However, ETH is not duplicated across those branches. Each child pool receives only the pool-level settlement collateral routed to it, migrated REP backing units and capacity ownerships, and converted child-universe REP backing that actually move into it. For this mechanism to remain secure, the value of the pool-held REP backing must exceed the value of the pool settlement collateral it moves. Otherwise, a malicious vault could shift a significant portion of this pool's collateral to a universe in which they hold favorable positions. Example: backing-unit and settlement-collateral migration Bob's vault has 5 backing units out of 20 backing units in this pool. At the fork snapshot, the parent pool has 40 REP represented by those 20 units and 10 ETH of settlement collateral, so this example uses a 2 REP per backing-unit conversion. When the oracle forks into REP A and REP B , Bob believes universe A is correct and migrates to universe A. As a result: Bob's vault receives 5 backing units ; the child pool receives 10 REP A after applying the snapshot's 2 REP per backing-unit conversion. A proportional share of this pool's settlement collateral moves to universe A: 5 backing units 20 backing units × 10 ETH = 2.5 ETH Bob's 5 of 20 pool backing units route 25% of this pool's 10 ETH settlement collateral, or 2.5 ETH, to the selected child. The remaining pool backing units route to universe B. Only this pool's collateral is divided; other markets and pools are unaffected. Their vaults receive the remaining 15 backing units ; the child pool receives the corresponding 30 REP B . The remaining proportional share of this pool's settlement collateral moves to universe B: 15 backing units 20 backing units × 10 ETH = 7.5 ETH The remaining 15 of 20 pool backing units route the remaining 7.5 ETH of this pool's settlement collateral to universe B. After migration: Universe A receives Bob's 5 backing units and 2.5 ETH of this pool's settlement collateral. Universe B receives the remaining 15 backing units and 7.5 ETH of this pool's settlement collateral. Escalation principal, rewards, carried claims, and externally supplied dispute-staked REP have zero parent-OI migration power. A losing participant would otherwise rationally route dispute-staked REP toward a false child merely to preserve valuable OI attached to it. Only pool-held vault REP backing determines a vault's migration weight. vaultMigrationPoweredAttoRep = vaultTotalAssociatedAttoRep − vaultEscalationGameAttoRep Escalation claims are excluded. Migration safety uses only pool-held vault REP backing. The effective multiplier is floored at 10,500 BPS so free backing normally funds the complete liquidation award. The exact live health rules are owned by the liquidation design . This separation lets REP perform escalation work without letting contingent claims secure parent open interest. A vault's settlement-collateral migration weight is its pool-held vault REP backing share of all pool-held vault REP backing at the fork. migrateVault transfers the vault's REP backing units and capacity ownership to one selected child and clears those two parent fields. The REP-backing share separately routes proportional pool-level settlement collateral, while the transferred capacity ownership determines the vault's live proportional open-interest allocation in the child. Claimable fees are checkpointed and remain funded and redeemable in the parent vault. The vault cannot divide or reuse its migration state across sibling universes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"child-outcome-resolution","heading":"Child Outcome Resolution","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Child pools resolve outcomes through the forker. When the parent universe forks on the pool's question, each child stores its selected Zoltar outcome as the fixed result, whether the fork began through the pool-specific path or a direct Zoltar call. The pool stores and reports that result from child creation, and it is final for that pool. Pool asset redemptions begin after the child becomes operational. Question, pool, escalation, fork, child migration, auction repair, fixed-outcome settlement, and unresolved recursive continuation flow. System Decision Flow The lifecycle connects question registration, pool operation, local escalation, fork migration, auction repair, settlement, and recursive continuation when a child remains unresolved.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"migration","heading":"Shares migration","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"When a fork occurs, share migration locks the source balance and accepts an ordered list of child targets. The first materialization gives each untouched selected child the full current source balance; later calls mint only that child's unmaterialized delta. Shares do not disappear from the source or split into a pro-rata remainder. On first materialization into untouched children, the full current parent balance materializes in each selected child token id while the source remains locked as an entitlement. Later calls mint only the unmaterialized delta for each child. Share Migration Share migration is not a pro-rata split across selected branches. The source balance remains as a transfer-locked entitlement after first use, and each untouched selected child first materializes the full current balance. Later calls mint only that child's unmaterialized delta.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"auction","heading":"8. Truth Auction","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# After migration, a child pool may still have less ETH than it needs to support its open interest. A Truth Auction can sell some child-universe REP for ETH to repair that shortfall. The auction has an ETH raise target and a cap on how much REP it may sell. The cap depends on migrated REP, pool-held REP, and unresolved dispute-staked REP; it may allow the full auctionable amount, reserve a small amount, apply a migration haircut, or be zero. The child resumes with the collateral actually migrated and raised. See the Truth Auction design for timing, caps, rounding, bidding, and settlement. See the collateral-repair illustrations Parent settlement collateral 50 ETH Migration-routed collateral 47.5 ETH Auction ETH raised 2.5 ETH A child pool before auction, after full auction repair, and after weak auction demand settles at the collateral actually raised. Truth Auction Balance Sheet The auction compares received settlement collateral with the parent settlement target and shows any remaining shortfall. Child collateral repair progress Collateral Repair Progress Blue shows settlement collateral routed during migration, green shows auction repair, and the dashed line marks the parent settlement target. Migration-routed collateral Auction repair Dashed line: parent settlement target Routed collateral 47.50 ETH Initial shortfall 2.50 ETH Remaining shortfall 0.00 ETH Repair status no contribution required","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"oracle","heading":"9. REP/ETH Price Oracle","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"# Statoblast uses a REP/ETH price to evaluate pool security. The price feed supports operations whose safety depends on REP value relative to pool settlement collateral.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"statoblast-glossary","heading":"Glossary","keywords":[],"path":"explanation/statoblast.html","sectionTitle":"Explanation","summary":"A binary prediction market protocol","text":"Capacity ownership A REP-denominated, price-independent share of pool minting capacity and fee eligibility created from deposited REP and the selected target health factor. The live oracle quote converts it into ETH minting capacity; it is not the vault's ETH debt. Settlement collateral ETH held by a security pool to redeem complete sets and settle the winning outcome. Statoblast retrieves this price using OpenOracle ; see the OpenOracle integration explanation for the coordinator lifecycle. A REP withdrawal or liquidation either executes immediately with a valid cached price or stages behind a pending OpenOracle report. REP/ETH Oracle Flow The coordinator is a queue plus cache. A fresh price can execute immediately; a stale price moves operations into a pending-settlement path whose callback batch is capped at four operations but whose duration is unbounded under paid rolling disputes.","title":"Statoblast protocol","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["Zoltar","universes","questions","REP"],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Draft Zoltar is a generalized branching oracle and coordination layer for binary, categorical, and scalar questions. Rather than forcing a single answer when disagreement persists, it represents unresolved outcomes as explicit child universes. After a fork, REP holders can convert parent REP into a migration balance and use that balance to mint REP in one or more child universes. The design closely follows the Colored Coins model: the protocol branches state first, while applications and their users later coordinate which branch ultimately carries economic value. Zoltar is not itself a prediction market or an outcome-reporting oracle. Instead, it provides the shared infrastructure for questions, REP tokens, and universe forks. Any application can build on top of it and define its own local dispute process. If disagreement cannot be resolved locally, anyone able to commit the required REP threshold may fork an unforked universe using an eligible ended global question. The resulting child universes become the new foundations from which applications may choose to continue.","title":"Zoltar and branching truth","topic":"Protocol","weight":1},{"fragment":"timeline","heading":"Lifecycle Timeline","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar's lifecycle has six conceptual steps, from question registration through post-fork coordination. 1. Register question ZoltarQuestionData stores the question and defines its valid answer space. 2. A fork is triggered After the question end date, any address able to supply the threshold REP can call forkUniverse . Zoltar does not verify that a disagreement exists; applications decide when a fork is justified. 3. Parent universe forks Zoltar records the fork question and deterministically defines every valid child branch. 4. Child universes deploy lazily Branches exist by deterministic id first; contracts are deployed only when needed. 5. REP holders split Migration balances mint child REP into one or more selected branches. 6. Applications and users choose where to continue The protocol does not pick a winner. Users and applications decide where durable activity continues.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"1. System Overview","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar has one responsibility: define and account for branching universes. It does not implement the market, collateral, or underwriting system built on top of it elsewhere in the repo. Its role is narrower and more fundamental: register questions encode valid answer spaces represent forks as child universes mint and burn child-universe REP turn migration balances into selected child REP In practice, an application can register a question, trigger a fork after that question ends, and use the child-universe structure that Zoltar defines. ZoltarQuestionData stores question state and answer encoding. Only Zoltar can mint the child-universe ReputationToken ; genesis REP is separately deployed and supplied to Zoltar. Zoltar can never mint more REP into a single universe than its parent has REP.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"overview","heading":"Zoltar Contracts","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Zoltar : universe forks and REP splitting ZoltarQuestionData : question registry and outcome encoding ReputationToken : child-universe REP minted and burned by Zoltar ScalarOutcomes : scalar formatting and interpolation logic","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"universe-model","heading":"2. Universe Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# A universe is one branch of protocol state. The Zoltar.Universe struct stores the minimum data needed to identify that branch and connect it to its parent: forkTime : when the universe forked forkQuestionId : the question id recorded by forkUniverse and used to define child branches forkingOutcomeIndex : the outcome index represented by the universe when it is a child reputationToken : the REP token used inside that universe parentUniverseId : the parent branch Universe 0 is the genesis universe. Its network-specific REP token is supplied to the Zoltar constructor. Mainnet uses the existing REP deployment; Sepolia deterministically deploys a genesis REP token from the configured initial-holder allocations before deploying Zoltar . Child universes are identified deterministically as: childUniverseId = uint248 ( keccak256 ( abi.encode ( parentUniverseId , outcomeIndex ) ) ) Child universe ids are deterministic hashes of the parent universe and outcome index. Deterministic child ids make each branch reproducible from parent universe and outcome index alone. Child universes are deployed lazily when a forked branch is actually needed. Concrete balance example. Alice converts 100 parent-universe REP into a migration balance and mints 100 REP in the Yes child and 100 REP in the No child. This reproduces the parent REP claim across explicitly selected branches. Genesis ├── Yes: Alice holds 100 Yes-REP │ ├── Option A: Alice may later mint 100 Yes/A-REP │ └── Option B: Alice may later mint 100 Yes/B-REP └── No: Alice holds 100 No-REP Universe Tree Universe tree. A fork creates the full valid branch set; no branch is privileged by the contract. Forking parent Invalid branch Valid outcome branches Universe tree A fork creates the full valid branch set; no branch is privileged by the contract. Important distinction Zoltar defines the branch set. It does not select one canonical child universe or delete the others.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"global-question-scope","heading":"Global Question Scope","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Questions are global protocol objects in Zoltar. forkUniverse checks that the target universe exists, still has supply, has not already forked, and that the supplied question exists and has ended. It does not require the question to have been created for that universe. Applications that need a stricter universe/question relationship enforce it above Zoltar. Accepted eligibility boundary Zoltar intentionally accepts any existing ended global question. It does not require a minimum creation age, prior universe registration, or proof that the fork's disruption is smaller than its economic benefit. The fork-threshold REP commitment and configured haircut are the protocol's admission mechanism. A stricter relevance, aging, or cost-benefit rule is not a Zoltar invariant.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"3. Fork Thresholds and REP Economics","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# To trigger a Zoltar fork, the caller must commit the forkThresholdAttoRep : the parent universe's theoretical REP supply divided by the configured threshold divisor, rounded down. The REP threshold and haircut are the intended admission cost for a fork, including unnecessary forks that impose migration work on applications using that universe. Both the threshold and the haircut are intended to make unnecessary forks economically costly. Forking affects applications and users relying on that parent universe; each application chooses which child branch, if any, to continue using. forkThresholdAttoRep = ⌊ totalTheoreticalRepSupplyAttoRep forkThresholdDivisor ⌋ With the default divisor of 20 , this is approximately 5% of theoretical REP supply, subject to integer flooring. Triggering a fork removes the full fork threshold from the parent universe. `forkUniverse` removes `forkThresholdAttoRep` of parent REP and reduces the parent universe's theoretical REP supply by the same amount. Only part of that removed REP is credited toward REP in the child universe. forkBurnDivisor determines the haircut. With forkBurnDivisor = 5 : 20% is burnt: ⌊forkThresholdAttoRep / 5⌋ 80% becomes migration credit: ⌈4 × forkThresholdAttoRep / 5⌉ The constructor rejects forkBurnDivisor < 5 uncreditedForkHaircutAttoRep = ⌊ forkThresholdAttoRep forkBurnDivisor ⌋ forkInitiatorMigrationBalanceAttoRep = forkThresholdAttoRep - uncreditedForkHaircutAttoRep Threshold Deposit Split Threshold Deposit Split. The full parent-REP threshold leaves parent supply. Most is re-expressed as migratable child-REP credit; the haircut is not credited. Genesis REP cannot be burned natively, so the contract transfers it to the configured burn address. Child-universe REP is minted and burned directly by ReputationToken under Zoltar’s control. A child's theoretical supply is a maximum for REP that can be minted in that branch. It starts from the parent's pre-fork theoretical supply and subtracts only the uncredited haircut. Later REP added to a migration balance converts 1:1; only fork initiation pays this admission cost. A holder can voluntarily burn REP and reduce theoretical supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"economics","heading":"Repeated-fork threshold decay","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Along one lineage, each fork subtracts its floored haircut from child theoretical supply. With threshold divisor 20 and burn divisor 5 , supply is close to 99% of the previous generation, subject to Solidity integer flooring. Repeated-fork economics. Each default fork leaves approximately 99% of the previous generation's theoretical REP supply, and the next fork threshold remains 5% of that declining supply.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"splitting","heading":"4. Child Universes and REP Splitting","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Once a universe forks, child universes can be deployed lazily through deployChild . A user can also add more REP into the migration balance with addRepToMigrationBalance . The core post-fork action is splitMigrationRep , which lets a holder mint child-universe REP for valid outcome indices. Supplying no outcome indices is accepted as a no-op at the Zoltar layer. For categorical questions, Invalid and any in-range categorical outcome are allowed, while out-of-range values are rejected. For scalar questions, only well-formed scalar encodings are allowed. The fork question defines the child-branch shape for the whole universe. A child branch may therefore be keyed by Invalid , a categorical outcome index, or a scalar encoding depending on which parent-universe question forkUniverse used for the fork. Colored Coins-style property The same migration balance can mint child REP in each selected child. Later value concentration determines which branch or branches matters economically. At the implementation level, splitMigrationRep validates each selected outcome against the parent universe's fork question, lazily deploys any missing child universe, and records how much of the caller's migration balance has already been minted into each child. A caller cannot mint more into one child than the source migration balance available to that caller, but the same source balance can be reproduced into multiple valid children.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"security","heading":"5. Assumptions and Security Model","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar is a Colored Coins -style system, so its security argument depends on user behavior and value concentration rather than on the contract being able to identify one objectively correct branch onchain. users can choose which child universe (or child universes) to continue using after a fork users prefer to continue in the universes they regard as truthful the protocol itself does not know which universe is truthful and treats all valid branches symmetrically most durable economic activity concentrates in the branch that users expect other users to keep using dishonest or abandoned branches may continue to exist, but are assumed to retain little long-term value compared with the branch that market participants keep coordinating around","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"6. Questions and Outcome Encoding","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# ZoltarQuestionData.QuestionData stores title, description, start and end time, scalar metadata such as numTicks , displayValueMin , displayValueMax , and answerUnit . Question ids are deterministic hashes of the question data. For categorical questions, that hash path also includes the sorted categorical outcome options. For scalar questions, there are no categorical labels to include, so the id is determined from the scalar question fields alone. Every call to createQuestion also requires endTime >= startTime . The contract rejects any question whose end time is earlier than its start time before it reaches scalar or categorical validation.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Categorical Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Categorical questions store sorted outcome labels. The implementation requires labels to be non-empty and strictly ordered by hash: each label's keccak256(abi.encode(label)) value must be lower than the previous label's hash, so callers provide labels in descending hash order. The contract stores the labels in outcomeLabels[questionId] . Any number of categorical outcomes can exist at the Zoltar level as long as those conditions hold.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"questions","heading":"Scalar Questions","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"Scalar questions store no categorical labels. Instead, numTicks , displayValueMin , displayValueMax , and answerUnit define the answer space. At creation time, scalar questions must satisfy numTicks > 0 and displayValueMax > displayValueMin . Onchain, each scalar answer is encoded into a single uint256 that packs: the highest bit as an invalid flag a 120-bit first payout numerator a 120-bit second payout numerator Packed Scalar Answer Packed Scalar Answer. A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks. Namespace bit First payout Second payout Packed Scalar Answer A scalar answer is easier to read as reserved bits, a namespace flag, and two payout fields: bits 254...240 must be zero, 0 in bit 255 is the invalid namespace, and 1 means the payout fields must sum to numTicks . The all-zero encoding is the canonical Invalid answer for scalar questions. For a valid scalar answer, the two payout numerators must sum exactly to numTicks : firstPayoutNumerator + secondPayoutNumerator = numTicks A valid scalar answer must allocate all ticks across the two payout numerators. The all-zero word is the only canonical scalar Invalid value. Any other word with the highest bit clear is malformed, and any word with a nonzero reserved bit in 254...240 is malformed even if the payout fields would otherwise decode cleanly. At the helper and UI level, a scalar tick index named tickIndex is encoded as: firstPayoutNumerator = numTicks - tickIndex secondPayoutNumerator = tickIndex highest bit = 1 ( numTicks - tickIndex , tickIndex ) The helper encodes a tick as left and right payout numerators. ScalarOutcomes interprets secondPayoutNumerator as the position along the scalar range: displayedAtomic = displayValueMin + ⌊ secondPayoutNumerator ⋅ ( displayValueMax - displayValueMin ) numTicks ⌋ The contract interpolates the scalar answer with mulDiv , so uneven divisions round down before the value is formatted. displayValueMin and displayValueMax are stored as 18-decimal fixed-point display bounds. Formatting divides the atomic result by 1e18 and trims trailing zeroes. For example, with displayValueMin = 0 , displayValueMax = 10e18 , numTicks = 6 , and secondPayoutNumerator = 1 , the atomic value is ⌊10e18 / 6⌋ , which displays as 1.666666666666666666 rather than an unrounded real number.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"question-types","heading":"7. Question Types Supported by Zoltar","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# At the Zoltar layer, market type support comes from how questions and outcomes are encoded in ZoltarQuestionData . categorical questions, implemented as an ordered array of non-empty outcome labels binary questions, implemented as categorical questions scalar questions, implemented as a tick-based numeric range with no categorical labels","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"invalid","heading":"8. Invalid vs Malformed","keywords":[],"path":"explanation/zoltar.html","sectionTitle":"Explanation","summary":"Universes, question encoding, global forks, and REP splitting.","text":"# Zoltar distinguishes Invalid answers from Malformed answers. Malformed answers are rejected, while Invalid remains a valid branch and a valid final outcome. Term Meaning Effect Invalid A legitimate resolution state. Can be a valid branch and final outcome. Malformed A submitted outcome index or scalar encoding that does not fit the question’s answer space. Rejected during child-universe REP splitting and fork-aware asset branching.","title":"Zoltar and branching truth","topic":"Protocol","weight":0},{"fragment":"","heading":"","keywords":["contracts","architecture","calls","assets"],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Documentation index Mental model: actors enter through pools, oracle coordination, Zoltar universes, and child repair; custody boundaries explain the call graph. This map connects the primary deployed Zoltar registry and Statoblast market contracts. It emphasizes contract-to-contract calls and asset movement, not every public user entrypoint. Factory-only deployment workers, delegate modules, event emitters, and compatibility contracts are summarized separately so the runtime path stays legible. Related reading: the generated transaction reference for exact caller and prerequisite rules, the Statoblast whitepaper for lifecycle context, and the invariant catalog for cross-contract safety properties.","title":"Contract architecture","topic":"Architecture","weight":1},{"fragment":"conceptual-model","heading":"Conceptual Model Before the Graph","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Statoblast separates identity, market accounting, price-sensitive execution, dispute escrow, and fork repair: ZoltarQuestionData stores question state and answer encoding; Zoltar manages universes, forks, and REP branches. A security pool tracks market collateral, vault accounting, open interest, and lifecycle state. The share token tracks outcome-share balances. The price coordinator stages operations that require a fresh REP/ETH price. The escalation game escrows reporting REP and tracks local dispute state. The pool forker creates and repairs child pools after a fork. One complete-set path is: a trader calls SecurityPool , the pool accepts ETH collateral, and the pool calls ShareToken to mint one share for every outcome. The event stream records the supply and accounting changes. The detailed graph below shows primary protocol interactions; supporting factories, delegates, emitters, callbacks, and compatibility contracts are summarized rather than expanded.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"overview","heading":"Primary Interaction Flow","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Arrows point from the contract initiating an interaction to the contract receiving it. The map repeats a contract when it participates in more than one phase, separating construction-time wiring, recurring runtime calls, and fork repair into three readable panels. Users and keepers enter through several of these contracts, but are omitted so the plot can focus on contract-to-contract boundaries. Contract interactions are separated into three phases. During deployment, the pool factory validates question and universe data before deploying the pool, share token, and price coordinator. During runtime, the pool manages claims and escalation escrow while guarded actions travel through the price coordinator and OpenOracle. During a fork, the share token enters the pool forker, which snapshots escalation, migrates REP through a proxy, creates and migrates child pools, and repairs backing through the truth auction. Contract Interaction Map Each panel is a distinct protocol phase; repeated contracts preserve local reading order and prevent unrelated lifecycle edges from crossing. Blue nodes are registries or oracle infrastructure, green nodes hold market state and claims, red marks local resolution, and gold marks deployment or fork coordination. Short edge labels state the action; the table below supplies its exact contract-level meaning.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"edges","heading":"What Each Arrow Means","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Initiator Receiver Phase Interaction SecurityPoolFactory ZoltarQuestionData Deployment Confirms that the question exists and is the required Yes/No categorical shape before reserving the canonical pool identity. SecurityPoolFactory Zoltar Deployment Checks the universe and resolves its REP token for the new pool and price coordinator. SecurityPoolFactory SecurityPool Deployment Deploys and records the canonical origin pool. Forker-requested child deployment copies the lineage and fork state. SecurityPoolFactory ShareToken Deployment Deploys the origin lineage token and authorizes the canonical pool; child pools reuse that same token. SecurityPoolFactory OpenOraclePriceCoordinator Deployment Deploys the REP/WETH price coordinator and wires it to the newly created pool. Zoltar ReputationToken Universe lifecycle Deploys child-universe REP, sets its theoretical supply, and exclusively mints or burns that child REP during migration and voluntary burns. SecurityPool ShareToken Market runtime Mints complete sets, burns complete sets or winning shares, and authorizes canonical child pools to continue the same lineage claims. SecurityPool EscalationGame Resolution Deploys the local game on first use and transfers vault REP into outcome-specific escrow. Settlement returns winning value or carries proofs across a fork. SecurityPool OpenOraclePriceCoordinator Risk operations Reads the coordinator's last valid REP/ETH price. Vault owners and liquidation operators stage withdrawals and liquidations directly on the coordinator before restricted pool execution; target-health deposits create capacity ownership directly at the pool. OpenOraclePriceCoordinator OpenOracle Price discovery Funds a REP/WETH report and later withdraws the settled reporter balances that it validates and returns to the report sponsor. OpenOracle OpenOraclePriceCoordinator Price settlement Calls openOracleCallback after settlement so the coordinator can validate the final report, update its cached price, and attempt bounded staged-operation execution. OpenOraclePriceCoordinator SecurityPool Risk execution After price and snapshot checks, calls the pool-only liquidation or REP-withdrawal entrypoint. Those operations update capacity ownership internally when REP backing or liquidation ownership moves. ShareToken SecurityPoolForker Share migration Initiates a source-pool fork when needed and asks the forker to create a missing canonical child before migrating a holder's shares. SecurityPoolForker EscalationGame Fork snapshot Reads carry peaks, roots, outcome balances, timing, and fork parameters so unresolved escalation state can continue in child pools. SecurityPoolForker SecurityPoolMigrationProxy Fork migration Deploys and controls the pool-specific adapter, transfers parent REP to it, and instructs it to lock, fork, split, and sweep child REP. SecurityPoolMigrationProxy Zoltar Fork migration Acts as the literal Zoltar caller whose stable address holds the migration ledger balance while parent REP is locked and split into child-universe REP. SecurityPoolForker SecurityPoolFactory Fork migration Requests canonical child-pool deployment for selected child universes. SecurityPoolForker SecurityPool Fork migration Copies or recomputes financial state, migrates vaults and collateral, and activates the selected child lifecycle. SecurityPoolForker UniformPriceDualCapBatchAuction Backing repair Starts, finalizes, and settles the child truth auction when migrated REP and inherited ETH collateral do not arrive in the required proportions.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"paths","heading":"Four Useful Reading Paths","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"Deploy a market: ZoltarQuestionData and Zoltar provide the question and universe context; SecurityPoolFactory creates the pool, share token, and price coordinator. Trade and resolve locally: SecurityPool accepts ETH collateral and mints one share for every outcome through ShareToken , then escrows reporting REP in EscalationGame . Reprice and liquidate: OpenOraclePriceCoordinator obtains a report from OpenOracle , then calls a restricted execution method on SecurityPool . Continue after a fork: SecurityPoolForker migrates REP through Zoltar , asks SecurityPoolFactory for child pools, transfers the inherited state, and uses UniformPriceDualCapBatchAuction when backing must be repaired.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"supporting-contracts","heading":"Supporting Contracts Not Expanded in the Plot","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The diagram groups construction helpers and delegate modules behind the deployed component whose behavior they implement: ShareTokenFactory , EscalationGameFactory , PriceOracleManagerAndOperatorQueuerFactory , UniformPriceDualCapBatchAuctionFactory , and SecurityPoolDeployer construct the displayed runtime contracts. EscalationGameDepositDelegate , EscalationGameClaimDelegate , and the SecurityPoolForker* delegate modules execute against their owning contract's storage; they are implementation boundaries, not independent user-facing protocol hubs. SecurityPoolMigrationProxy isolates a pool's Zoltar migration balance, while SecurityPoolEventEmitter preserves pool-address event attribution under delegated fork work. WETH9 , Multicall3 , and the imported OpenOracle implementation are compatibility or external boundaries; only OpenOracle's protocol-facing relationship is shown.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"sources","heading":"Source Contracts","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"ZoltarQuestionData.sol Zoltar.sol and ReputationToken.sol SecurityPoolFactory.sol SecurityPool.sol and ShareToken.sol EscalationGame.sol and SecurityPoolForker.sol OpenOraclePriceCoordinator.sol and OpenOracle.sol UniformPriceDualCapBatchAuction.sol","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"actors-and-boundaries","heading":"Actors, custody, and one operation","keywords":[],"path":"explanation/contract-architecture.html","sectionTitle":"Explanation","summary":"How deployed contracts call, deploy, and move assets between one another.","text":"The protocol consists of several participants with distinct responsibilities. Market creators configure security pools, traders mint and hold complete sets and outcome shares, vault owners provide REP backing, liquidators repair under-collateralized vaults, auction bidders recapitalize child pools after forks, keepers execute permissionless maintenance operations, and indexers reconstruct protocol state from emitted events. Each contract handles a specific part of the protocol. Security pools hold user funds and manage market state. The Oracle Coordinator runs price-sensitive operations that need a REP/ETH price. Zoltar manages universes, questions, REP, and forks. An operation may involve several contracts, but that does not move every asset or piece of state between them. For example, when a trader mints a complete set, they send ETH to the security pool. The pool accepts the deposit, mints one outcome share for every possible outcome, and updates its accounting. Each contract changes only the state it manages, while emitted events let external indexers reconstruct the operation. The deployment graph above illustrates the primary contract interactions in this release. Supporting factories, delegates, callbacks, emitters, and compatibility contracts are omitted for clarity. Contract addresses and call relationships are deployment details, while the custody and responsibility boundaries described above remain stable across deployments.","title":"Contract architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["EscalationGame","modules","authority","accounting"],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The Escalation Game's deposit, timing, outcome-selection, and continuation rules fit into the wider prediction-market lifecycle described in the Statoblast white paper. When a Statoblast market ends, the security pool sends a valid deposit through its escalation-deposit delegate. The deposit must pass the pool's vault-backing and lifecycle checks and usually meet the configured starting bond. A smaller deposit is allowed only when it fills an outcome to the non-decision threshold . The first deposit does not resolve the market by itself. A deposit moves the local deadline only when it raises the median of the three outcome balances. A strict leader identifies the local result, but the pool can finalize it only after the deadline. If competing outcomes reach the non-decision threshold, the pool follows the fork path. Lifecycle: tentative claim → conflicting deposit → accepted amount and new deadline → local settlement or non-decision → child continuation after a fork → proof-backed claims.","title":"Escalation game architecture","topic":"Architecture","weight":1},{"fragment":"escalation","heading":"Escalation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"A vault owner with enough backing can add REP to an outcome with a valid deposit that meets the configured start bond. The deposit adds to that outcome's cumulative balance; it does not have to exceed the leader. The deadline moves only when the deposit raises the median outcome balance. Separately, the cumulative binding-capital threshold rises over time: Attrition cost is zero on days 0–2, equals the start bond on day 3, and reaches the non-decision threshold on day 52. Required support threshold A(d) = 0 for 0 ≤ d < 3; S for d = 3; S × exp(ln(T / S) × (d − 3) / 49) for 3 < d < 52; T for d ≥ 52 Here S is the configured start bond and T is the non-decision threshold. The rendered chart uses the contract's fixed-point arithmetic; this exponential expression is its readable idealization. The contract waits three days after start() before the escalation clock begins. The chart labels day 0 (game start), day 3 (activation), and day 52 (the end of the seven-week escalation interval). Try a simplified escalation round This simulator uses the contract model: a three-day activation delay, a 49-day escalation interval, and the fixed-point attrition curve. Change the configuration, then choose an outcome and requested deposit to see clipping, tie adjustment, acceptance, or rejection. Start bond 1 REP Non-decision threshold 10 REP Current Yes balance 1 REP Current No balance 1 REP Current Invalid balance 0 REP Deposit outcome Yes No Invalid Requested deposit 1 REP Days since start 0 days Configured start bond 1 REP Configured threshold 10 REP Leading outcome Yes / No Median balance 1 REP Deposit before → after Yes 1 / 2 REP; No 1 / 1 REP; Invalid 0 / 0 REP Deposit result accepted: 1 REP Activation 3 days Scheduled deadline (before → after) day 3.0 → day 3.0 Contract-model state deadline pending","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"The game end","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"The game can end two ways The required escalation game increased high enough that only one side was able to keep up with it: That side wins and Statoblast finalizes on that outcome Two sides have reached the maximal deposit amount: Statoblast triggers a fork After the game ends, the payout uses binding capital , the safety boundary , and the reward-eligible cap . A winning deposit returns its principal and may receive a bonus. Any excess above the reward cap returns principal without a bonus. The game burns the winning-deposit haircut ; forked payouts can also be scaled by the fork threshold. Exact example. Let binding capital be 10 REP , so the reward-eligible cap is 10 + 10 / 2 = 15 REP . The reward pool is 10 × 3 / 5 = 6 REP , and the haircut pool is 10 × 2 / 5 = 4 REP . If the winning outcome has 15 REP and a deposit contributes 5 REP within the eligible range: Principal returned: 5 REP . Bonus: 5 × 6 / 15 = 2 REP . Winning payout: 5 + 2 = 7 REP . Haircut burned: 5 × 4 / 15 = 1.333333333333333333 REP at attoREP precision. At local settlement, a deposit position above 15 REP returns its principal but receives no bonus. If the fork threshold is 8 REP instead of the 10 REP non-decision threshold, the whole local withdrawal is scaled: 7 × 8 / 10 = 5.6 REP , rounded down to attoREP. Fork scaling can therefore reduce the final transfer below principal.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"glossary","heading":"Glossary","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"Binding capital The median REP balance across Invalid, Yes, and No. It is the amount the winning result must support and the value that sets the scheduled deadline. Non-decision threshold The configured REP balance at which two outcomes have enough support to stop local resolution and use the fork path. Safety boundary The interval above binding capital where a winning deposit can still earn a bonus. In this model it is (binding capital, reward-eligible cap] . Reward-eligible cap Binding capital plus half of binding capital. Only the portion of winning deposits up to this cap participates in the bonus calculation. Principal The REP originally deposited. A winning deposit returns its principal before any bonus is added. Haircut The REP amount calculated for burning from the reward-eligible portion of a winning deposit. It is separate from the payout amount.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"payouts","heading":"Escalation game continuation","keywords":[],"path":"explanation/escalation-game.html","sectionTitle":"Explanation","summary":"Module boundaries, authority, proof flow, and accounting responsibilities.","text":"In AugurV2, when a fork happens, it cancels all currently running escalation games, and the players are refunded. This opens an attack vector for the oracle, discussed more in Zoltar Forks Should Not Cancel Escalation Games . The fix to the issue is to fork escalation games. This is called continuation. Escalation game continuation uses Merkle Mountain Range carry proofs and nullifier roots to migrate user game deposits, without requiring expensive data copying efforts onchain.","title":"Escalation game architecture","topic":"Architecture","weight":0},{"fragment":"","heading":"","keywords":["liquidation","capacity","operator","receiver vault","approval","reservation","bad debt"],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Liquidation moves live ETH-denominated open interest and the corresponding REP-denominated capacity ownership away from an unhealthy vault. The receiver accepts the liability and receives the funded REP award. A separate operator may submit the transaction and pay its gas and oracle costs.","title":"Capacity and delegated liquidation","topic":"Economics","weight":1},{"fragment":"position-model","heading":"Three Distinct Roles","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The operator requests and stages the liquidation, paying that transaction's gas and any oracle cost. The receiver vault accepts moved security-bond debt and receives the capacity ownership and REP award. The target vault is the unhealthy vault whose position is reduced. If the liquidation remains staged, any account may execute it later and pay the execution transaction's gas. That permissionless executor does not replace the staged operator named by a delegated approval. The receiver and target must differ. The operator may be either one, but identity alone is not a health or Sybil-resistance boundary. For the backward-compatible path, omitting a delegated receiver makes the operator the receiver and requires no signed approval. The operator receives no liquidation ownership or REP merely for submitting the transaction. The first release assumes the operator and receiver coordinate compensation externally; the protocol neither creates a keeper marketplace nor takes a fee from the award.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"capacity-and-health","heading":"Capacity and Live Health","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A vault deposit creates REP-denominated capacity ownership from the deposited REP and its selected target health factor. The pool totals capacity ownership in constant time. The live REP/ETH oracle price and the pool Statoblast security multiplier convert that total into current ETH minting capacity. A REP price change therefore changes capacity without rewriting vaults, and it never deletes already-open interest. Vault health uses the vault's live REP backing, locally attributed dispute-staked REP, proportional live open interest, capacity ownership, the settled REP/ETH price, and the pool multiplier. The ordinary associated-REP branch counts pool-held and locally staked REP. The free-REP branch counts only pool-held REP because locked claims may lose. Required REP is rounded upward. A delegated approval may require a higher post-liquidation health factor. 10,000 means exactly the protocol minimum. The signed factor is applied to both backing branches and is checked again against live post-liquidation state at execution; a queue-time preview is never a guarantee.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"punitive-liquidation","heading":"Funded Transfer and Bad Debt","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The requested amount is ETH-denominated debt. Execution first caps a nominal quote by the target's live open interest and by the debt whose complete 5% REP bonus the target can fund. Capacity ownership is selected from that quote and rounded downward. The debt actually moved is then the exact increase in the receiver's live, upward-rounded open-interest allocation, which cannot exceed the nominal quote. On a delegated route, the coordinator additionally requires it not to exceed the staged approval reservation; the self-receiving path has no approval reservation. A positive quote that produces no receiver debt reverts. The target and receiver fee positions are checkpointed before their ownership changes; the operator is not checkpointed unless it is also one of those vaults. Delegated Liquidation Transfer Floor-rounded proportional capacity ownership and ceiling-rounded REP backing units leave the target. The authorized receiver incurs the exact reported debt increase and receives the ownership and backing units; target claims, fees, surplus, and unmatched ownership remain. Only a full-target request records residual target debt as bad debt. Liquidation accounting The operator submits the transaction, while the authorized receiver accepts the funded liability and receives the award. grossRepAwardAttoRep = ⌈ debtMoved ⋅ repPerEthPrice ⋅ 10,500 / ( PRICE_PRECISION ⋅ 10,000 ) ⌉ The gross award is converted into REP backing units with upward rounding. Because backing units are a proportional claim on the pool's current REP balance, the credited units can convert back to more current REP than grossRepAwardAttoRep ; the excess is less than the current REP value of one backing unit. On a full-target request, target-local bad debt is the target's live debt minus the receiver's exact settled increase. The residual may include debt excluded by the REP-award funding cap and a small amount left by integer ownership and open-interest allocation rounding. It is not an ETH collateral-shortfall test. Capacity ownership is conserved between the target and receiver; any ownership not transferred remains with the target. After a funded transfer, the receiver's resulting debt must meet the configured debt minimum and its REP backing must meet the configured vault REP minimum. A transfer smaller than the debt floor can still succeed when the receiver's existing position keeps its resulting debt above the floor. The target's resulting debt must be zero or meet the debt floor; when target debt remains, its REP backing must also meet the vault REP floor. These receiver or target dust checks revert instead of converting otherwise funded debt into bad debt.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"receiver-approvals","heading":"Bounded Receiver Approval","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"A delegated route needs a pool-specific approval for the exact receiver and operator. It may name one target or use the zero-address wildcard for any target in that pool. Each approval defines a cumulative ETH debt limit, a per-liquidation limit, a minimum post-liquidation health factor, an activation time, an expiration time, and a nonce. The receiver may install an approval directly or sign EIP-712 typed data. The domain binds the stable name and version, chain ID, and approval registry; the typed message explicitly binds the security pool. EOA signatures use ECDSA and contract-wallet signatures use ERC-1271. A contract-wallet signature is validated when installed and becomes explicit onchain state rather than an opaque signature retained until execution. Each receiver-scoped nonce can install only one approval, and replaying it is rejected. The receiver can revoke unused approval capacity or advance its nonce floor to invalidate older nonces. Both actions prevent new reservations without cancelling reservations already attached to staged operations. Approval state exposes available, pending reserved, and permanently consumed amounts so indexers can reconstruct every transition.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"queue-semantics","heading":"Reservation and Queued Execution","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"The coordinator reserves approval quota when it stages a liquidation. The reservation is no greater than the requested debt, the snapshotted target debt, the approval's remaining cumulative quota, or its per-operation limit. The approval must remain valid through the operation's latest possible execution time. Successful execution permanently consumes exactly the debt moved and releases unused reservation. A bad-debt-only result consumes no receiver quota. Expiration and every terminal failure release the full reservation, including stale target snapshots, target rescue, price-distance failure, inactive or forked pools, caught pool reverts, panics, unknown failures, and pending-report recovery. Cleanup is permissionless and recognizes expiration before requiring a valid oracle price. A terminal operation cannot release or consume twice. The coordinator snapshots target backing and ownership to prevent a stale quote from becoming a keeper windfall, then uses the settled price and live state at execution. Receiver state changes after staging can therefore cause a safe failure and reservation release.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"fork-behavior","heading":"Fork Isolation","keywords":[],"path":"explanation/liquidations.html","sectionTitle":"Explanation","summary":"Dynamic capacity, liquidation roles, bounded receiver approvals, and reservation lifecycle.","text":"Approvals and reservations belong to one pool and its coordinator. They are not copied into child pools or universes. A receiver approving liabilities in one universe has not approved them in a child. Pending parent operations remain permissionlessly cleanable after the parent becomes unable to execute, releasing their reservations.","title":"Capacity and delegated liquidation","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["fees","markets","vaults","accounting"],"path":"explanation/fees.html","sectionTitle":"Explanation","summary":"Understand fee accounting across markets, vaults, and protocol operations.","text":"Vault holders have a lot responsibilities, they need to maintain enough REP to cover their commitments for open interest holders, they need to participate in escalation games, fork migrations etc. The reward for this service is fees generated from open interest. The security pool sets a side fees for vault holders to claim when they like. Earned fees are not ever taken away from the vault holders. REP becomes valuable only when its used in the ecosystem. Complete-set collateral gradually decays into an unallocated fee reserve. Fee-eligible vaults accrue fees in proportion to their assigned capacity ownership. The retention rate falls as utilization rises, so security becomes more expensive as open interest consumes underwriting slack. Retention follows a piecewise heuristic. It starts high, declines linearly until 80% utilization, and then stays at the minimum retention rate. The intent is to charge more aggressively for security as utilization rises and underwriting slack falls. At zero utilization, the constants imply roughly a 10% yearly fee. Once utilization reaches the 80% dip, the annualized fee is roughly 50% and then stays flat because the on-chain retention rate is already pinned to its minimum. The UI annualizes the rate as annualFee = 1 - (retentionRate / PRICE_PRECISION)^SECONDS_PER_YEAR . Retention and utilization curve. The annualized fee rises from roughly 10% at zero utilization to roughly 50% at 80% utilization, then remains flat because the retention rate has reached its minimum. Piecewise retention formula The 80% rule marks the point where the minimum per-second retention rate begins to apply. Fee accrual is lazy: state-changing pool operations checkpoint the global fee index, and vault operations checkpoint each vault against that index. Fractional amounts remain in explicit remainders until they accumulate into whole attoETH, so callers do not need to iterate over every vault when time advances. Fee accrual is time-clamped. The pool extracts collateral into fees only until the question end time while the universe remains unforked; after the universe forks, the fee accumulator is instead clamped to the fork time. Retention-rate updates no-op outside Operational mode or when the recalculated rate is unchanged. While operational, utilization is tracked collateral divided by live ETH minting capacity, and that capacity uses total capacity ownership. An unclaimed truth-auction allocation is already part of total ownership, so it can provide minting headroom and affect utilization before the vault owner claims it. Claiming the allocation makes it fee-eligible without adding it to total ownership again, so that claim alone does not change live capacity or the retention curve. A child pool begins a new fee epoch when migration and any truth auction finalize. Only capacity ownership already assigned to a vault enters the fee denominator. Auctioned capacity ownership becomes fee-eligible when the winning bid is claimed, after that vault first checkpoints at the current index. Each delayed claim adds only its newly assigned amount to the live eligible total; it does not reconstruct that total from fork-time migration counters, so intervening capacity ownership changes and liquidations remain intact. Per-vault fractional remainders survive public checkpoints, while whole fees move from the pool reserve into totalClaimableVaultFeesAttoEth ; this keeps aggregate claimable fees equal to redeemable vault balances. The totalAccruedFeesAttoEth() view adds those assigned claimable fees to the unallocated reserve when callers need to reconcile all fee-adjusted collateral. A fork permanently closes the parent's fee epoch. The pool tracks how much eligible capacity ownership still has not checkpointed the final index. After every eligible vault syncs, any whole reserve attoETH left solely from aggregating individually sub-attoETH vault remainders cannot become redeemable, so it returns to complete-set collateral. Until the last checkpoint, that reserve remains protected by totalAccruedFeesAttoEth() . REP deposits mint proportional REP backing units, settlement collateral decay increments the fee index, and vault capacity ownerships claim fees from that index. Pool Accounting REP backing units and fee accounting use separate proportional ledgers: REP backing units track vault REP backing, which is not automatically withdrawable REP, while the fee index tracks decayed ETH settlement collateral owed to vaults. attoRepToBackingUnits ( attoRepAmount ) = attoRepAmount ⋅ PRICE_PRECISION if the ledger or pool-held REP balance is empty = ⌊ attoRepAmount ⋅ totalRepBackingUnits totalPoolHeldRepBalanceAttoRep ⌋ otherwise backingUnitsToAttoRep ( repBackingUnits ) = 0 if the backing ledger is empty = ⌊ repBackingUnits ⋅ totalPoolHeldRepBalanceAttoRep totalRepBackingUnits ⌋ otherwise The bootstrap branch establishes PRICE_PRECISION backing units per attoREP. Once both totals are nonzero, each conversion floors integer division exactly as the contract does.","title":"Protocol fees","topic":"Economics","weight":1},{"fragment":"","heading":"","keywords":["truth auction","clearing","settlement","repair"],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When a pool's migration deadline has passed, if a child pool lacks the ETH needed to fully collateralize its open interest, the child may start a truth auction. The fork may have been triggered either by a direct Zoltar fork or by the Statoblast Escalation Game. Because collateral follows migration rather than being copied in full, a child pool retains its obligations while lacking enough ETH to reopen cleanly. The truth auction sells child-universe REP to raise as much repair ETH as demand supports. A truth auction does not determine which outcome is true. It sells REP belonging to one already-defined child universe to repair that child pool's collateral. The auction has an ETH raise target and a cap on how much child-universe REP it may sell. That cap depends on migrated and dispute-staked REP. Statoblast writes winning claims back into vault state rather than paying simple token transfers, so bidders buy into the child pool and its matching capacity ownership instead of merely receiving REP.","title":"Truth auction economics","topic":"Economics","weight":1},{"fragment":"lifecycle","heading":"Operational Lifecycle","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Migration deadline passes; the child may start the auction later Bidding Bidding ends and finalization occurs one week after the auction actually starts Auction Lifecycle After migration closes, startTruthAuction takes one of two paths. When no sale is required, TruthAuctionFinalized activates the child immediately and bypasses AuctionStarted, bidding, and bid settlement. Otherwise AuctionStarted opens bidding; finalizeTruthAuction later computes clearing and activates the child, and paged calls then settle individual claims and refunds. Auction Lifecycle The start transition activates a child immediately when no sale is needed. On the repair branch, ETH bids restore collateral, auction finalization activates the child, and later calls settle winning claims and refunds. REP doesn't leave the system. Finalization accounts the ETH actually raised and rejects contribution-only ETH, so bidder settlement never depends on a donor.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Starting the auction","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The start call is permissionless but not automatic. It is valid strictly after the eight-week migration deadline and records the actual auction start time. That timestamp starts the one-week bidding window.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"Bidders submit bids at discrete price ticks , where each tick represents a maximum price in ETH per REP that the bidder is willing to pay. Tick 0 corresponds to a price of 1 ETH/REP . Each increase of one tick multiplies the price by approximately 1.0001 , while each decrease divides it by the same factor. The price represented by tick tickIndex is therefore approximately 1.0001 tickIndex ETH/REP . Uniswap uses a similar tick system. Losing bids can be refunded before finalization once current demand is enough to find a clearing tick. Only ticks below that clearing tick can be withdrawn through the pre-finalization refund path; binding or potentially winning bids stay in the auction. Once a clearing tick is found, it cannot be lowered, so currently losing bids remain losing bids.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"lifecycle","heading":"Bidding end","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When bidding ends, anyone may finalize the auction. Finalization clears the batch auction, sends the raised ETH into the respective security pool, and computes the accounting rates used to settle bidders. The child activates with legitimate migration settlement collateral plus retained bid ETH, even when that total is below the original fork snapshot. Full repair is not guaranteed, and the remaining impairment is borne by the affected child's open-interest holders. After finalization, winning bids are written into child-pool vault accounting. Winners receive REP backing units plus their proportional share of auctioned capacity ownership, rather than a direct REP token transfer. The same capacity share carries its cumulative portion of auctioned bad debt into the winner's vault. Intermediate claims round down, and the final capacity claim receives the exact residual, so settlement order cannot change the total bad debt assigned. The forker tracks collateral received through migration and the auction separately from the child contract's raw ETH balance. ETH forced into the child does not count toward the repair target. Finalization activates the child with migrated collateral plus accepted auction ETH and rejects nonzero finalizer contribution ETH.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"How Clearing Works","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The clearing process walks demand from the highest price downward. A bid above the clearing tick wins in full, a bid below it loses in full, and bids at the clearing tick share the last fill. In the uniform-clearing branch that last fill is First in First Out within the tick, so earlier same-tick bids are consumed before later ones. Higher ticks represent higher bid prices and therefore receive priority during auction clearing. The clearing algorithm matches bids starting from the highest tick and works downward until all available REP has been allocated. The auction has two caps ETH raise cap. The auction only wants to raise ETH required to make open interest holders whole REP selling Cap. The auction can at most sell all the rep it has As soon as cumulative demand is enough to hit either cap at some price, that price becomes the clearing tick and all winning bids settle from it at one price. That is a valid uniform clearing even when the REP cap binds before the ETH repair target is fully met.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"clearing","heading":"Underfunded Clearing","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"When demand never reaches a full clearing price, the auction switches into the underfunded branch. Finalization computes underfundedThreshold , stores the lowest tick whose price reaches that qualification threshold as clearingTick . Only bids at or above the cap-implied qualification threshold are retained; lower ticks refund. Qualifying bidders collectively purchase maxRepBeingSold for underfundedWinningEth , so the common effective price is their retained ETH divided by the REP cap. That effective price can be lower than the qualification threshold; the threshold is not an execution-price floor. If no ETH qualifies, every bid refunds. Sold REP is allocated between them proportionally with integer floors: “them” means the pool-held and unresolved dispute-staked REP buckets. The dispute-staked bucket receives ⌊repPurchased * disputeStakedRepBefore / combinedRepBefore⌋ , and the pool-held bucket receives the complementary remainder. This bucket split happens before REP is allocated among qualifying bidders, and each bucket keeps separate backing and retention accounting. underfundedThreshold = ⌈ attoEthRaiseCap ⋅ PRICE_PRECISION maxAttoRepBeingSold ⌉ PRICE_PRECISION = 1e18 . The ceiling tick is the bid-eligibility boundary, not an execution-price floor. Before a winner claims, its reserved auction capacity ownership remains part of the child's total capacity ownership but is not assigned to a responsible vault. It therefore provides live ETH minting headroom and affects retention utilization, but it does not accrue vault fees. Claim settlement assigns that already-counted ownership to the bidder vault and adds it to feeEligibleCapacityOwnershipAttoRep atomically without increasing total ownership again. Zero-migration full-cap REP backing units. When no vault REP migrated into the child and the auction sells every auctionable REP, the parent's inherited denominator has no migrated child-vault backing-unit attribution. The forker therefore sets auctionRepBackingUnitsPerAttoRep to PRICE_PRECISION and sets the final denominator to repAvailable * PRICE_PRECISION . Each winner's REP backing units then round-trips through backingUnitsToAttoRep to its full purchased REP, and later direct REP deposits join the same live scale. When REP remains unsold behind an inherited denominator, the forker instead derives the backing-unit rate from that residual REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"avl-tree","heading":"AVL tree","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The auction indexes bids in an AVL tree keyed by tick. The valid tick range is finite: [-524288, 524288] , or 1,048,577 possible price levels. The tree stores only submitted ticks and keeps each subtree's demand summary. Because a full tree over the range has maximum height 28 , finalize() follows bounded paths and prunes subtrees instead of looping over every possible price level.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"interactive-example","heading":"Interactive Clearing Example","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"The slider example shows the uniform-clearing path, the underfunded path, and how the same bid ladder reacts when the ETH cap, REP cap, and bid sizes change. Try a simple auction clearing run The simplified calculator visualizes both finalized branches with three fixed price levels and real-number arithmetic. The clearing section specifies the contract's exact tick and cumulative-floor behavior. ETH raise cap 12 ETH REP inventory 4 REP Alice bid near 5 ETH/REP 3 ETH Bob bid near 4 ETH/REP 4 ETH Carol bid near 3 ETH/REP 6 ETH High-price bids are considered first. If cumulative demand reaches the ETH raise cap or REP sale cap, the first binding tick sets normal uniform clearing. If neither cap is reached, finalization selects bids at or above the cap-implied qualification tick and allocates the complete REP sale cap among them at one effective price. With no qualifying bids, every bid refunds. Interactive Demand Curve The stepped curve accumulates bids from the highest limit price to the lowest. The vertical rule is available REP, the horizontal rule is the current clearing or qualification price, and colored bid points distinguish accepted demand from demand below the boundary. The chart updates from the auction controls directly above it. Mode uniform clearing near 3 ETH/REP Binding condition both caps ETH retained 12 ETH Winning ETH kept not underfunded Threshold not underfunded Approximate Alice REP 1.00 REP Approximate Bob REP 1.33 REP Approximate Carol REP 1.67 REP Total REP allocated 4 REP Refunds 1.00 ETH","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"three-auction-outcomes","heading":"Three outcomes to keep separate","keywords":[],"path":"explanation/truth-auctions.html","sectionTitle":"Explanation","summary":"Auction clearing, settlement, and weak-demand loss allocation.","text":"No auction is required when migrated REP already repairs the child pool. A funded auction means a clearing cap bound, not necessarily full ETH repair: compare accepted ETH with the repair cap. An escalation-allocation haircut can still apply when purchased REP is allocated to positive dispute-staked REP. An underfunded auction clears qualifying demand at or above the reserve threshold and refunds lower bids; it records the ETH shortfall, may apply the conditional escalation-allocation haircut, and cannot create missing ETH or duplicate REP.","title":"Truth auction economics","topic":"Economics","weight":0},{"fragment":"","heading":"","keywords":["OpenOracle","price","coordinator","callback"],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle is a source for REP/ETH price for Statoblast. Open oracle supplies a fresh REP/ETH price for solvency-sensitive operations. It does not decide market truth; local escalation games and Zoltar remain responsible for truth and branching.","title":"OpenOracle integration","topic":"Architecture","weight":1},{"fragment":"openoracle-role","heading":"Why Statoblast uses OpenOracle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Statoblast needs a REP/ETH price to enforce live backing and convert price-independent REP capacity ownership into ETH minting capacity. Price-gated operations include REP withdrawals, liquidations, and certain Escalation Game deposits. A stale or easily manipulated price could allow an unsafe withdrawal or make a healthy vault appear liquidatable. A price change updates aggregate ETH capacity arithmetically and never requires a vault scan. Statoblast therefore uses OpenOracle to obtain a fresh, economically contestable REP/ETH price. OpenOracle does not prove a price by consulting a trusted source. Instead, a reporter posts a WETH/REP position, and other participants can replace an inaccurate report through an, economically motivated correction trade. Statoblast configures WETH as token1 and REP as token2 . Keeping WETH on the exact side lets the coordinator size the report directly from ETH-denominated gas costs without first assuming a REP/ETH conversion price. When a report settles, the coordinator converts the settled token amounts into REP per ETH: lastPrice = amount2 ⋅ 10 18 amount1 The settled REP amount ( amount2 ) is scaled by the fixed price precision and divided by the settled WETH amount ( amount1 ). This raw-amount ratio is valid because the configured WETH and REP contracts both use 18 decimals. The coordinator does not normalize token decimals in the settlement callback. An accepted report becomes the coordinator's cached REP/ETH price. It is reusable only while it remains inside PRICE_VALID_FOR_SECONDS , currently five minutes. The coordinator does not meter how much operation volume uses that price during the freshness window.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"statoblast-integration","heading":"Integration architecture","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Every security pool has its own OpenOraclePriceCoordinator . The coordinator connects users, the pool, and the shared OpenOracle instance. It keeps the pool's cached REP/ETH price, pending report state, staged-operation queue, and settlement callback state. Keeping this state per pool prevents one pool's pending report or freshness window from becoming a protocol-wide queue. OpenOraclePriceCoordinator is deployed with an immutable OpenOracle address. For each origin or child security pool, SecurityPoolFactory asks PriceOracleManagerAndOperatorQueuerFactory for a fresh coordinator wired to the shared OpenOracle instance, shared WETH, and that universe's REP token. SecurityPoolDeployer passes the coordinator to the pool, after which the factory calls setSecurityPool once with the nonzero pool address. Only the configured pool may seed the coordinator's inherited lastPrice . A child pool inherits the parent's numeric price as a starting value, but its settlement timestamp remains zero, so that number is unusable until the child accepts its own report. The coordinator's validity window then determines whether the child price can be used. Statoblast queues solvency-sensitive operations behind a bounded OpenOracle REP/ETH report and executes them only with a fresh accepted price. Integration Flow The coordinator is the trust boundary between Statoblast operations and OpenOracle reports. It applies guardrails before request and staging, then again after callback before execution.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Price request lifecycle","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A liquidation or REP withdrawal begins at the pool's coordinator. REP deposits create capacity ownership directly from the selected target health factor and do not use this staged oracle path. The coordinator first validates and stages a withdrawal or liquidation, then either uses a cached price or opens an OpenOracle report. Validate and stage the operation. The coordinator checks the caller, target vault, requested amount, and validity window before any report is requested. Check the cached price. If the cached REP/ETH price is fresh, the coordinator attempts the operation immediately. Open a report when the price is stale. The refresh sponsor supplies the request bounty and initial WETH/REP position, and the coordinator creates the report as its initial reporter. Allow corrections. Other participants may replace the report by posting the position required by OpenOracle. Every valid replacement installs a new reporter and restarts the settlement window. Settle the current report. When its settlement deadline is reached without another dispute, OpenOracle settles the report and calls the coordinator. Accept or reject the price. The coordinator validates the report identity, history, economics, amounts, and settlement-time base fee before updating the cache. Execute queued operations. An accepted fresh price allows the coordinator to attempt the queued pool operations, each of which still performs its own current safety checks. The complete flow is operation requested → cached price checked → immediate execution or report opened → report corrected or left unchanged → report settled → coordinator accepts or rejects → operation safety checks → execution .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Staging rules","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Liquidation and withdrawal amounts must be nonzero. validForSeconds must be positive and no greater than five minutes. A liquidation's receiver vault must differ from its target vault. The operator may be the target; operator identity is not used as a Sybil-resistant boundary. Non-liquidation operations must target the caller's own vault. Staging is disabled after the security pool's local Escalation Game has resolved. A liquidation whose receiver equals its target is rejected with Receiver is target before the coordinator requests or consumes an oracle report. A self-receiving operator uses a zero approval ID; a distinct receiver requires a bounded approval.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Execution batches","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"If a cached price is valid, the coordinator stages the operation record and then attempts immediate execution. Liquidation gates, a stale queue-time snapshot, a zero-effect withdrawal, or a downstream pool check may still cause the staged operation to fail and be consumed. When a new report is needed, one settlement callback can automatically attempt up to MAX_PENDING_SETTLEMENT_OPERATIONS = 4 operations. Additional staged operations remain active outside the callback batch. If they have not expired and the accepted price remains fresh, they can be executed later with executeStagedOperation .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"report-lifecycle","heading":"Escalation Game deposits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Escalation Game REP deposits do not use the staged callback path. When a pool has active capacity ownership, depositToEscalationGame requires an already-fresh coordinator price and reverts when the price is stale. After previewing the accepted REP deposit, the pool uses lastPrice for the post-transfer vault and pool coverage checks.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"request-funding","heading":"Funding and report ownership","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Report funding is caller supplied. The coordinator's getRequestPriceCostAttoEth() getter calculates the request bounty from the current base fee, callback gas limit, and the coordinator's own report processing budget: requestPriceCostAttoEth = block.basefee ⋅ 4 ⋅ ( callbackGasLimit + gasConsumedOpenOracleReportPrice ) + 101 callbackGasLimit is the gas reserved for the settlement callback, gasConsumedOpenOracleReportPrice is the coordinator's own report-price callback work, and the small 101 offset keeps the forwarded bounty strictly above the computed gas product so the OpenOracle-funded reward path has a positive buffer instead of landing exactly on the boundary. If a cached price is usable, no new oracle request cost is retained and unused ETH is refunded. If a report is needed, only the first pending settlement slot retains the request cost. requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth) forwards exactly the bounty to OpenOracle and refunds any excess. The same transaction submits the initial WETH/REP position, so the refresh sponsor funds both the ETH bounty and report position up front. Both public request paths refund only a positive unused or excess amount by making a low-level native-ETH call to the caller. A contract caller must accept that callback. If it rejects the refund, the whole transaction reverts, including operation staging, immediate execution, and any report opened in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"rolling-dispute-exclusivity","heading":"Sponsor lane","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator refuses a direct requestPrice call while the cached price is still fresh, preventing a redundant report from being opened over a usable price. Once the price is stale and a report is pending, only the original pendingReportSponsor may add operations to that in-flight settlement. Those follow-up operations pay no additional join fee; other callers must wait until the report finishes. Every successful dispute replaces the current report and restarts its settlement window. The resulting liveness consequences are described under Economic tradeoffs and limits .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Report sizing and correction incentives","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle's correctness depends on profitable arbitrage correcting inaccurate prices. The coordinator sizes each report so that correcting an error at or above the target remains profitable after modeled dispute costs and OpenOracle fees, with the configured security margin. In the conservative correction direction, the true REP/ETH price is (1 + targetError) times the reported price. Before gas, correcting that report releases the fraction (targetError - fees) / (1 + targetError) of the WETH position. The sizing formula therefore multiplies the modeled gas cost of one dispute by the security multiplier and divides by that correction-profit fraction. The coordinator sizes the initial WETH position to make correction economically profitable when the price deviates enough. Every dispute resets the oracle's reporting clock, so repeated disputes can delay settlement indefinitely. The position therefore also accounts for pool open interest. The minimum initial WETH position combines three inputs: Request-block base fee: a report sized from the current block.basefee . Lineage priority-fee assumption: a separate report sized from the immutable initialReportPriorityFeeAttoEthPerGas . Pool open interest: a floor equal to one percent of the pool's current settlement collateral. The priority-fee report is added to the larger of the base-fee report and the open-interest report. The sponsor may use this minimum or request a larger initial WETH position. reportAttoEth ( gasPriceAttoEthPerGas ) = ⌈ gasPriceAttoEthPerGas ⋅ gasUnitsForOneDispute ⋅ openOracleSecurityMultiplierBps ⋅ ( percentagePrecision + targetPriceErrorForDispute ) 10000 ⋅ ( targetPriceErrorForDispute - protocolFee - reporterFee ) ⌉ openInterestReportAttoEth = ⌈ settlementCollateralAttoEth 100 ⌉ minimumToken1ReportAttoEth = reportAttoEth ( initialReportPriorityFeeAttoEthPerGas ) + max ( reportAttoEth ( block.basefee ) , openInterestReportAttoEth ) escalationHaltAttoEth = max ( ⌊ initialWethReportAttoEth ⋅ escalationHaltMultiplierBps 10000 ⌋ , openInterestReportAttoEth ) The initial-report minimum adds a report derived from the lineage's immutable initialReportPriorityFeeAttoEthPerGas to the larger of a report derived from the request block's block.basefee and one percent of current pool open interest. The multiplicative-escalation halt is the greater of the selected initial report times the configured halt multiplier and one percent of the pool's stored ETH collateral backing complete sets. The open-interest division rounds up. No REP price, expected REP price movement, or individual protected-operation notional enters the minimum WETH calculation. The immutable priority fee is selected when the origin pool is created and inherited unchanged by its child pools. Open interest contributes ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ , with OPEN_INTEREST_DIVIDER = 100 , before the priority-derived report is added. The coordinator sets initialWethReportAttoEth = max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) , uses that value as OpenOracle's currentAmount1 , and derives amount2 = ⌈initialWethReportAttoEth * proposedRepPerEthPrice / 1e18⌉ in the same transaction.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Formula inputs","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Formula input Current value Source block.basefee Current request block EVM block context initialReportPriorityFeeAttoEthPerGas Origin-pool parameter; the UI defaults to 10 gwei Immutable lineage configuration inherited by child pools settlementCollateralAttoEth Current pool open interest in ETH units Configured security pool OPEN_INTEREST_DIVIDER 100 (a 1% floor) Coordinator constant gasUnitsForOneDispute 300,000 gas ORACLE_GAS_UNITS_FOR_ONE_DISPUTE openOracleSecurityMultiplierBps 100,000 bps ( 10x ) Factory constructor; initial default OPEN_ORACLE_SECURITY_MULTIPLIER_BPS targetPriceErrorForDispute 500,000 / 10,000,000 ( 5% ) Factory constructor; initial default ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE protocolFee 1% Coordinator/OpenOracle report parameters reporterFee 0.1% Coordinator/OpenOracle report parameters","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"security-guarantee","heading":"Construction bounds and parameter effects","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Coordinator construction requires: positive dispute gas units a target error no greater than 100% an Open Oracle Security multiplier of at least 1x combined OpenOracle fees below the target error It also rejects a priority fee whose constant-derived report would consume the reserved uint128 report or escalation-halt capacity; half of that capacity remains available for the dynamic base-fee or open-interest component. The factory stores its deployment configuration, so an invalid parameter domain causes coordinator deployment to revert. Moving the target error toward the fee floor increases the required WETH sharply. Increasing the Open Oracle Security multiplier increases the required WETH linearly. A larger target error reduces the required position, but permits a wider error before the modeled correction incentive applies. The immutable priority fee is an operator-selected transaction-inclusion assumption, not a tip observed from the request block. Setting it too low weakens the modeled incentive for a disputer when prevailing priority fees are higher. Setting it too high raises the sponsor's initial WETH and REP requirements and the initial-report-derived escalation halt, and can make requests impractical. Child pools inherit the origin's value. At a 30 gwei base fee and a configured 10 gwei priority fee, the modeled gas cost is 0.012 ETH . After applying the 10x Open Oracle Security multiplier and the worst-direction five-percent correction fraction after fees, the minimum report with 100 WETH of open interest is 3.230769230769230770 WETH . With the configured 10x halt multiplier, the initial-report-derived escalation halt is 32.307692307692307700 WETH . Dynamic WETH report estimator This calculator mirrors the onchain formula. The deployment parameters and caller-selected amount are shown as adjustable inputs to make their effect explicit. For a deployed coordinator, the base fee and pool open interest can change from one request block to another. Block base fee 30 gwei Initial-report priority fee 10 gwei Pool open interest 100 WETH Gas units for one dispute 300,000 gas Open Oracle Security multiplier 10.0x Target wrong-price error 5.0% OpenOracle protocol fee 1.0% OpenOracle reporter fee 0.1% Requested initial WETH 0.00 WETH Escalation halt multiplier 10x Initial-derived halt 32.307692307692307700 WETH Open-interest halt floor 1.000000000000000000 WETH Minimum token1 report 3.230769230769230770 WETH Selected initial WETH 3.230769230769230770 WETH Escalation halt 32.307692307692307700 WETH Modeled dispute gas cost 0.012000 ETH Buffered gas target 0.120000 ETH Correction profit fraction after fees 3.7143% Safety state fees below target error","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Settlement validation, rejection, and recovery","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Low-level callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"OpenOracle invokes the coordinator through a low-level callback whose success is intentionally neither stored nor emitted. A failed callback does not automatically undo an otherwise valid OpenOracle settlement. The coordinator accepts callbacks only from the configured OpenOracle and only for the current pending report id. If those checks revert inside the low-level call, the report can still settle while the coordinator remains pending. If the settlement transaction cannot retain enough gas headroom after the callback attempt, OpenOracle reverts settlement with InvalidGasLimit . In that case, the report has not settled and coordinator recovery is not yet available.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"settlement-validation-scope","heading":"Coordinator settlement validation","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A callback that successfully enters the coordinator can still produce a rejected price. The coordinator clears the pending report id, emits PriceReportRejected , and leaves the price cache unchanged when any of the following conditions holds: the settlement base fee exceeds the request-time cap; storedGame(reportId).numReports is saturated at the uint24 maximum, reported as Counter saturated ; the final history record's WETH amount is too small to support another dispute at that record's base fee plus the configured priority fee, reported as Report uneconomic ; either settled token amount is zero; or the integer REP/ETH calculation produces a zero price. When dispute tracking is enabled, OpenOracle records each report block's base fee. If the report counter is not saturated, the coordinator selects history index numReports - 1 and checks the final WETH position against the configured security-sizing formula using that record's base fee plus the configured priority fee. It also enforces the request-time settlement base-fee cap before recording the final ratio. These checks establish only that the final WETH position meets the modeled security floor and that the callback data is structurally acceptable. They do not prove that the accepted price is externally correct, that an independent corrector exists, or that a correcting transaction will obtain inclusion.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Rejected-price cleanup","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"A rejected report does not replay its pending settlement operations. Instead, the coordinator terminally fails every operation attached to that report, removes it from the active and pending sets, and releases any liquidation-approval reservation exactly once. A caller must stage a new operation to try again with a later price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"callback-rejection-and-recovery","heading":"Recovering a settled report after callback failure","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"When OpenOracle has already settled but the coordinator remains pending because the low-level callback did not complete, anyone can call recoverSettledPendingReport . Recovery clears the pending report, sponsor, and base-fee cap; withdraws coordinator-owned OpenOracle balances; and terminally fails every pending settlement operation attached to the report. Recovery does not treat those operations as successful and does not accept the report as a new price. Each affected liquidation reservation is released during the same cleanup, so no approval quota remains locked.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Economic tradeoffs and limits","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"No accepted-price notional budget","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The coordinator does not maintain a notional exposure budget. One accepted price may authorize multiple otherwise-valid operations during its freshness window. The report position is therefore an economic correction incentive, not a bond sized to the exact aggregate payoff of every operation that may use the price.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Unbounded funded delay","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"The stale-price refresh sponsor retains the staging lane while its report is pending. A timely valid dispute replaces the reporter and restarts OpenOracle's settlement clock. Repeated disputes can therefore delay settlement. Each extension requires a transaction and the contract-specified replacement position. Under ordinary non-dust parameters, fees and required position size also accumulate. There is intentionally no absolute coordinator deadline. The protocol does not claim liveness against a participant with unbounded capital that is willing to keep funding valid replacements. This availability model is separate from the dynamic initial WETH sizing rule and is not a bounded-liveness invariant.","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"intentional-economic-tradeoffs","heading":"Dispute escalation after the halt","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"In this integration, WETH is the escalation variable. Before escalationHalt , OpenOracle requires min(⌊oldAmount1 × multiplier / 100⌋, escalationHalt) . With the configured multiplier of 115 , integer flooring leaves token1 amounts from one through six attoETH unchanged; seven attoETH is the first amount that grows. At or above the halt, each replacement instead requires oldAmount1 + 1 , adding one attoETH of WETH. The selected initial amount is max(minimumToken1ReportAttoEth(), requestedInitialAttoWeth) . One escalation-halt candidate is that amount multiplied by the configured halt multiplier. The other is ⌈settlementCollateralAttoEth / OPEN_INTEREST_DIVIDER⌉ . The actual halt is the greater of the two, as shown in Dynamic Report and Escalation Threshold .","title":"OpenOracle integration","topic":"Architecture","weight":0},{"fragment":"parameters","heading":"OpenOracle parameters used","keywords":[],"path":"explanation/open-oracle.html","sectionTitle":"Explanation","summary":"REP/ETH price oracle for Statoblast.","text":"Use the coordinator reference for source constants and parameter definitions, and deployment verification for deployed getter values. The Statoblast whitepaper links to both. Parameter Current integration value Use in Statoblast token1Address / token2Address WETH / REP Defines the REP/ETH price pair used for security-pool solvency checks. minimumToken1ReportAttoEth() Dynamic; see Dynamic Minimum WETH Report and the estimator . Adds the priority-fee report to the larger base-fee or open-interest correction floor. initialReportPriorityFeeAttoEthPerGas Configured per origin pool; UI default 10 gwei Provides an immutable inclusion-fee component that every child pool inherits. OPEN_INTEREST_DIVIDER 100 Makes one percent of settlementCollateralAttoEth the open-interest-derived initial-report and escalationHalt floor. OpenOracle initial currentAmount1 The greater of minimumToken1ReportAttoEth() and the sponsor's requestedInitialAttoWeth The sponsor may request and fund more than the minimum; the coordinator submits the selected amount as currentAmount1 . escalationHalt The greater of 10x the selected initial currentAmount1 and 1% of pool open interest Raises the multiplicative-escalation threshold with pool exposure and stops multiplicative report-size escalation there; later disputes add one attoETH of WETH at a time. settlerRewardAttoEth Dynamic; see Oracle Request Cost . The full request bounty is assigned to the account that settles the report and triggers the callback. settlementTime 480 seconds (8 minutes) Sets the timestamp-based report settlement delay. The 40 * 12 derivation assumes twelve-second blocks, but the configured OpenOracle timeType uses seconds. Staged operations expire after this delay plus their operation-specific validity window. disputeDelay 0 Allows disputes immediately after each report and strictly before its settlement deadline. At the deadline, settlement is valid and a dispute is too late. callbackGasLimit Derived from settlement gas and the maximum callback batch; see Callback Gas Limit . Sets the callback gas limit for up to MAX_PENDING_SETTLEMENT_OPERATIONS staged operations; settlement still needs enough surrounding gas to satisfy OpenOracle's callback headroom check. feePercentage / protocolFee 10000 / 100000 Configures OpenOracle reporter and protocol fee accounting, and creates the fee boundary behind the honestDisputeBarrierFraction dispute barrier in the attack model. protocolFeeRecipient 0x000000000000000000000000000000000000dEaD Receives OpenOracle protocol fees for the coordinator-created report instance. multiplier 115 Before escalationHalt , requires WETH token1 equal to ⌊prior amount × 1.15⌋ , capped at the halt. Integer flooring leaves prior amounts of one through six attoETH unchanged. At or above the halt, each dispute requires one more attoETH of WETH. timeType / trackDisputes true / true Uses timestamps. Coordinator reports always enable dispute history so settlement can validate the final report's recorded base fee. Settlement rejects the price if the uint24 report counter is saturated. callbackContract OpenOraclePriceCoordinator Routes settled amounts back into Statoblast's price cache and staged-operation executor.","title":"OpenOracle integration","topic":"Architecture","weight":0}] diff --git a/docs/data/open-oracle-coordinator.json b/docs/data/open-oracle-coordinator.json index 4ced59909..0c3406073 100644 --- a/docs/data/open-oracle-coordinator.json +++ b/docs/data/open-oracle-coordinator.json @@ -34,6 +34,11 @@ "preconditions": ["stale-cache", "pending-report", "msg.sender == pendingReportSponsor"], "outcomes": ["StagedOperationQueued", "operation joins the pending settlement list or remains active according to the bounded batch rules", "pending report remains pending"] }, + { + "trigger": "stageAndRequestOperationBounty", + "preconditions": ["caller is the configured operation bounty board", "no bounty operation is already being staged", "the bounty operator is the pending report sponsor when a report exists"], + "outcomes": ["StagedOperationQueued", "the operation records its bounty id", "fresh-cache execution reports success or failure to the bounty board", "stale-cache execution attaches to the pending callback batch or remains active according to the bounded batch rules"] + }, { "trigger": "openOracleCallback", "preconditions": ["pending-report"], @@ -60,7 +65,7 @@ "outcomes": ["ExecutedStagedOperation failure", "operation is consumed without requiring a valid price", "delegated-liquidation reservation is released"] } ], - "events": ["SecurityPoolSet", "RepEthPriceSet", "PriceRequested", "PriceReported", "PriceReportRejected", "PendingReportRecovered", "LiquidationRouteStaged", "StagedOperationQueued", "ExecutedStagedOperation", "CoordinatorStateCheckpoint"], + "events": ["SecurityPoolSet", "OperationBountyBoardSet", "RepEthPriceSet", "PriceRequested", "PriceReported", "PriceReportRejected", "PendingReportRecovered", "LiquidationRouteStaged", "StagedOperationQueued", "ExecutedStagedOperation", "CoordinatorStateCheckpoint"], "checkpointReasons": { "0": "SecurityPoolSetup", "1": "PriceSeeded", "2": "PriceRequested", "3": "PriceReported", "4": "PriceRejected", "5": "PendingReportRecovered", "6": "OperationQueued", "7": "OperationExecuted" }, "enumValues": { "OperationType": { "0": "Liquidation", "1": "WithdrawRep" }, @@ -68,6 +73,7 @@ }, "eventDeclarations": { "SecurityPoolSet": "SecurityPoolSet(address indexed securityPool)", + "OperationBountyBoardSet": "OperationBountyBoardSet(address indexed operationBountyBoard)", "RepEthPriceSet": "RepEthPriceSet(uint256 price)", "PriceRequested": "PriceRequested(uint256 indexed reportId, uint256 pendingReportMaxSettlementBaseFeeAttoEthPerGas)", "PriceReportRejected": "PriceReportRejected(uint256 indexed reportId, string reason, uint256 pendingReportId, uint256 pendingReportMaxSettlementBaseFeeAttoEthPerGas, uint256 lastPrice, uint256 lastSettlementTimestamp)", @@ -114,6 +120,7 @@ "openOracle": "view", "openOracleCallback": "nonpayable", "openOracleSecurityMultiplierBps": "view", + "operationBountyBoard": "view", "pendingOperationSlotId": "view", "liquidationApprovalRegistry": "view", "pendingReportId": "view", @@ -129,8 +136,10 @@ "securityPool": "view", "setRepEthPrice": "nonpayable", "setLiquidationApprovalRegistry": "nonpayable", + "setOperationBountyBoard": "nonpayable", "setSecurityPool": "nonpayable", "settlementTime": "view", + "stageAndRequestOperationBounty": "payable", "stagedOperationCounter": "view", "stagedOperations": "view", "targetPriceErrorForDispute": "view", @@ -150,8 +159,10 @@ "requestPriceIfNeededAndStageOperation": "requestPriceIfNeededAndStageOperation(uint8,address,uint256,uint256,uint256,uint256)", "requestPriceIfNeededAndStageLiquidation": "requestPriceIfNeededAndStageLiquidation(address,address,uint256,bytes32,uint256,uint256,uint256)", "setLiquidationApprovalRegistry": "setLiquidationApprovalRegistry(address)", + "setOperationBountyBoard": "setOperationBountyBoard(address)", "setRepEthPrice": "setRepEthPrice(uint256)", "setSecurityPool": "setSecurityPool(address)", + "stageAndRequestOperationBounty": "stageAndRequestOperationBounty(uint256,address,address,uint8,address,uint256,uint256,uint256,uint256)", "stagedOperations": "stagedOperations(uint256)" }, "functionSignatureNote": "The functions map is the complete ABI name-to-mutability inventory. Signatures are listed for parameterized and tuple-returning entry points; zero-input and scalar getters use their Solidity names directly.", diff --git a/docs/mainnet-deployment-addresses.json b/docs/mainnet-deployment-addresses.json index 5b27b9d13..25369dc90 100644 --- a/docs/mainnet-deployment-addresses.json +++ b/docs/mainnet-deployment-addresses.json @@ -23,7 +23,7 @@ { "id": "deploymentStatusOracle", "label": "Deployment Status Oracle", - "address": "0x6bBD9e11C3Ef13dad9D556d1f8bf3726DaE76f79" + "address": "0xb36Ac9fCf3C1552426f601415a477aBe20d587a2" }, { "id": "multicall3", @@ -68,7 +68,7 @@ { "id": "priceOracleManagerAndOperatorQueuerFactory", "label": "OpenOracle Price Coordinator Factory", - "address": "0xd8A1b5ed8E0f7c4D5c05a2D6714be6894B764091" + "address": "0xaac5cE13B13f27864671b1B3b39169C23b1aEE41" }, { "id": "securityPoolForker", @@ -88,7 +88,7 @@ { "id": "securityPoolFactory", "label": "Security Pool Factory", - "address": "0xC2bfaaF003dA832edDb3f0945D7FcdF47De4F5a6" + "address": "0x46Ae334a0d48F6929e98f3Ee0603a2953ff21D98" } ], "derivedContracts": [ diff --git a/docs/reference/contracts.html b/docs/reference/contracts.html index 7f5eb0eaf..135624ba5 100644 --- a/docs/reference/contracts.html +++ b/docs/reference/contracts.html @@ -26,6 +26,7 @@

Contract interactions

  • EscalationGame
  • LiquidationApprovalRegistry
  • OpenOraclePriceCoordinator
  • +
  • OpenOracleOperationBountyBoard
  • ShareToken
  • UniformPriceDualCapBatchAuction
  • @@ -769,11 +770,11 @@

    LiquidationApprovalRegistry

    OpenOraclePriceCoordinator

    Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup. Source

    -

    Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS, OPEN_INTEREST_DIVIDER, reputationToken, securityPool, openOracle, weth, liquidationApprovalRegistry, gasConsumedOpenOracleReportPrice, gasConsumedSettlement, gasUnitsForOneDispute, initialReportPriorityFeeAttoEthPerGas, targetPriceErrorForDispute, openOracleSecurityMultiplierBps, settlementTime, disputeDelay, protocolFee, feePercentage, multiplier, timeType, trackDisputes, protocolFeeRecipient, escalationHaltMultiplierBps, maxSettlementBaseFeeMultiplierBps, and minLiquidationPriceDistanceBps. Current report and operation getters are pendingReportId, pendingReportSponsor, pendingOperationSlotId, lastSettlementTimestamp, lastPrice, pendingReportMaxSettlementBaseFeeAttoEthPerGas, stagedOperationCounter, and stagedOperations. Use isPriceValid, minimumToken1ReportAttoEth, getRequestPriceCostAttoEth, getQueuedOperationCostAttoEth, getSettlementCallbackGasLimit, getPendingOperationSlot, getActiveStagedOperationCount, getActiveStagedOperations, getPendingSettlementOperationCount, and getPendingSettlementOperationIds for derived or paged state.

    +

    Read surface: Configuration getters are MAX_PENDING_SETTLEMENT_OPERATIONS, OPEN_INTEREST_DIVIDER, reputationToken, securityPool, openOracle, weth, liquidationApprovalRegistry, operationBountyBoard, gasConsumedOpenOracleReportPrice, gasConsumedSettlement, gasUnitsForOneDispute, initialReportPriorityFeeAttoEthPerGas, targetPriceErrorForDispute, openOracleSecurityMultiplierBps, settlementTime, disputeDelay, protocolFee, feePercentage, multiplier, timeType, trackDisputes, protocolFeeRecipient, escalationHaltMultiplierBps, maxSettlementBaseFeeMultiplierBps, and minLiquidationPriceDistanceBps. Current report and operation getters are pendingReportId, pendingReportSponsor, pendingOperationSlotId, lastSettlementTimestamp, lastPrice, pendingReportMaxSettlementBaseFeeAttoEthPerGas, stagedOperationCounter, and stagedOperations. Use isPriceValid, minimumToken1ReportAttoEth, getRequestPriceCostAttoEth, getQueuedOperationCostAttoEth, getSettlementCallbackGasLimit, getPendingOperationSlot, getActiveStagedOperationCount, getActiveStagedOperations, getPendingSettlementOperationCount, and getPendingSettlementOperationIds for derived or paged state.

    Report and staged-operation liveness depends on A16 timely inclusion, A17 corrector capability, A18 independent correction incentive, A19 observable correctable price, and A06 lifecycle executors. When lastPrice is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path.

    - - + + @@ -806,6 +807,13 @@

    OpenOraclePriceCoordinator

    + + + + + + + @@ -834,6 +842,13 @@

    OpenOraclePriceCoordinator

    + + + + + + + @@ -857,6 +872,74 @@

    OpenOraclePriceCoordinator

    Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position. PriceRequested and CoordinatorStateCheckpoint
    stageAndRequestOperationBounty(bountyId, sponsor, creator, operation, targetVault, ...)The coordinator’s configured operationBountyBoard onlyNo nested bounty staging call is active; the creator and target satisfy the normal operation route; and a stale price has room in the four-operation pending settlement batch.Stages a self-receiving creator operation while assigning initial-report token funding, report sponsorship, and unused ETH refunds to the accepting operator. The operation result is returned to the board before the coordinator emits its execution event.StagedOperationQueued, possibly PriceRequested, then ExecutedStagedOperation; authoritative CoordinatorStateCheckpoint records
    executeStagedOperation(operationId) Anyone A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation. PriceReported or PriceReportRejected; operation execution events; authoritative CoordinatorStateCheckpoint records
    setOperationBountyBoard(board)Coordinator deployment factory onlyBoard has deployed code and no board was previously installed.Binds the coordinator-local operation bounty board once.OperationBountyBoardSet
    setLiquidationApprovalRegistry(registry) Coordinator deployment factory only
    +

    OpenOracleOperationBountyBoard

    +

    Escrows REP or WETH rewards for creator-defined coordinator operations and pays the operator only after successful staged execution. Source

    +

    Read surface: Use coordinator, reputationToken, and weth to identify the bound coordinator and token contracts. nextOperationBountyId, operationBounties, and operationExecutionStatuses expose bounty identity, escrow terms, assignment, staged operation and report IDs, terminal state, and execution outcome. getOperationBounties pages forward from an explicit ID. The co-located deployment factory exposes its immutable owner and shared implementation.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TransactionCallerMain prerequisitesState or asset effectPrimary signals
    initialize(coordinator, reputationToken, weth)Anyone while uninitialized; the canonical factory initializes new clones atomicallyThe proxy is not initialized and every supplied address is nonzero. The shared implementation is constructor-locked against initialization.Binds the proxy to its coordinator and token pair and starts bounty IDs at one.No event; the factory completes initialization before returning the proxy.
    postOperationBounty(operation, targetVault, amount, validForSeconds, rewardToken, rewardAmount, acceptanceDeadline, minimumInitialAttoWeth, maximumInitialAttoWeth)Any creator with sufficient REP or WETH allowance and balanceamount is positive attoREP for a withdrawal or positive maximum requested debt in attoETH for a liquidation. The reward is positive coordinator REP or WETH; its token amount uses that token's 18-decimal base unit. The acceptance deadline is strictly in the future; execution validity is from 1 second through 5 minutes. A zero maximumInitialAttoWeth means no maximum; otherwise the minimum cannot exceed it. Withdrawals target the creator and liquidations target another vault.Creates an open bounty and escrows the full reward on the board.OperationBountyPosted
    acceptOperationBounty(bountyId, proposedRepPerEthPrice, requestedInitialAttoWeth) with report funding when staleAny operator at or before the acceptance deadlineBounty is open; its initial WETH bounds admit the new or existing report; the settlement batch has room; and an existing pending report is sponsored by this operator.Assigns the bounty, stages its operation, and either executes against a fresh price or attaches to a pending report. When a report must be opened, the operator supplies its REP, WETH, and ETH funding and becomes the report sponsor.OperationBountyAccepted; coordinator staging and reporting events
    claimOperationBounty(bountyId)Assigned operator onlyBounty remains assigned and its recorded execution status is succeeded.Marks the bounty paid and transfers its escrowed reward to the operator.OperationBountyClaimed
    refundOperationBounty(bountyId)Bounty creator onlyBounty is open, has failed, or remains assigned and pending strictly after queuedAt + settlementTime + validForSeconds; equality is still active. That fixed cancellation deadline does not move when disputes extend report settlement. Successful execution cannot be refunded.Cancels an open bounty immediately, refunds a failed bounty, or expires an overdue staged operation before refunding its full escrow.OperationBountyRefunded; overdue assigned bounties also produce ExecutedStagedOperation
    recordOperationResult(bountyId, success)Bound OpenOraclePriceCoordinator onlyBounty remains assigned.Records the assigned bounty as succeeded or failed so its escrow can be claimed or refunded.The coordinator emits ExecutedStagedOperation with the same result and any failure detail.
    deploy(coordinator, reputationToken, weth, salt)Owning price-coordinator factory onlyCaller equals the factory owner; the derived CREATE2 address is unused.Lazily deploys one shared board implementation, then deterministically deploys and initializes a minimal proxy for the coordinator and token pair.No dedicated event; the coordinator subsequently emits OperationBountyBoardSet.

    ShareToken

    Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches. Source

    Read surface: Base and relationship getters are name, symbol, zoltar, canonicalPoolByUniverse, _balances, _supplies, and _operatorApprovals. Standard ERC-1155 reads are supportsInterface, balanceOf, totalSupply, balanceOfBatch, and isApprovedForAll; protocol-specific reads are isAuthorized, totalSupplyForOutcome, maximumOutcomeSupply, balanceOfOutcome, balanceOfShares, getMigratedShareAmountAttoShares, getTokenId, getTokenIds, and unpackTokenId.

    diff --git a/docs/reference/operator-guardrails.html b/docs/reference/operator-guardrails.html index a0b58de76..98aeac748 100644 --- a/docs/reference/operator-guardrails.html +++ b/docs/reference/operator-guardrails.html @@ -856,6 +856,7 @@

    SecurityPoolUtils read surface

    Protocol interfaces IEscalationGame.sol, IOperationBountyResultReceiver.sol, ISecurityPool.sol, ISecurityPoolForker.sol, ISecurityPoolForkerChildEscalationGameInitializer.sol row.startsWith('`acceptOperationBounty(bountyId, proposedRepPerEthPrice, requestedInitialAttoWeth)` with report funding when stale\t')) + assert.ok(acceptOperationBountyRow, 'Expected exactly one generated acceptOperationBounty interaction row') + const refundOperationBountyRow = getContractInteractionRow('refundOperationBounty(bountyId)') const activateForkModeRow = getContractInteractionRow('activateForkMode()') const initiateSecurityPoolForkRow = getContractInteractionRow('initiateSecurityPoolFork(securityPool)') const ownEscalationForkRow = getContractInteractionRow('forkZoltarWithOwnEscalationGame(securityPool)') @@ -510,6 +516,17 @@ function assertContractInteractionDistinctions(): void { const recordForkedEscrowRow = getContractInteractionRow('recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep)') const startTruthAuctionRow = getContractInteractionRow('startTruthAuction(securityPool)') const finalizeTruthAuctionRow = getContractInteractionRow('finalizeTruthAuction(securityPool)') + const initializeOperationBountyBoard = operationBountyBoard.match(/function initialize\([\s\S]*?\n\t\}/)?.[0] + assert.ok(initializeOperationBountyBoard, 'OpenOracleOperationBountyBoard.sol must define initialize') + assert.match(initializeOperationBountyBoard, /require\(!initialized, 'Operation bounty board is already initialized'\)/) + assert.doesNotMatch(initializeOperationBountyBoard, /msg\.sender/) + assert.match(initializeOperationBountyBoardRow, /Anyone while uninitialized; the canonical factory initializes new clones atomically[\s\S]*implementation is constructor-locked/) + assert.match(operationBountyBoard, /maximumInitialAttoWeth == 0 \|\| minimumInitialAttoWeth <= maximumInitialAttoWeth/) + assert.match(postOperationBountyRow, /positive attoREP for a withdrawal[\s\S]*positive maximum requested debt in attoETH for a liquidation[\s\S]*zero `maximumInitialAttoWeth` means no maximum/) + assert.match(operationBountyBoard, /block\.timestamp <= bounty\.acceptanceDeadline/) + assert.match(acceptOperationBountyRow, /at or before the acceptance deadline/) + assert.match(priceCoordinator, /block\.timestamp > stagedOperation\.queuedAt \+ settlementTime \+ stagedOperation\.validForSeconds, 'Staged operation active'/) + assert.match(refundOperationBountyRow, /strictly after `queuedAt \+ settlementTime \+ validForSeconds`; equality is still active[\s\S]*does not move when disputes extend report settlement/) assert.match(contractReferenceGenerator, /interaction\.declarations\.length, 1,[\s\S]*interaction rows must describe exactly one entrypoint name; split materially different guards, effects, and signals into separate rows/, 'generated interaction rows must remain limited to one entrypoint name') assert.match(invariantsHtml, /SHARE-04<\/code>[\s\S]*remaining economic claim[\s\S]*source entitlements/) assert.match(invariantsHtml, /id="fork-10"[\s\S]*FORK-10<\/code>[\s\S]*mints only the unmaterialized balance/) @@ -789,8 +806,8 @@ function assertContractInteractionDistinctions(): void { assert.match(contractInteractionReference, /createCompleteSet\(\)[\s\S]*contract trader accepts `onERC1155BatchReceived`[\s\S]*Callback rejection rolls back the ETH, pool accounting, events, and share mint/) assert.match(contractInteractionReference, /migrate\(fromId, targetOutcomeIndexes\)[\s\S]*contract holder accepts `onERC1155Received` for every target mint[\s\S]*one ERC-1155 mint `TransferSingle` and `Migrate` per materialized target on successful callbacks/) assert.match(contractInteractionReference, /mintCompleteSets\(universeId, account, amountAttoShares\)[\s\S]*contract account accepts `onERC1155BatchReceived`[\s\S]*Rejection rolls back the mint and the authorized pool's surrounding transaction/) - assert.match(priceCoordinator, /function requestPrice\([\s\S]*if \(excess > 0\) \{[\s\S]*payable\(msg\.sender\)\.call\{\s*value:\s*excess\s*\}\(''\)[\s\S]*require\(sent, 'Oracle coordinator failed to refund excess ETH bounty'\)/) - assert.match(priceCoordinator, /function requestPriceIfNeededAndStageOperation\([\s\S]*if \(refund > 0\) \{[\s\S]*payable\(msg\.sender\)\.call\{\s*value:\s*refund\s*\}\(''\)[\s\S]*require\(sent, 'Oracle coordinator failed to return unused ETH'\)/) + assert.match(priceCoordinator, /function requestPrice\([\s\S]*if \(excess > 0\) \{[\s\S]*payable\(msg\.sender\)\.call\{\s*value:\s*excess\s*\}\(''\)[\s\S]*require\(sent, ETH_REFUND_FAILED\)/) + assert.match(priceCoordinator, /function requestPriceIfNeededAndStageOperation\([\s\S]*if \(refund > 0\) \{[\s\S]*payable\(reportSponsor\)\.call\{\s*value:\s*refund\s*\}\(''\)[\s\S]*require\(sent, ETH_REFUND_FAILED\)/) assert.match(contractInteractionReference, /requestPriceIfNeededAndStageOperation\(\.\.\.\)[\s\S]*caller must accept any positive unused-ETH refund[\s\S]*rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report/) assert.match(contractInteractionReference, /requestPrice\(proposedRepPerEthPrice, requestedInitialAttoWeth\)[\s\S]*caller must accept any positive excess-ETH refund[\s\S]*Callback rejection rolls back the report and initial position/) assert.match(operatorReference, /Immediate execution[\s\S]*canonical refund warning[\s\S]*open-oracle\.html#refund-callback/) @@ -823,7 +840,7 @@ function assertContractInteractionDistinctions(): void { assert.match(priceCoordinator, /finalReportDisputeStatus == FINAL_REPORT_COUNTER_SATURATED\s*\? 'Counter saturated'\s*: 'Report uneconomic'/) assert.match(priceCoordinator, /_rejectReportAndPendingOperations\(reportId, 'Empty oracle settlement'\);\s*return;/) assert.match(priceCoordinator, /_rejectReportAndPendingOperations\(reportId, 'Oracle price is zero'\);\s*return;/) - assert.match(priceCoordinator, /require\(\s*msg\.sender == pendingReportSponsor,\s*'Only the pending report sponsor can queue more operations until settlement'/) + assert.match(priceCoordinator, /require\(\s*reportSponsor == pendingReportSponsor,\s*'Only the pending report sponsor can queue more operations until settlement'/) assert.match(priceCoordinator, /bool shouldRequestPrice = pendingReportId == 0 && pendingSettlementOperationIds\.length == 0/) assert.match(priceCoordinator, /if \(shouldRequestPrice && isPendingSettlementOperationId\)/) assert.match(escalationGameForker, /if \(child\.systemState\(\) != SystemState\.ForkMigration\) revert\(\)/) @@ -895,7 +912,7 @@ function assertContractInteractionDistinctions(): void { assert.match(operatorReference, /reserved OpenOracle `uint128` report and escalation-halt capacity/) assert.match(openOracleIntegration, /half[\s\S]*capacity remains available for the dynamic base-fee or open-interest[\s\S]*component/) assert.match(priceCoordinator, /maximumPriorityFeeReportAttoEth \/= 2/) - assert.match(priceCoordinator, /'Initial report priority fee exceeds OpenOracle limits'/) + assert.match(priceCoordinator, /_initialReportPriorityFeeAttoEthPerGas <= maximumInitialReportPriorityFeeAttoEthPerGas, INVALID_ORACLE_CONFIGURATION/) assert.match(contractInteractionReference, /deployChildSecurityPool\(parent, shareToken[\s\S]*inherits `initialReportPriorityFeeAttoEthPerGas` from the parent coordinator/) for (const emitterFunction of ['emitPoolAccountingCheckpoint', 'emitVaultAccountingCheckpoint']) { assert.match(securityPoolEventEmitter, new RegExp(`function ${emitterFunction}\\([\\s\\S]*?\\) external payable`), `${emitterFunction} must remain externally payable for delegatecall flows`) diff --git a/scripts/deploy-testnet.mts b/scripts/deploy-testnet.mts index 1bb4366c7..483057950 100644 --- a/scripts/deploy-testnet.mts +++ b/scripts/deploy-testnet.mts @@ -78,11 +78,12 @@ const EXPECTED_BOOTSTRAP_DESCENDANT_RUNTIME_CODE_HASHES: Readonly { uniswap.addresses.uniswapV4QuoterAddress, ] for (const address of requiredAddresses) expect(addressSet.has(address)).toBe(true) - expect(Object.keys(bootstrapDescendants)).toHaveLength(12) - expect(new Set(Object.values(bootstrapDescendants)).size).toBe(12) + expect(Object.keys(bootstrapDescendants)).toHaveLength(13) + expect(new Set(Object.values(bootstrapDescendants)).size).toBe(13) for (const address of Object.values(bootstrapDescendants)) expect(addressSet.has(address)).toBe(false) expect(bootstrapDescendants.escalationGameProofVerifier).toBe(infrastructure.escalationGameProofVerifier) expect(bootstrapDescendants.liquidationApprovalRegistryDeployer).toBe(getCreateAddress({ from: infrastructure.priceOracleManagerAndOperatorQueuerFactory, nonce: 1n })) expect(bootstrapDescendants.liquidationApprovalRegistryImplementation).toBe(getCreateAddress({ from: bootstrapDescendants.liquidationApprovalRegistryDeployer, nonce: 1n })) + expect(bootstrapDescendants.operationBountyBoardFactory).toBe(getCreateAddress({ from: infrastructure.priceOracleManagerAndOperatorQueuerFactory, nonce: 3n })) expect(bootstrapDescendants.priceCoordinatorDeploymentWorker).toBe(getCreateAddress({ from: infrastructure.priceOracleManagerAndOperatorQueuerFactory, nonce: 2n })) expect(plan.some(step => step.id === 'escalationGameFactory')).toBe(true) expect(plan).toHaveLength(24) diff --git a/scripts/generate-contract-interaction-reference.mts b/scripts/generate-contract-interaction-reference.mts index 6beea40c8..327224cb4 100644 --- a/scripts/generate-contract-interaction-reference.mts +++ b/scripts/generate-contract-interaction-reference.mts @@ -46,7 +46,7 @@ type AssemblyDelegateCall = { } const outputPath = 'docs/reference/contracts.html' -const expectedProductionSoliditySourceFingerprint = '45012935fca2322bf64475cec4fa1f7ae1ed74c6eae935c1eb4978575a0f502c' +const expectedProductionSoliditySourceFingerprint = 'f2128388d2ac3bdd0ae6b83a2b85c807500710d1f981085322f6f92dd2a38927' const eventSourceByName: Record = { VaultBadDebtMigrated: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', @@ -103,6 +103,11 @@ const eventSourceByName: Record = { MigrationRepSplit: 'solidity/contracts/Zoltar.sol', Mint: 'solidity/contracts/ReputationToken.sol', NonDecisionReached: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + OperationBountyAccepted: 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol', + OperationBountyBoardSet: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + OperationBountyClaimed: 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol', + OperationBountyPosted: 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol', + OperationBountyRefunded: 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol', TotalRepBackingUnitsSet: 'solidity/contracts/peripherals/SecurityPool.sol', VaultTargetHealthFactorSet: 'solidity/contracts/peripherals/SecurityPool.sol', PendingEthRefundWithdrawn: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', @@ -336,7 +341,7 @@ const assemblyDelegateCalls: AssemblyDelegateCall[] = [ }, ] -const referencedEventAbiFingerprint = 'f73cedceb07d7243fbd91f9e0bdd7c5f19a886ba391f5e8bcd115408d9489206' +const referencedEventAbiFingerprint = '5460fde219ab46fb9f87e20f4b95d8a7d48da5751856ba815b5f495ba2c5251b' const entrypointSignaturesBySource: Record> = { 'solidity/contracts/ERC20.sol': { @@ -396,9 +401,20 @@ const entrypointSignaturesBySource: Record> = { requestPrice: ['public(uint256,uint256)'], requestPriceIfNeededAndStageLiquidation: ['external(address,address,uint256,bytes32,uint256,uint256,uint256)'], requestPriceIfNeededAndStageOperation: ['public(OperationType,address,uint256,uint256,uint256,uint256)'], + setOperationBountyBoard: ['external(address)'], setLiquidationApprovalRegistry: ['external(LiquidationApprovalRegistry)'], setRepEthPrice: ['public(uint256)'], setSecurityPool: ['public(ISecurityPool)'], + stageAndRequestOperationBounty: ['external(uint256,address,address,OperationType,address,uint256,uint256,uint256,uint256)'], + }, + 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol': { + acceptOperationBounty: ['external(uint256,uint256,uint256)'], + claimOperationBounty: ['external(uint256)'], + deploy: ['external(OpenOraclePriceCoordinator,ReputationToken,IWeth9,bytes32)'], + initialize: ['external(OpenOraclePriceCoordinator,ReputationToken,IWeth9)'], + postOperationBounty: ['external(OperationType,address,uint256,uint256,address,uint256,uint256,uint256,uint256)'], + recordOperationResult: ['external(uint256,bool)'], + refundOperationBounty: ['external(uint256)'], }, 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': { consume: ['external(uint256,uint256)'], @@ -493,7 +509,8 @@ const stateChangingAbiFingerprintBySource: Record = { 'solidity/contracts/peripherals/EscalationGameSettlement.sol': '73f9aad63165cacbff5bd02fd57a6b5a3f73737545018ecdf152c46f905c8c32', 'solidity/contracts/peripherals/EscalationGameState.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 'solidity/contracts/peripherals/EscalationGameStorage.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': '2a27b7ed5407ac8067de39d67bbe84902f4c1c36ab070eeaeb99375db6f8b8e1', + 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': '2cf0bda2841046d295f4ba60a5a6d70393c5f678b1ab0453f97bb8a28e408820', + 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol': 'cb45855931f4c1e5c15071f42d1932bcff166dc7e876a063ea70185ee339ebb4', 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': '986a20fc0e4cfe0898be8fc91c6b911b93ef0ae1086d4cb1142a93c66f315684', 'solidity/contracts/peripherals/SecurityPool.sol': 'a92b712be45bffcf5096ec978a2ddeed79f8c206958806221d662af785b90432', 'solidity/contracts/peripherals/SecurityPoolForker.sol': '282c464a68623405a6241816a1c5fcef4b80e9db39e42e89d77177d8a4f10eae', @@ -508,6 +525,7 @@ const stateChangingAbiFingerprintBySource: Record = { const readDeclarationExclusionsBySource: Record = { 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol': ['securityPool'], 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': ['storedGame', 'disputeHistory'], + 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol': ['storedGame'], 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': ['securityPool'], 'solidity/contracts/peripherals/SecurityPool.sol': ['eventEmitter', 'factory'], 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol': [], @@ -1461,12 +1479,12 @@ const contractReferences: ContractReference[] = [ ], }, { - compiledAbiFingerprint: '60cd10890a685efe179e17e93a783c660542bd6368f86fed87f46de03695b243', + compiledAbiFingerprint: '037b3c515414b5930e0e8484041e3933b44b55a3c46ddd4acbbf3eb183ad4bd2', name: 'OpenOraclePriceCoordinator', purpose: 'Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup.', - readAbiFingerprint: '288a73d13de5a0f593226105eb11eb177bf085ac2ee708a31645c3d7c4eb7237', + readAbiFingerprint: '41f3d91e7c93867012ef0372c603c6305208524cf8df9149073c23b9f1bd43cc', readSurface: - 'Configuration getters are `MAX_PENDING_SETTLEMENT_OPERATIONS`, `OPEN_INTEREST_DIVIDER`, `reputationToken`, `securityPool`, `openOracle`, `weth`, `liquidationApprovalRegistry`, `gasConsumedOpenOracleReportPrice`, `gasConsumedSettlement`, `gasUnitsForOneDispute`, `initialReportPriorityFeeAttoEthPerGas`, `targetPriceErrorForDispute`, `openOracleSecurityMultiplierBps`, `settlementTime`, `disputeDelay`, `protocolFee`, `feePercentage`, `multiplier`, `timeType`, `trackDisputes`, `protocolFeeRecipient`, `escalationHaltMultiplierBps`, `maxSettlementBaseFeeMultiplierBps`, and `minLiquidationPriceDistanceBps`. Current report and operation getters are `pendingReportId`, `pendingReportSponsor`, `pendingOperationSlotId`, `lastSettlementTimestamp`, `lastPrice`, `pendingReportMaxSettlementBaseFeeAttoEthPerGas`, `stagedOperationCounter`, and `stagedOperations`. Use `isPriceValid`, `minimumToken1ReportAttoEth`, `getRequestPriceCostAttoEth`, `getQueuedOperationCostAttoEth`, `getSettlementCallbackGasLimit`, `getPendingOperationSlot`, `getActiveStagedOperationCount`, `getActiveStagedOperations`, `getPendingSettlementOperationCount`, and `getPendingSettlementOperationIds` for derived or paged state.', + 'Configuration getters are `MAX_PENDING_SETTLEMENT_OPERATIONS`, `OPEN_INTEREST_DIVIDER`, `reputationToken`, `securityPool`, `openOracle`, `weth`, `liquidationApprovalRegistry`, `operationBountyBoard`, `gasConsumedOpenOracleReportPrice`, `gasConsumedSettlement`, `gasUnitsForOneDispute`, `initialReportPriorityFeeAttoEthPerGas`, `targetPriceErrorForDispute`, `openOracleSecurityMultiplierBps`, `settlementTime`, `disputeDelay`, `protocolFee`, `feePercentage`, `multiplier`, `timeType`, `trackDisputes`, `protocolFeeRecipient`, `escalationHaltMultiplierBps`, `maxSettlementBaseFeeMultiplierBps`, and `minLiquidationPriceDistanceBps`. Current report and operation getters are `pendingReportId`, `pendingReportSponsor`, `pendingOperationSlotId`, `lastSettlementTimestamp`, `lastPrice`, `pendingReportMaxSettlementBaseFeeAttoEthPerGas`, `stagedOperationCounter`, and `stagedOperations`. Use `isPriceValid`, `minimumToken1ReportAttoEth`, `getRequestPriceCostAttoEth`, `getQueuedOperationCostAttoEth`, `getSettlementCallbackGasLimit`, `getPendingOperationSlot`, `getActiveStagedOperationCount`, `getActiveStagedOperations`, `getPendingSettlementOperationCount`, and `getPendingSettlementOperationIds` for derived or paged state.', securityBoundary: 'Report and staged-operation liveness depends on [A16 timely inclusion](./security-model.html#assumption-a16), [A17 corrector capability](./security-model.html#assumption-a17), [A18 independent correction incentive](./security-model.html#assumption-a18), [A19 observable correctable price](./security-model.html#assumption-a19), and [A06 lifecycle executors](./security-model.html#assumption-a06). When `lastPrice` is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path.', readDeclarations: [ @@ -1514,6 +1532,7 @@ const contractReferences: ContractReference[] = [ { name: 'stagedOperationCounter' }, { name: 'stagedOperations' }, { name: 'liquidationApprovalRegistry' }, + { name: 'operationBountyBoard' }, ], sourcePath: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', interactions: [ @@ -1544,6 +1563,14 @@ const contractReferences: ContractReference[] = [ 'Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund.', signals: '`PriceRequested` and `CoordinatorStateCheckpoint`', }, + { + call: '`stageAndRequestOperationBounty(bountyId, sponsor, creator, operation, targetVault, ...)`', + caller: 'The coordinator’s configured `operationBountyBoard` only', + effect: 'Stages a self-receiving creator operation while assigning initial-report token funding, report sponsorship, and unused ETH refunds to the accepting operator. The operation result is returned to the board before the coordinator emits its execution event.', + declarations: [{ name: 'stageAndRequestOperationBounty' }], + preconditions: 'No nested bounty staging call is active; the creator and target satisfy the normal operation route; and a stale price has room in the four-operation pending settlement batch.', + signals: '`StagedOperationQueued`, possibly `PriceRequested`, then `ExecutedStagedOperation`; authoritative `CoordinatorStateCheckpoint` records', + }, { call: '`executeStagedOperation(operationId)`', caller: 'Anyone', @@ -1577,6 +1604,14 @@ const contractReferences: ContractReference[] = [ preconditions: 'Callback report matches the pending report; excessive settlement basefee, a saturated `uint24` report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state.', signals: '`PriceReported` or `PriceReportRejected`; operation execution events; authoritative `CoordinatorStateCheckpoint` records', }, + { + call: '`setOperationBountyBoard(board)`', + caller: 'Coordinator deployment factory only', + effect: 'Binds the coordinator-local operation bounty board once.', + declarations: [{ name: 'setOperationBountyBoard' }], + preconditions: 'Board has deployed code and no board was previously installed.', + signals: '`OperationBountyBoardSet`', + }, { call: '`setLiquidationApprovalRegistry(registry)`', caller: 'Coordinator deployment factory only', @@ -1603,6 +1638,76 @@ const contractReferences: ContractReference[] = [ }, ], }, + { + compiledAbiFingerprint: '46b37ce4b96591befccadb0f39f90495131153c75924d48fd58686fb3282215a', + name: 'OpenOracleOperationBountyBoard', + purpose: 'Escrows REP or WETH rewards for creator-defined coordinator operations and pays the operator only after successful staged execution.', + readAbiFingerprint: 'b4c41228128077b7670e924c6f3952cfd00a5b10fc12533316cdb7f5e684c601', + readSurface: + 'Use `coordinator`, `reputationToken`, and `weth` to identify the bound coordinator and token contracts. `nextOperationBountyId`, `operationBounties`, and `operationExecutionStatuses` expose bounty identity, escrow terms, assignment, staged operation and report IDs, terminal state, and execution outcome. `getOperationBounties` pages forward from an explicit ID. The co-located deployment factory exposes its immutable `owner` and shared `implementation`.', + readDeclarations: [{ name: 'getOperationBounties' }], + readStorageDeclarations: [{ name: 'coordinator' }, { name: 'reputationToken' }, { name: 'weth' }, { name: 'nextOperationBountyId' }, { name: 'operationBounties' }, { name: 'operationExecutionStatuses' }, { name: 'owner' }, { name: 'implementation' }], + sourcePath: 'solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol', + interactions: [ + { + call: '`initialize(coordinator, reputationToken, weth)`', + caller: 'Anyone while uninitialized; the canonical factory initializes new clones atomically', + effect: 'Binds the proxy to its coordinator and token pair and starts bounty IDs at one.', + declarations: [{ name: 'initialize' }], + preconditions: 'The proxy is not initialized and every supplied address is nonzero. The shared implementation is constructor-locked against initialization.', + signals: 'No event; the factory completes initialization before returning the proxy.', + }, + { + call: '`postOperationBounty(operation, targetVault, amount, validForSeconds, rewardToken, rewardAmount, acceptanceDeadline, minimumInitialAttoWeth, maximumInitialAttoWeth)`', + caller: 'Any creator with sufficient REP or WETH allowance and balance', + effect: 'Creates an open bounty and escrows the full reward on the board.', + declarations: [{ name: 'postOperationBounty' }], + preconditions: + "`amount` is positive attoREP for a withdrawal or positive maximum requested debt in attoETH for a liquidation. The reward is positive coordinator REP or WETH; its token amount uses that token's 18-decimal base unit. The acceptance deadline is strictly in the future; execution validity is from 1 second through 5 minutes. A zero `maximumInitialAttoWeth` means no maximum; otherwise the minimum cannot exceed it. Withdrawals target the creator and liquidations target another vault.", + signals: '`OperationBountyPosted`', + }, + { + call: '`acceptOperationBounty(bountyId, proposedRepPerEthPrice, requestedInitialAttoWeth)` with report funding when stale', + caller: 'Any operator at or before the acceptance deadline', + effect: 'Assigns the bounty, stages its operation, and either executes against a fresh price or attaches to a pending report. When a report must be opened, the operator supplies its REP, WETH, and ETH funding and becomes the report sponsor.', + declarations: [{ name: 'acceptOperationBounty' }], + preconditions: 'Bounty is open; its initial WETH bounds admit the new or existing report; the settlement batch has room; and an existing pending report is sponsored by this operator.', + signals: '`OperationBountyAccepted`; coordinator staging and reporting events', + }, + { + call: '`claimOperationBounty(bountyId)`', + caller: 'Assigned operator only', + effect: 'Marks the bounty paid and transfers its escrowed reward to the operator.', + declarations: [{ name: 'claimOperationBounty' }], + preconditions: 'Bounty remains assigned and its recorded execution status is succeeded.', + signals: '`OperationBountyClaimed`', + }, + { + call: '`refundOperationBounty(bountyId)`', + caller: 'Bounty creator only', + effect: 'Cancels an open bounty immediately, refunds a failed bounty, or expires an overdue staged operation before refunding its full escrow.', + declarations: [{ name: 'refundOperationBounty' }], + preconditions: 'Bounty is open, has failed, or remains assigned and pending strictly after `queuedAt + settlementTime + validForSeconds`; equality is still active. That fixed cancellation deadline does not move when disputes extend report settlement. Successful execution cannot be refunded.', + signals: '`OperationBountyRefunded`; overdue assigned bounties also produce `ExecutedStagedOperation`', + }, + { + call: '`recordOperationResult(bountyId, success)`', + caller: 'Bound `OpenOraclePriceCoordinator` only', + effect: 'Records the assigned bounty as succeeded or failed so its escrow can be claimed or refunded.', + declarations: [{ name: 'recordOperationResult' }], + preconditions: 'Bounty remains assigned.', + signals: 'The coordinator emits `ExecutedStagedOperation` with the same result and any failure detail.', + }, + { + call: '`deploy(coordinator, reputationToken, weth, salt)`', + caller: 'Owning price-coordinator factory only', + effect: 'Lazily deploys one shared board implementation, then deterministically deploys and initializes a minimal proxy for the coordinator and token pair.', + declarations: [{ name: 'deploy' }], + preconditions: 'Caller equals the factory `owner`; the derived CREATE2 address is unused.', + signals: 'No dedicated event; the coordinator subsequently emits `OperationBountyBoardSet`.', + }, + ], + }, { compiledAbiFingerprint: 'b4d43db4a275c3118a700ca255a7f63d42dfdca1fb1e7c554d681e589a76ac85', name: 'ShareToken', diff --git a/solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol b/solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol new file mode 100644 index 000000000..c89da5334 --- /dev/null +++ b/solidity/contracts/peripherals/OpenOracleOperationBountyBoard.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.35; + +import { IERC20 } from '../IERC20.sol'; +import { ReputationToken } from '../ReputationToken.sol'; +import { SafeERC20Ops } from '../SafeERC20Ops.sol'; +import { IWeth9 } from './interfaces/IWeth9.sol'; +import { OpenOraclePriceCoordinator, OperationType } from './OpenOraclePriceCoordinator.sol'; + +interface IStoredOpenOracleBountyGame { + function storedGame(uint256 reportId) + external + view + returns ( + uint128 currentAmount1, + uint128 currentAmount2, + address currentReporter, + uint48 reportTimestamp, + uint48 settlementTimestamp + ); +} + +enum OperationBountyState { + None, + Open, + Assigned, + Paid, + Refunded +} + +enum OperationExecutionStatus { + None, + Pending, + Succeeded, + Failed +} + +struct OperationBounty { + address creator; + address operator; + OperationType operation; + address targetVault; + uint256 amount; + uint256 validForSeconds; + address rewardToken; + uint256 rewardAmount; + uint256 acceptanceDeadline; + uint256 minimumInitialAttoWeth; + uint256 maximumInitialAttoWeth; + uint256 operationId; + uint256 reportId; + OperationBountyState state; +} + +contract OpenOracleOperationBountyBoard { + using SafeERC20Ops for IERC20; + + OpenOraclePriceCoordinator public coordinator; + ReputationToken public reputationToken; + IWeth9 public weth; + uint256 public nextOperationBountyId; + mapping(uint256 => OperationBounty) public operationBounties; + mapping(uint256 => OperationExecutionStatus) public operationExecutionStatuses; + bool private initialized; + + event OperationBountyPosted(uint256 indexed bountyId, address indexed creator, address indexed rewardToken, OperationType operation, address targetVault, uint256 amount, uint256 validForSeconds, uint256 rewardAmount, uint256 acceptanceDeadline, uint256 minimumInitialAttoWeth, uint256 maximumInitialAttoWeth); + event OperationBountyAccepted(uint256 indexed bountyId, address indexed operator, uint256 indexed operationId, uint256 reportId); + event OperationBountyClaimed(uint256 indexed bountyId, address indexed operator, address indexed rewardToken, uint256 rewardAmount); + event OperationBountyRefunded(uint256 indexed bountyId, address indexed creator, address indexed rewardToken, uint256 rewardAmount); + + constructor() { + initialized = true; + } + + function initialize(OpenOraclePriceCoordinator _coordinator, ReputationToken _reputationToken, IWeth9 _weth) external { + require(!initialized, 'Operation bounty board is already initialized'); + require(address(_coordinator) != address(0) && address(_reputationToken) != address(0) && address(_weth) != address(0), 'Invalid operation bounty board setup'); + initialized = true; + coordinator = _coordinator; + reputationToken = _reputationToken; + weth = _weth; + nextOperationBountyId = 1; + } + + function postOperationBounty(OperationType operation, address targetVault, uint256 amount, uint256 validForSeconds, address rewardToken, uint256 rewardAmount, uint256 acceptanceDeadline, uint256 minimumInitialAttoWeth, uint256 maximumInitialAttoWeth) external returns (uint256 bountyId) { + require(amount > 0 && validForSeconds > 0 && validForSeconds <= 5 minutes, 'Invalid bounty operation'); + require(operation == OperationType.Liquidation ? targetVault != msg.sender : targetVault == msg.sender, 'Invalid bounty target'); + require(rewardToken == address(reputationToken) || rewardToken == address(weth), 'Operation bounty reward token must be this coordinator REP or WETH'); + require(rewardAmount > 0, 'Operation bounty reward must be positive'); + require(acceptanceDeadline > block.timestamp, 'Operation bounty acceptance deadline must be in the future'); + require(maximumInitialAttoWeth == 0 || minimumInitialAttoWeth <= maximumInitialAttoWeth, 'Operation bounty initial report bounds are invalid'); + + bountyId = nextOperationBountyId++; + operationBounties[bountyId] = OperationBounty({creator: msg.sender, operator: address(0), operation: operation, targetVault: targetVault, amount: amount, validForSeconds: validForSeconds, rewardToken: rewardToken, rewardAmount: rewardAmount, acceptanceDeadline: acceptanceDeadline, minimumInitialAttoWeth: minimumInitialAttoWeth, maximumInitialAttoWeth: maximumInitialAttoWeth, operationId: 0, reportId: 0, state: OperationBountyState.Open}); + IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), rewardAmount); + emit OperationBountyPosted(bountyId, msg.sender, rewardToken, operation, targetVault, amount, validForSeconds, rewardAmount, acceptanceDeadline, minimumInitialAttoWeth, maximumInitialAttoWeth); + } + + function acceptOperationBounty(uint256 bountyId, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) external payable returns (uint256 operationId) { + OperationBounty storage bounty = operationBounties[bountyId]; + require(bounty.state == OperationBountyState.Open, 'Operation bounty is not open'); + require(block.timestamp <= bounty.acceptanceDeadline, 'Operation bounty acceptance deadline has passed'); + if (!coordinator.isPriceValid()) { + uint256 currentPendingReportId = coordinator.pendingReportId(); + if (currentPendingReportId == 0) { + uint256 minimumReportAttoWeth = coordinator.minimumToken1ReportAttoEth(); + uint256 initialReportAttoWeth = + requestedInitialAttoWeth > minimumReportAttoWeth ? requestedInitialAttoWeth : minimumReportAttoWeth; + _validateInitialAttoWeth(bounty, initialReportAttoWeth); + } else { + (uint128 currentInitialAttoWeth, , , , ) = IStoredOpenOracleBountyGame(address(coordinator.openOracle())).storedGame(currentPendingReportId); + _validateInitialAttoWeth(bounty, currentInitialAttoWeth); + } + } + + bounty.operator = msg.sender; + bounty.state = OperationBountyState.Assigned; + operationExecutionStatuses[bountyId] = OperationExecutionStatus.Pending; + (operationId, bounty.reportId) = coordinator.stageAndRequestOperationBounty{value: msg.value}(bountyId, msg.sender, bounty.creator, bounty.operation, bounty.targetVault, bounty.amount, bounty.validForSeconds, proposedRepPerEthPrice, requestedInitialAttoWeth); + bounty.operationId = operationId; + emit OperationBountyAccepted(bountyId, msg.sender, operationId, bounty.reportId); + } + + function _validateInitialAttoWeth(OperationBounty storage bounty, uint256 initialAttoWeth) private view { + require(initialAttoWeth >= bounty.minimumInitialAttoWeth, 'Initial report WETH amount is below the bounty minimum'); + if (bounty.maximumInitialAttoWeth != 0) { + require(initialAttoWeth <= bounty.maximumInitialAttoWeth, 'Initial report WETH amount exceeds the bounty maximum'); + } + } + + function claimOperationBounty(uint256 bountyId) external { + OperationBounty storage bounty = operationBounties[bountyId]; + require(bounty.state == OperationBountyState.Assigned, 'Operation bounty is not assigned'); + require(msg.sender == bounty.operator, 'Only the assigned operator can claim the operation bounty'); + require(operationExecutionStatuses[bountyId] == OperationExecutionStatus.Succeeded, 'Operation bounty cannot be claimed before successful execution'); + bounty.state = OperationBountyState.Paid; + IERC20(bounty.rewardToken).safeTransfer(msg.sender, bounty.rewardAmount); + emit OperationBountyClaimed(bountyId, msg.sender, bounty.rewardToken, bounty.rewardAmount); + } + + function refundOperationBounty(uint256 bountyId) external { + OperationBounty storage bounty = operationBounties[bountyId]; + require(msg.sender == bounty.creator, 'Only the bounty creator can refund the operation bounty'); + if (bounty.state == OperationBountyState.Assigned) { + OperationExecutionStatus status = operationExecutionStatuses[bountyId]; + if (status == OperationExecutionStatus.Pending) { + coordinator.expireStagedOperation(bounty.operationId); + } else { + require(status == OperationExecutionStatus.Failed, 'Successful operation bounty cannot be refunded'); + } + } else { + require(bounty.state == OperationBountyState.Open, 'Operation bounty cannot be refunded'); + } + + bounty.state = OperationBountyState.Refunded; + IERC20(bounty.rewardToken).safeTransfer(bounty.creator, bounty.rewardAmount); + emit OperationBountyRefunded(bountyId, bounty.creator, bounty.rewardToken, bounty.rewardAmount); + } + + function recordOperationResult(uint256 bountyId, bool success) external { + require(msg.sender == address(coordinator), 'Only coordinator'); + require(operationBounties[bountyId].state == OperationBountyState.Assigned, 'Operation bounty is not assigned'); + operationExecutionStatuses[bountyId] = + success ? OperationExecutionStatus.Succeeded : OperationExecutionStatus.Failed; + } + + function getOperationBounties(uint256 startId, uint256 count) external view returns (uint256[] memory bountyIds, OperationBounty[] memory bounties) { + if (startId == 0 || startId >= nextOperationBountyId || count == 0) { + return (new uint256[](0), new OperationBounty[](0)); + } + uint256 available = nextOperationBountyId - startId; + uint256 resultCount = count < available ? count : available; + bountyIds = new uint256[](resultCount); + bounties = new OperationBounty[](resultCount); + for (uint256 index = 0; index < resultCount; index++) { + uint256 bountyId = startId + index; + bountyIds[index] = bountyId; + bounties[index] = operationBounties[bountyId]; + } + } +} + +contract OpenOracleOperationBountyBoardFactory { + address public immutable owner; + OpenOracleOperationBountyBoard public implementation; + + constructor() { + owner = msg.sender; + } + + function deploy(OpenOraclePriceCoordinator coordinator, ReputationToken reputationToken, IWeth9 weth, bytes32 salt) external returns (OpenOracleOperationBountyBoard board) { + require(msg.sender == owner, 'Only the owner can deploy an operation bounty board'); + OpenOracleOperationBountyBoard currentImplementation = implementation; + if (address(currentImplementation) == address(0)) { + currentImplementation = new OpenOracleOperationBountyBoard(); + implementation = currentImplementation; + } + bytes memory initCode = abi.encodePacked(hex'3d602d80600a3d3981f3', hex'363d3d373d3d3d363d73', address(currentImplementation), hex'5af43d82803e903d91602b57fd5bf3'); + address deployed; + bytes32 deploymentSalt = keccak256(abi.encode(coordinator, salt)); + assembly ('memory-safe') { + deployed := create2(0, add(initCode, 0x20), mload(initCode), deploymentSalt) + } + require(deployed != address(0), 'Operation bounty board deployment failed'); + board = OpenOracleOperationBountyBoard(deployed); + board.initialize(coordinator, reputationToken, weth); + } +} diff --git a/solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol b/solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol index d09f742b1..541e7c6ff 100644 --- a/solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol +++ b/solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol @@ -8,6 +8,7 @@ import { ISecurityPool } from './interfaces/ISecurityPool.sol'; import { SecurityPoolUtils } from './SecurityPoolUtils.sol'; import { Math } from './openOracle/openzeppelin/contracts/utils/math/Math.sol'; import { LiquidationApprovalRegistry } from './LiquidationApprovalRegistry.sol'; +import { IOperationBountyResultReceiver } from './interfaces/IOperationBountyResultReceiver.sol'; // price oracle uint256 constant PRICE_VALID_FOR_SECONDS = 5 minutes; @@ -77,6 +78,14 @@ contract OpenOraclePriceCoordinator { string private constant STAGED_OPERATION_ERROR_ZERO_WITHDRAW = 'withdraw amount has no effect'; string private constant STAGED_OPERATION_ERROR_PANIC = 'Panic'; string private constant STAGED_OPERATION_ERROR_UNKNOWN = 'Unknown error'; + string private constant INVALID_ORACLE_CONFIGURATION = 'Invalid oracle configuration'; + string private constant INVALID_COORDINATOR_SETUP = 'Invalid coordinator setup'; + string private constant INVALID_ORACLE_REQUEST = 'Invalid oracle request'; + string private constant ORACLE_VALUE_EXCEEDS_LIMIT = 'Oracle value exceeds limit'; + string private constant ORACLE_TOKEN_FUNDING_FAILED = 'Oracle token funding failed'; + string private constant INVALID_STAGED_OPERATION = 'Invalid staged operation'; + string private constant INVALID_STAGED_ROUTE = 'Invalid staged route'; + string private constant ETH_REFUND_FAILED = 'ETH refund failed'; uint256 public pendingReportId; address public pendingReportSponsor; uint256 public pendingOperationSlotId; @@ -106,8 +115,13 @@ contract OpenOraclePriceCoordinator { uint256 public pendingReportMaxSettlementBaseFeeAttoEthPerGas; LiquidationApprovalRegistry public liquidationApprovalRegistry; address private immutable coordinatorFactory; + address public operationBountyBoard; + address private bountyOperationCreator; + address private bountyReportSponsor; + uint256 private bountyOperationId; event SecurityPoolSet(ISecurityPool indexed securityPool); + event OperationBountyBoardSet(address indexed operationBountyBoard); event RepEthPriceSet(uint256 price); event PriceRequested(uint256 indexed reportId, uint256 pendingReportMaxSettlementBaseFeeAttoEthPerGas); event PriceReportRejected(uint256 indexed reportId, string reason, uint256 pendingReportId, uint256 pendingReportMaxSettlementBaseFeeAttoEthPerGas, uint256 lastPrice, uint256 lastSettlementTimestamp); @@ -126,6 +140,7 @@ contract OpenOraclePriceCoordinator { // execution removes older entries from the set. uint256 public stagedOperationCounter; mapping(uint256 => StagedOperation) public stagedOperations; + mapping(uint256 => uint256) private operationBountyIds; uint256 private activeStagedOperationCount; uint256 private latestActiveStagedOperationId; mapping(uint256 => uint256) private olderActiveStagedOperationIds; @@ -140,13 +155,13 @@ contract OpenOraclePriceCoordinator { weth = _weth; gasConsumedOpenOracleReportPrice = _gasConsumedOpenOracleReportPrice; gasConsumedSettlement = _gasConsumedSettlement; - require(_gasUnitsForOneDispute > 0, 'Dispute gas units zero'); - require(_initialReportPriorityFeeAttoEthPerGas > 0, 'Initial priority fee zero'); - require(_targetPriceErrorForDispute <= OPEN_ORACLE_PERCENTAGE_PRECISION, 'Target price error cannot exceed one hundred percent'); - require(_openOracleSecurityMultiplierBps >= SecurityPoolUtils.BPS_DENOMINATOR, 'Open Oracle Security multiplier must be at least one hundred percent'); - require(uint256(_protocolFee) + uint256(_feePercentage) < _targetPriceErrorForDispute, 'Oracle fees must be below the target price error'); - require(_escalationHaltMultiplierBps > 0, 'Escalation multiplier zero'); - require(_openOracleSecurityMultiplierBps <= type(uint256).max / (OPEN_ORACLE_PERCENTAGE_PRECISION + _targetPriceErrorForDispute), 'Open Oracle Security multiplier is too large'); + require(_gasUnitsForOneDispute > 0, INVALID_ORACLE_CONFIGURATION); + require(_initialReportPriorityFeeAttoEthPerGas > 0, INVALID_ORACLE_CONFIGURATION); + require(_targetPriceErrorForDispute <= OPEN_ORACLE_PERCENTAGE_PRECISION, INVALID_ORACLE_CONFIGURATION); + require(_openOracleSecurityMultiplierBps >= SecurityPoolUtils.BPS_DENOMINATOR, INVALID_ORACLE_CONFIGURATION); + require(uint256(_protocolFee) + uint256(_feePercentage) < _targetPriceErrorForDispute, INVALID_ORACLE_CONFIGURATION); + require(_escalationHaltMultiplierBps > 0, INVALID_ORACLE_CONFIGURATION); + require(_openOracleSecurityMultiplierBps <= type(uint256).max / (OPEN_ORACLE_PERCENTAGE_PRECISION + _targetPriceErrorForDispute), INVALID_ORACLE_CONFIGURATION); uint256 correctionProfitNumerator = _targetPriceErrorForDispute - uint256(_protocolFee) - uint256(_feePercentage); uint256 reportNumeratorMultiplier = @@ -157,7 +172,7 @@ contract OpenOraclePriceCoordinator { maximumPriorityFeeReportAttoEth /= 2; uint256 maximumPriorityDisputeGasCost = Math.mulDiv(maximumPriorityFeeReportAttoEth, reportDenominator, reportNumeratorMultiplier); uint256 maximumInitialReportPriorityFeeAttoEthPerGas = maximumPriorityDisputeGasCost / _gasUnitsForOneDispute; - require(_initialReportPriorityFeeAttoEthPerGas <= maximumInitialReportPriorityFeeAttoEthPerGas, 'Initial report priority fee exceeds OpenOracle limits'); + require(_initialReportPriorityFeeAttoEthPerGas <= maximumInitialReportPriorityFeeAttoEthPerGas, INVALID_ORACLE_CONFIGURATION); gasUnitsForOneDispute = _gasUnitsForOneDispute; initialReportPriorityFeeAttoEthPerGas = _initialReportPriorityFeeAttoEthPerGas; targetPriceErrorForDispute = _targetPriceErrorForDispute; @@ -172,19 +187,25 @@ contract OpenOraclePriceCoordinator { trackDisputes = _trackDisputes; protocolFeeRecipient = _protocolFeeRecipient; escalationHaltMultiplierBps = _escalationHaltMultiplierBps; - require(_maxSettlementBaseFeeMultiplierBps >= SecurityPoolUtils.BPS_DENOMINATOR, 'Max settlement base fee multiplier must be at least one hundred percent'); - require(_minLiquidationPriceDistanceBps <= SecurityPoolUtils.BPS_DENOMINATOR, 'Minimum liquidation price distance cannot exceed one hundred percent'); + require(_maxSettlementBaseFeeMultiplierBps >= SecurityPoolUtils.BPS_DENOMINATOR, INVALID_ORACLE_CONFIGURATION); + require(_minLiquidationPriceDistanceBps <= SecurityPoolUtils.BPS_DENOMINATOR, INVALID_ORACLE_CONFIGURATION); maxSettlementBaseFeeMultiplierBps = _maxSettlementBaseFeeMultiplierBps; minLiquidationPriceDistanceBps = _minLiquidationPriceDistanceBps; } function setLiquidationApprovalRegistry(LiquidationApprovalRegistry registry) external { - require(msg.sender == coordinatorFactory && address(liquidationApprovalRegistry) == address(0) && address(registry) != address(0), 'Registry setup invalid'); + require(msg.sender == coordinatorFactory && address(liquidationApprovalRegistry) == address(0) && address(registry) != address(0), INVALID_COORDINATOR_SETUP); liquidationApprovalRegistry = registry; } + function setOperationBountyBoard(address board) external { + require(msg.sender == coordinatorFactory && operationBountyBoard == address(0) && board.code.length > 0, INVALID_COORDINATOR_SETUP); + operationBountyBoard = board; + emit OperationBountyBoardSet(board); + } + function setSecurityPool(ISecurityPool _securityPool) public { - require(address(securityPool) == address(0x0), 'Security pool already set'); + require(address(securityPool) == address(0x0), INVALID_COORDINATOR_SETUP); securityPool = _securityPool; emit SecurityPoolSet(securityPool); _emitCoordinatorStateCheckpoint(CoordinatorCheckpointReason.SecurityPoolSetup, 0, 0); @@ -207,7 +228,7 @@ contract OpenOraclePriceCoordinator { function getSettlementCallbackGasLimit() public view returns (uint32) { uint256 callbackGasLimit = uint256(gasConsumedSettlement) * MAX_PENDING_SETTLEMENT_OPERATIONS; - require(callbackGasLimit <= type(uint32).max, 'Callback gas exceeds uint32'); + require(callbackGasLimit <= type(uint32).max, ORACLE_VALUE_EXCEEDS_LIMIT); return uint32(callbackGasLimit); } @@ -234,20 +255,20 @@ contract OpenOraclePriceCoordinator { function requestPrice(uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) public payable { uint256 costAttoEth = getRequestPriceCostAttoEth(); - require(msg.value >= costAttoEth, 'Oracle bounty too small'); - require(!isPriceValid(), 'Oracle price already fresh'); + require(msg.value >= costAttoEth, INVALID_ORACLE_REQUEST); + require(!isPriceValid(), INVALID_ORACLE_REQUEST); _requestPrice(msg.sender, costAttoEth, proposedRepPerEthPrice, requestedInitialAttoWeth); uint256 excess = msg.value - costAttoEth; if (excess > 0) { (bool sent, ) = payable(msg.sender).call{value: excess}(''); - require(sent, 'Oracle coordinator failed to refund excess ETH bounty'); + require(sent, ETH_REFUND_FAILED); } } function _requestPrice(address sponsor, uint256 costAttoEth, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) private { - require(pendingReportId == 0, 'Oracle request already pending'); - require(proposedRepPerEthPrice > 0, 'Initial oracle price zero'); + require(pendingReportId == 0, INVALID_ORACLE_REQUEST); + require(proposedRepPerEthPrice > 0, INVALID_ORACLE_REQUEST); uint256 minimumWethReportAttoEth = minimumToken1ReportAttoEth(); uint256 initialWethReportAttoEth = requestedInitialAttoWeth > minimumWethReportAttoEth ? requestedInitialAttoWeth : minimumWethReportAttoEth; @@ -257,10 +278,10 @@ contract OpenOraclePriceCoordinator { if (openInterestEscalationHaltAttoEth > escalationHaltAttoEth) escalationHaltAttoEth = openInterestEscalationHaltAttoEth; uint256 settlerRewardAttoEth = costAttoEth; - require(initialWethReportAttoEth <= type(uint128).max, 'WETH report exceeds uint128'); - require(initialRepReportAttoRep <= type(uint128).max, 'REP report exceeds uint128'); - require(escalationHaltAttoEth <= type(uint128).max, 'Oracle escalation halt amount exceeds uint128 maximum'); - require(settlerRewardAttoEth <= type(uint96).max, 'Oracle settler reward exceeds uint96 maximum'); + require(initialWethReportAttoEth <= type(uint128).max, ORACLE_VALUE_EXCEEDS_LIMIT); + require(initialRepReportAttoRep <= type(uint128).max, ORACLE_VALUE_EXCEEDS_LIMIT); + require(escalationHaltAttoEth <= type(uint128).max, ORACLE_VALUE_EXCEEDS_LIMIT); + require(settlerRewardAttoEth <= type(uint96).max, ORACLE_VALUE_EXCEEDS_LIMIT); pendingReportMaxSettlementBaseFeeAttoEthPerGas = (block.basefee * maxSettlementBaseFeeMultiplierBps) / SecurityPoolUtils.BPS_DENOMINATOR; @@ -269,10 +290,10 @@ contract OpenOraclePriceCoordinator { OpenOracle.OracleGame memory reportParams = OpenOracle.OracleGame({currentAmount1: uint128(initialWethReportAttoEth), currentAmount2: uint128(initialRepReportAttoRep), currentReporter: address(this), reportTimestamp: 0, settlementTimestamp: 0, token1: address(weth), lastReportOppoTime: 0, settlementTime: settlementTime, escalationHalt: uint128(escalationHaltAttoEth), protocolFeeRecipient: protocolFeeRecipient, settlerReward: uint96(settlerRewardAttoEth), token2: address(reputationToken), numReports: 0, disputeDelay: disputeDelay, feePercentage: feePercentage, multiplier: multiplier, callbackContract: address(this), callbackGasLimit: getSettlementCallbackGasLimit(), protocolFee: protocolFee, flags: flags}); pendingReportSponsor = sponsor; - require(weth.transferFrom(sponsor, address(this), initialWethReportAttoEth), 'WETH transfer for initial report failed'); - require(reputationToken.transferFrom(sponsor, address(this), initialRepReportAttoRep), 'REP transfer for initial report failed'); - require(weth.approve(address(openOracle), initialWethReportAttoEth), 'WETH approval for initial report failed'); - require(reputationToken.approve(address(openOracle), initialRepReportAttoRep), 'REP approval for initial report failed'); + require(weth.transferFrom(sponsor, address(this), initialWethReportAttoEth), ORACLE_TOKEN_FUNDING_FAILED); + require(reputationToken.transferFrom(sponsor, address(this), initialRepReportAttoRep), ORACLE_TOKEN_FUNDING_FAILED); + require(weth.approve(address(openOracle), initialWethReportAttoEth), ORACLE_TOKEN_FUNDING_FAILED); + require(reputationToken.approve(address(openOracle), initialRepReportAttoRep), ORACLE_TOKEN_FUNDING_FAILED); pendingReportId = openOracle.report{value: costAttoEth}(reportParams, false, false, OpenOracle.TimingBoundaries({blockNumber: 0, blockNumberBound: 0, blockTimestamp: 0, blockTimestampBound: 0})); emit PriceRequested(pendingReportId, pendingReportMaxSettlementBaseFeeAttoEthPerGas); _emitCoordinatorStateCheckpoint(CoordinatorCheckpointReason.PriceRequested, pendingReportId, 0); @@ -381,7 +402,8 @@ contract OpenOraclePriceCoordinator { } function requestPriceIfNeededAndStageOperation(OperationType operation, address targetVault, uint256 operationAmountAttoRepOrAttoEth, uint256 validForSeconds, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) public payable { - _requestPriceIfNeededAndStageOperation(operation, targetVault, msg.sender, bytes32(0), operationAmountAttoRepOrAttoEth, validForSeconds, proposedRepPerEthPrice, requestedInitialAttoWeth); + address actor = msg.sender == address(this) ? bountyOperationCreator : msg.sender; + _requestPriceIfNeededAndStageOperation(operation, targetVault, actor, bytes32(0), operationAmountAttoRepOrAttoEth, validForSeconds, proposedRepPerEthPrice, requestedInitialAttoWeth); } function requestPriceIfNeededAndStageLiquidation(address targetVault, address receiverVault, uint256 requestedDebtAttoEth, bytes32 approvalId, uint256 validForSeconds, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) external payable { @@ -389,22 +411,24 @@ contract OpenOraclePriceCoordinator { } function _requestPriceIfNeededAndStageOperation(OperationType operation, address targetVault, address receiverVault, bytes32 approvalId, uint256 operationAmountAttoRepOrAttoEth, uint256 validForSeconds, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) private { - require(operationAmountAttoRepOrAttoEth > 0, 'Staged operation amount must be non-zero'); - require(validForSeconds > 0, 'Staged operation timeout must be positive'); - require(validForSeconds <= MAX_OPERATION_VALID_FOR_SECONDS, 'Staged operation timeout exceeds the maximum allowed'); + address operator = msg.sender == address(this) ? bountyOperationCreator : msg.sender; + address reportSponsor = msg.sender == address(this) ? bountyReportSponsor : msg.sender; + require(operationAmountAttoRepOrAttoEth > 0, INVALID_STAGED_OPERATION); + require(validForSeconds > 0, INVALID_STAGED_OPERATION); + require(validForSeconds <= MAX_OPERATION_VALID_FOR_SECONDS, INVALID_STAGED_OPERATION); if (operation != OperationType.Liquidation) { - require(targetVault == msg.sender, 'Self operation target mismatch'); - require(receiverVault == msg.sender && approvalId == bytes32(0), 'Self route mismatch'); + require(targetVault == operator, INVALID_STAGED_ROUTE); + require(receiverVault == operator && approvalId == bytes32(0), INVALID_STAGED_ROUTE); } else { - require(receiverVault != targetVault, 'Receiver is target'); + require(receiverVault != targetVault, INVALID_STAGED_ROUTE); } - require(!securityPool.isEscalationResolved(), 'question already resolved, so staged operations are unavailable'); + require(!securityPool.isEscalationResolved(), INVALID_STAGED_OPERATION); if (pendingReportId != 0) { - require(msg.sender == pendingReportSponsor, 'Only the pending report sponsor can queue more operations until settlement'); + require(reportSponsor == pendingReportSponsor, 'Only the pending report sponsor can queue more operations until settlement'); } if (operation == OperationType.WithdrawRep) { - (, uint256 withdrawRepAmountAttoRep) = _previewWithdrawRep(msg.sender, operationAmountAttoRepOrAttoEth); - require(withdrawRepAmountAttoRep > 0, 'Withdraw amount has no effect'); + (, uint256 withdrawRepAmountAttoRep) = _previewWithdrawRep(operator, operationAmountAttoRepOrAttoEth); + require(withdrawRepAmountAttoRep > 0, INVALID_STAGED_OPERATION); } stagedOperationCounter++; uint256 operationId = stagedOperationCounter; @@ -424,15 +448,16 @@ contract OpenOraclePriceCoordinator { ? securityPool.escalationGame().disputeStakedRepByVaultAttoRep(targetVault) : 0; uint256 reservedLiquidationDebtAttoEth; - if (operation == OperationType.Liquidation && receiverVault != msg.sender) { - reservedLiquidationDebtAttoEth = liquidationApprovalRegistry.reserve(operationId, approvalId, receiverVault, targetVault, msg.sender, operationAmountAttoRepOrAttoEth, snapshotTargetOpenInterestAttoEth, block.timestamp + uint256(settlementTime) + validForSeconds); + if (operation == OperationType.Liquidation && receiverVault != operator) { + reservedLiquidationDebtAttoEth = liquidationApprovalRegistry.reserve(operationId, approvalId, receiverVault, targetVault, operator, operationAmountAttoRepOrAttoEth, snapshotTargetOpenInterestAttoEth, block.timestamp + uint256(settlementTime) + validForSeconds); } else if (operation == OperationType.Liquidation) { - require(approvalId == bytes32(0), 'Self approval must be zero'); + require(approvalId == bytes32(0), INVALID_STAGED_ROUTE); } - stagedOperations[operationId] = StagedOperation({operation: operation, operator: msg.sender, receiverVault: receiverVault, targetVault: targetVault, operationAmountAttoRepOrAttoEth: operationAmountAttoRepOrAttoEth, queuedAt: block.timestamp, validForSeconds: validForSeconds, snapshotTargetBackingUnits: snapshotTargetBackingUnits, snapshotTargetCapacityOwnershipAttoRep: snapshotTargetCapacityOwnershipAttoRep, snapshotTargetOpenInterestAttoEth: snapshotTargetOpenInterestAttoEth, snapshotTargetDisputeStakedAttoRep: snapshotTargetDisputeStakedAttoRep, snapshotTotalPoolHeldAttoRep: snapshotTotalPoolHeldAttoRep, snapshotTotalRepBackingUnits: snapshotTotalRepBackingUnits, liquidationApprovalId: approvalId, reservedLiquidationDebtAttoEth: reservedLiquidationDebtAttoEth}); + stagedOperations[operationId] = StagedOperation({operation: operation, operator: operator, receiverVault: receiverVault, targetVault: targetVault, operationAmountAttoRepOrAttoEth: operationAmountAttoRepOrAttoEth, queuedAt: block.timestamp, validForSeconds: validForSeconds, snapshotTargetBackingUnits: snapshotTargetBackingUnits, snapshotTargetCapacityOwnershipAttoRep: snapshotTargetCapacityOwnershipAttoRep, snapshotTargetOpenInterestAttoEth: snapshotTargetOpenInterestAttoEth, snapshotTargetDisputeStakedAttoRep: snapshotTargetDisputeStakedAttoRep, snapshotTotalPoolHeldAttoRep: snapshotTotalPoolHeldAttoRep, snapshotTotalRepBackingUnits: snapshotTotalRepBackingUnits, liquidationApprovalId: approvalId, reservedLiquidationDebtAttoEth: reservedLiquidationDebtAttoEth}); + if (msg.sender == address(this)) operationBountyIds[operationId] = bountyOperationId; _trackActiveStagedOperation(operationId); if (operation == OperationType.Liquidation) { - emit LiquidationRouteStaged(operationId, msg.sender, receiverVault, targetVault, approvalId, operationAmountAttoRepOrAttoEth, reservedLiquidationDebtAttoEth); + emit LiquidationRouteStaged(operationId, operator, receiverVault, targetVault, approvalId, operationAmountAttoRepOrAttoEth, reservedLiquidationDebtAttoEth); } uint256 retained = 0; // amount to retain from msg.value (cost incurred) @@ -447,20 +472,34 @@ contract OpenOraclePriceCoordinator { _emitStagedOperationQueued(operationId, isPendingSettlementOperationId); if (shouldRequestPrice && isPendingSettlementOperationId) { uint256 costAttoEth = getRequestPriceCostAttoEth(); - require(msg.value >= costAttoEth, 'Not enough ETH was provided to request a fresh oracle price'); + require(msg.value >= costAttoEth, INVALID_ORACLE_REQUEST); retained += costAttoEth; - _requestPrice(msg.sender, costAttoEth, proposedRepPerEthPrice, requestedInitialAttoWeth); + _requestPrice(reportSponsor, costAttoEth, proposedRepPerEthPrice, requestedInitialAttoWeth); } } // Refund the excess of msg.value that was not retained uint256 refund = msg.value - retained; if (refund > 0) { - (bool sent, ) = payable(msg.sender).call{value: refund}(''); - require(sent, 'Oracle coordinator failed to return unused ETH'); + (bool sent, ) = payable(reportSponsor).call{value: refund}(''); + require(sent, ETH_REFUND_FAILED); } } + function stageAndRequestOperationBounty(uint256 bountyId, address sponsor, address creator, OperationType operation, address targetVault, uint256 amount, uint256 validForSeconds, uint256 proposedRepPerEthPrice, uint256 requestedInitialAttoWeth) external payable returns (uint256 operationId, uint256 reportId) { + require(msg.sender == operationBountyBoard && bountyOperationCreator == address(0), 'Invalid bounty caller'); + require(isPriceValid() || pendingSettlementOperationIds.length < MAX_PENDING_SETTLEMENT_OPERATIONS, 'Operation bounty queue full'); + operationId = stagedOperationCounter + 1; + bountyOperationCreator = creator; + bountyReportSponsor = sponsor; + bountyOperationId = bountyId; + this.requestPriceIfNeededAndStageOperation{value: msg.value}(operation, targetVault, amount, validForSeconds, proposedRepPerEthPrice, requestedInitialAttoWeth); + bountyOperationCreator = address(0); + bountyReportSponsor = address(0); + bountyOperationId = 0; + return (operationId, pendingReportId); + } + function executeStagedOperation(uint256 operationId) public { StagedOperation memory stagedOperation = stagedOperations[operationId]; require(stagedOperation.operator != address(0), 'Staged operation unavailable'); @@ -498,6 +537,11 @@ contract OpenOraclePriceCoordinator { } function _emitExecutedStagedOperation(uint256 operationId, OperationType operation, bool success, string memory errorMessage) private { + uint256 bountyId = operationBountyIds[operationId]; + if (bountyId != 0) { + delete operationBountyIds[operationId]; + IOperationBountyResultReceiver(operationBountyBoard).recordOperationResult(bountyId, success); + } emit ExecutedStagedOperation(operationId, operation, success, errorMessage); _emitCoordinatorStateCheckpoint(CoordinatorCheckpointReason.OperationExecuted, 0, operationId); } diff --git a/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol b/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol index a4ca985ab..c7eccc298 100644 --- a/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol +++ b/solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol @@ -8,6 +8,10 @@ import { OpenOracle } from '../openOracle/OpenOracle.sol'; import { ReputationToken } from '../../ReputationToken.sol'; import { OpenOraclePriceCoordinator } from '../OpenOraclePriceCoordinator.sol'; import { LiquidationApprovalRegistry } from '../LiquidationApprovalRegistry.sol'; +import { + OpenOracleOperationBountyBoard, + OpenOracleOperationBountyBoardFactory +} from '../OpenOracleOperationBountyBoard.sol'; contract LiquidationApprovalRegistryDeployer { address private immutable factory; @@ -59,11 +63,17 @@ contract PriceCoordinatorDeploymentWorker { require(msg.sender == factory, 'Only factory'); coordinator.setLiquidationApprovalRegistry(registry); } + + function configureOperationBountyBoard(OpenOraclePriceCoordinator coordinator, OpenOracleOperationBountyBoard board) external { + require(msg.sender == factory, 'Only factory'); + coordinator.setOperationBountyBoard(address(board)); + } } contract PriceOracleManagerAndOperatorQueuerFactory { LiquidationApprovalRegistryDeployer private immutable liquidationApprovalRegistryDeployer; PriceCoordinatorDeploymentWorker private immutable priceCoordinatorDeploymentWorker; + OpenOracleOperationBountyBoardFactory public immutable operationBountyBoardFactory; IWeth9 public immutable weth; uint256 public immutable gasConsumedOpenOracleReportPrice; uint32 public immutable gasConsumedSettlement; @@ -85,6 +95,7 @@ contract PriceOracleManagerAndOperatorQueuerFactory { constructor(IWeth9 _weth, uint256 _gasConsumedOpenOracleReportPrice, uint32 _gasConsumedSettlement, uint256 _gasUnitsForOneDispute, uint256 _targetPriceErrorForDispute, uint256 _openOracleSecurityMultiplierBps, uint48 _settlementTime, uint24 _disputeDelay, uint24 _protocolFee, uint24 _feePercentage, uint16 _multiplier, bool _timeType, bool _trackDisputes, address _protocolFeeRecipient, uint256 _escalationHaltMultiplierBps, uint256 _maxSettlementBaseFeeMultiplierBps, uint256 _minLiquidationPriceDistanceBps) { liquidationApprovalRegistryDeployer = new LiquidationApprovalRegistryDeployer(); priceCoordinatorDeploymentWorker = new PriceCoordinatorDeploymentWorker(); + operationBountyBoardFactory = new OpenOracleOperationBountyBoardFactory(); weth = _weth; gasConsumedOpenOracleReportPrice = _gasConsumedOpenOracleReportPrice; gasConsumedSettlement = _gasConsumedSettlement; @@ -109,6 +120,8 @@ contract PriceOracleManagerAndOperatorQueuerFactory { OpenOraclePriceCoordinator coordinator = priceCoordinatorDeploymentWorker.deploy(abi.encode(_openOracle, _reputationToken, weth, gasConsumedOpenOracleReportPrice, gasConsumedSettlement, gasUnitsForOneDispute, _initialReportPriorityFeeAttoEthPerGas, targetPriceErrorForDispute, openOracleSecurityMultiplierBps, settlementTime, disputeDelay, protocolFee, feePercentage, multiplier, timeType, trackDisputes, protocolFeeRecipient, escalationHaltMultiplierBps, maxSettlementBaseFeeMultiplierBps, minLiquidationPriceDistanceBps), deploymentSalt); LiquidationApprovalRegistry registry = liquidationApprovalRegistryDeployer.deploy(address(coordinator), deploymentSalt); priceCoordinatorDeploymentWorker.configureLiquidationApprovalRegistry(coordinator, registry); + OpenOracleOperationBountyBoard board = operationBountyBoardFactory.deploy(coordinator, _reputationToken, weth, deploymentSalt); + priceCoordinatorDeploymentWorker.configureOperationBountyBoard(coordinator, board); return coordinator; } } diff --git a/solidity/contracts/peripherals/interfaces/IOperationBountyResultReceiver.sol b/solidity/contracts/peripherals/interfaces/IOperationBountyResultReceiver.sol new file mode 100644 index 000000000..a9ab6b2ac --- /dev/null +++ b/solidity/contracts/peripherals/interfaces/IOperationBountyResultReceiver.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.35; + +interface IOperationBountyResultReceiver { + function recordOperationResult(uint256 bountyId, bool success) external; +} diff --git a/solidity/ts/tests/coverageHelpers.test.ts b/solidity/ts/tests/coverageHelpers.test.ts index 5119e8d86..09d0033e6 100644 --- a/solidity/ts/tests/coverageHelpers.test.ts +++ b/solidity/ts/tests/coverageHelpers.test.ts @@ -28,6 +28,7 @@ import { peripherals_factories_SecurityPoolDeployer_SecurityPoolDeployer, peripherals_factories_SecurityPoolDeployer_SecurityPoolDeploymentWorker, peripherals_factories_SecurityPoolFactory_SecurityPoolFactory, + peripherals_WETH9_WETH9, ReputationToken_ReputationToken, test_peripherals_CoverageHelpersHarness_CoverageAttributionDecoy, test_peripherals_CoverageHelpersHarness_CoverageAttributionExecuted, @@ -1587,11 +1588,17 @@ describe('Solidity bytecode coverage helpers', () => { /Escalation game deployment failed/, ) + const wethAddress = await deployContract( + encodeDeployData({ + abi: peripherals_WETH9_WETH9.abi, + bytecode: `0x${peripherals_WETH9_WETH9.evm.bytecode.object}`, + }), + ) const priceOracleFactoryAddress = await deployContract( encodeDeployData({ abi: peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.abi, bytecode: applyLibraries(peripherals_factories_PriceOracleManagerAndOperatorQueuerFactory_PriceOracleManagerAndOperatorQueuerFactory.evm.bytecode.object), - args: [zeroAddress, 100000n, 1000000, ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, 480, 0, 100000, 10000, 115, true, true, client.account.address, 100000n, 30000n, 1000n], + args: [wethAddress, 100000n, 1000000, ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, 480, 0, 100000, 10000, 115, true, true, client.account.address, 100000n, 30000n, 1000n], }), ) await transact( diff --git a/solidity/ts/tests/peripherals/deploymentAndOwnForkEscalation.test.ts b/solidity/ts/tests/peripherals/deploymentAndOwnForkEscalation.test.ts index 14caf658a..54fc56196 100644 --- a/solidity/ts/tests/peripherals/deploymentAndOwnForkEscalation.test.ts +++ b/solidity/ts/tests/peripherals/deploymentAndOwnForkEscalation.test.ts @@ -368,7 +368,7 @@ describe('Peripherals: deployment and own-fork escalation', () => { assert.strictEqual(configuredPriorityFee, customPriorityFeeAttoEthPerGas) assert.notStrictEqual(customAddresses.shareToken, securityPoolAddresses.shareToken, 'the priority fee must be part of the origin lineage identity') - await assert.rejects(deployOriginSecurityPool(client, genesisUniverse, questionId, statoblastSecurityMultiplierBps, 0n), /Initial priority fee zero/) + await assert.rejects(deployOriginSecurityPool(client, genesisUniverse, questionId, statoblastSecurityMultiplierBps, 0n), /Invalid oracle configuration/) }) test('stateful factory sequences keep one canonical collateral ledger per child token namespace', async () => { diff --git a/solidity/ts/tests/peripherals/forkMigration.test.ts b/solidity/ts/tests/peripherals/forkMigration.test.ts index 46e40a134..663719109 100644 --- a/solidity/ts/tests/peripherals/forkMigration.test.ts +++ b/solidity/ts/tests/peripherals/forkMigration.test.ts @@ -1112,7 +1112,7 @@ describe('Peripherals: fork migration', () => { const targetClaimBefore = await getVaultRepClaim(client.account.address) const liquidationDebtAttoEth = 20n * 10n ** 18n - await assert.rejects(requestPriceIfNeededAndStageOperation(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.Liquidation, client.account.address, liquidationDebtAttoEth), /Receiver is target/) + await assert.rejects(requestPriceIfNeededAndStageOperation(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.Liquidation, client.account.address, liquidationDebtAttoEth), /Invalid staged route/) const targetVaultAfter = await getSecurityVault(client, securityPoolAddresses.securityPool, client.account.address) const targetClaimAfter = await getVaultRepClaim(client.account.address) diff --git a/solidity/ts/tests/peripherals/vaultAccounting.test.ts b/solidity/ts/tests/peripherals/vaultAccounting.test.ts index 819a8da1d..a25ee6853 100644 --- a/solidity/ts/tests/peripherals/vaultAccounting.test.ts +++ b/solidity/ts/tests/peripherals/vaultAccounting.test.ts @@ -661,7 +661,7 @@ describe('Peripherals: vault accounting', () => { test('oracle-staged collateral operations are rejected once escalation resolves', async () => { await finalizeQuestionAsYesWithoutFork() - await assert.rejects(requestPriceIfNeededAndStageOperation(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, 1n), /question already resolved, so staged operations are unavailable/) + await assert.rejects(requestPriceIfNeededAndStageOperation(client, securityPoolAddresses.priceOracleManagerAndOperatorQueuer, OperationType.WithdrawRep, client.account.address, 1n), /Invalid staged operation/) }) test('withdrawFromEscalationGame gives later safety-boundary deposits a pro-rata share of the binding-capital reward pool', async () => { diff --git a/solidity/ts/tests/peripheralsInvariant.test.ts b/solidity/ts/tests/peripheralsInvariant.test.ts index 7681aee19..f1cb54bab 100644 --- a/solidity/ts/tests/peripheralsInvariant.test.ts +++ b/solidity/ts/tests/peripheralsInvariant.test.ts @@ -767,7 +767,7 @@ describe('Peripherals invariant harness', () => { enabled: () => completed.has('actor B opens first-pool interest'), execute: async () => { const before = await readModelSnapshot() - await assert.rejects(queueLiquidationAtForcedPrice(actorA, firstPool.priceOracleManagerAndOperatorQueuer, actorA.account.address, 10n * 10n ** 18n, 10n * 10n ** 18n), /Receiver is target/) + await assert.rejects(queueLiquidationAtForcedPrice(actorA, firstPool.priceOracleManagerAndOperatorQueuer, actorA.account.address, 10n * 10n ** 18n, 10n * 10n ** 18n), /Invalid staged route/) assert.deepStrictEqual(await readModelSnapshot(), before, 'rejected receiver-target alias should preserve the complete accounting model') }, }, diff --git a/solidity/ts/tests/priceOracleSecurity.test.ts b/solidity/ts/tests/priceOracleSecurity.test.ts index 4980ddacc..6c4b5e206 100644 --- a/solidity/ts/tests/priceOracleSecurity.test.ts +++ b/solidity/ts/tests/priceOracleSecurity.test.ts @@ -45,6 +45,7 @@ import { createCompleteSet, depositRepToVault, depositToEscalationGame, getSettl import { peripherals_openOracle_OpenOracle_OpenOracle, peripherals_EscalationGame_EscalationGame, + peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard, peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, peripherals_SecurityPool_SecurityPool, peripherals_tokens_ShareToken_ShareToken, @@ -176,6 +177,7 @@ describe('Price Oracle Refund Security Tests', () => { const currentTimestamp = dateToBigintSeconds(new Date()) const questionEndDate = currentTimestamp + 365n * DAY let priceOracle: Address + let operationBountyBoard: Address let questionId: bigint const genesisUniverse = 0n const statoblastSecurityMultiplierBps = 20_000n @@ -287,6 +289,13 @@ describe('Price Oracle Refund Security Tests', () => { const addresses = getSecurityPoolAddresses(addressString(0x0n), genesisUniverse, questionId, statoblastSecurityMultiplierBps) priceOracle = addresses.priceOracleManagerAndOperatorQueuer securityPool = addresses.securityPool + operationBountyBoard = await client.readContract({ + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + address: priceOracle, + functionName: 'operationBountyBoard', + args: [], + }) + assert.notStrictEqual(operationBountyBoard, zeroAddress, 'every coordinator should have an operation bounty board') }) const queueStagedOperation = async (operation: OperationType, targetVault: Address, amount: bigint, validForSeconds: bigint, value = 0n) => await requestPriceIfNeededAndStageOperationWithValue(client, priceOracle, operation, targetVault, amount, validForSeconds, value) @@ -425,6 +434,207 @@ describe('Price Oracle Refund Security Tests', () => { assert.strictEqual(replayed.pendingSettlementOperationCount, pendingSettlementOperationCount, `${context}: pending settlement count replay mismatch`) } + test('factory-created bounty boards are initialized exactly once and implementations stay locked', async () => { + const repToken = addressString(GENESIS_REPUTATION_TOKEN) + assert.strictEqual((await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'coordinator', args: [] })).toLowerCase(), priceOracle.toLowerCase(), 'the factory should initialize the clone with its coordinator') + assert.strictEqual((await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'reputationToken', args: [] })).toLowerCase(), repToken.toLowerCase(), 'the factory should initialize the clone with coordinator REP') + assert.strictEqual((await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'weth', args: [] })).toLowerCase(), WETH_ADDRESS.toLowerCase(), 'the factory should initialize the clone with WETH') + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'nextOperationBountyId', args: [] }), 1n, 'the initialized clone should start bounty ids at one') + await assert.rejects( + client.simulateContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'initialize', + args: [priceOracle, repToken, WETH_ADDRESS], + account: client.account, + }), + /Operation bounty board is already initialized/, + ) + + const implementation = await deployContract( + encodeDeployData({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + bytecode: applyLibraries(peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.evm.bytecode.object), + }), + ) + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: implementation, functionName: 'nextOperationBountyId', args: [] }), 0n, 'the implementation must remain unusable rather than becoming an independently initialized board') + await assert.rejects( + client.simulateContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: implementation, + functionName: 'initialize', + args: [priceOracle, repToken, WETH_ADDRESS], + account: client.account, + }), + /Operation bounty board is already initialized/, + ) + }) + + test('an operator can fund a creator withdrawal and claim its WETH operation bounty', async () => { + const operator = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) + const rewardAmount = 2n * 10n ** 18n + const proposedRepPerEthPrice = 10n ** 18n + const requestedInitialWeth = await client.readContract({ + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + address: priceOracle, + functionName: 'minimumToken1ReportAttoEth', + args: [], + }) + const requestCost = await getRequestPriceCostAttoEth(client, priceOracle) + + await wrapWeth(client, rewardAmount) + await approveToken(client, WETH_ADDRESS, operationBountyBoard) + await wrapWeth(operator, requestedInitialWeth) + await approveToken(operator, WETH_ADDRESS, priceOracle) + await approveToken(operator, addressString(GENESIS_REPUTATION_TOKEN), priceOracle) + + const postHash = await client.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'postOperationBounty', + args: [OperationType.WithdrawRep, client.account.address, 10n ** 18n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, WETH_ADDRESS, rewardAmount, currentTimestamp + DAY, 0n, 0n], + }) + await client.waitForTransactionReceipt({ hash: postHash }) + + const acceptHash = await operator.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'acceptOperationBounty', + args: [1n, proposedRepPerEthPrice, 0n], + value: requestCost, + }) + await operator.waitForTransactionReceipt({ hash: acceptHash }) + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'operationExecutionStatuses', args: [1n] }), 1n, 'accepted bounty should remain pending until the oracle report settles') + await assert.rejects( + operator.simulateContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'recordOperationResult', + args: [1n, true], + account: operator.account, + }), + /Only coordinator/, + ) + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'operationExecutionStatuses', args: [1n] }), 1n, 'a rejected result forgery should leave the bounty pending') + await assert.rejects( + operator.simulateContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'claimOperationBounty', + args: [1n], + account: operator.account, + }), + /Operation bounty cannot be claimed before successful execution/, + ) + + await handleOracleReporting(operator, mockWindow, priceOracle, proposedRepPerEthPrice) + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'operationExecutionStatuses', args: [1n] }), 2n, 'successful execution should make the bounty claimable') + + const operatorWethBeforeClaim = await getERC20Balance(operator, WETH_ADDRESS, operator.account.address) + const claimHash = await operator.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'claimOperationBounty', + args: [1n], + }) + await operator.waitForTransactionReceipt({ hash: claimHash }) + assert.strictEqual(await getERC20Balance(operator, WETH_ADDRESS, operator.account.address), operatorWethBeforeClaim + rewardAmount, 'the operator should receive the escrowed bounty') + }) + + test('an expired liquidation bounty preserves attoETH units and refunds its REP escrow', async () => { + const operator = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) + const targetVault = addressString(TEST_ADDRESSES[2]) + const repToken = addressString(GENESIS_REPUTATION_TOKEN) + const requestedDebtAttoEth = 7n * 10n ** 17n + const validForSeconds = 60n + const rewardAmountAttoRep = 3n * 10n ** 18n + const proposedRepPerEthPrice = 10n ** 18n + const requestedInitialWeth = await client.readContract({ + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + address: priceOracle, + functionName: 'minimumToken1ReportAttoEth', + args: [], + }) + const requestCost = await getRequestPriceCostAttoEth(client, priceOracle) + const creatorRepBeforePost = await getERC20Balance(client, repToken, client.account.address) + + await approveToken(client, repToken, operationBountyBoard) + await wrapWeth(operator, requestedInitialWeth) + await approveToken(operator, WETH_ADDRESS, priceOracle) + await approveToken(operator, repToken, priceOracle) + + const postHash = await client.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'postOperationBounty', + args: [OperationType.Liquidation, targetVault, requestedDebtAttoEth, validForSeconds, repToken, rewardAmountAttoRep, (await mockWindow.getTime()) + DAY, 0n, 0n], + }) + await client.waitForTransactionReceipt({ hash: postHash }) + assert.strictEqual(await getERC20Balance(client, repToken, client.account.address), creatorRepBeforePost - rewardAmountAttoRep, 'posting should transfer the exact REP reward into escrow') + assert.strictEqual(await getERC20Balance(client, repToken, operationBountyBoard), rewardAmountAttoRep, 'the bounty board should hold the REP escrow') + + const postedBounty = await client.readContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'operationBounties', + args: [1n], + }) + assert.strictEqual(postedBounty[2], BigInt(OperationType.Liquidation), 'the bounty should retain the liquidation operation type') + assert.strictEqual(postedBounty[3].toLowerCase(), targetVault.toLowerCase(), 'the bounty should retain the liquidation target') + assert.strictEqual(postedBounty[4], requestedDebtAttoEth, 'the bounty amount should remain denominated in attoETH without rescaling') + + const acceptHash = await operator.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'acceptOperationBounty', + args: [1n, proposedRepPerEthPrice, 0n], + value: requestCost, + }) + await operator.waitForTransactionReceipt({ hash: acceptHash }) + + const acceptedBounty = await client.readContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'operationBounties', + args: [1n], + }) + const operationId = acceptedBounty[11] + const stagedOperation = await getStagedOperation(client, priceOracle, operationId) + assert.strictEqual(stagedOperation[0], BigInt(OperationType.Liquidation), 'the coordinator should stage a liquidation') + assert.strictEqual(stagedOperation[3].toLowerCase(), targetVault.toLowerCase(), 'the coordinator should stage the requested target vault') + assert.strictEqual(stagedOperation[4], requestedDebtAttoEth, 'the coordinator should receive the exact requested debt in attoETH') + assert.strictEqual(await getPendingReportId(client, priceOracle), acceptedBounty[12], 'the oracle report should still be pending while the operation expires') + + const expirationTimestamp = stagedOperation[5] + BigInt(ORACLE_SETTLEMENT_TIME) + stagedOperation[6] + await mockWindow.setTime(expirationTimestamp) + await assert.rejects( + client.simulateContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'refundOperationBounty', + args: [1n], + account: client.account, + }), + /Staged operation active/, + 'the operation should remain active at the exact settlement-plus-validity boundary', + ) + + await mockWindow.setTime(expirationTimestamp + 1n) + const refundHash = await client.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: operationBountyBoard, + functionName: 'refundOperationBounty', + args: [1n], + }) + await client.waitForTransactionReceipt({ hash: refundHash }) + assert.strictEqual(await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'operationExecutionStatuses', args: [1n] }), 3n, 'expiration should record failed execution') + assert.strictEqual((await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: operationBountyBoard, functionName: 'operationBounties', args: [1n] }))[13], 4n, 'the bounty should transition to refunded') + assert.strictEqual(await getERC20Balance(client, repToken, client.account.address), creatorRepBeforePost, 'refund should return the full REP escrow to the creator') + assert.strictEqual(await getERC20Balance(client, repToken, operationBountyBoard), 0n, 'the board should not retain refunded REP') + assert.strictEqual((await getStagedOperation(client, priceOracle, operationId))[1], zeroAddress, 'expiration should consume the staged operation') + assert.strictEqual(await getPendingReportId(client, priceOracle), acceptedBounty[12], 'refunding an expired operation should not settle or cancel its pending oracle report') + }) + test('coordinator dynamically sizes the minimum WETH report side from the current base fee', async () => { const sizingConfigurationAbi = [ { @@ -756,7 +966,7 @@ describe('Price Oracle Refund Security Tests', () => { const invalidArgs = getOracleCoordinatorConstructorArgs() invalidArgs[6] = MAX_ORACLE_INITIAL_REPORT_PRIORITY_FEE_ATTO_ETH_PER_GAS + 1n - await assert.rejects(async () => await deployContract(encodeOracleCoordinatorDeployData(invalidArgs)), /initial report priority fee exceeds openoracle limits/i) + await assert.rejects(async () => await deployContract(encodeOracleCoordinatorDeployData(invalidArgs)), /invalid oracle configuration/i) }) test('coordinator constructor rejects unsafe oracle risk parameters', async () => { @@ -811,7 +1021,7 @@ describe('Price Oracle Refund Security Tests', () => { }> = [ { args: [...baseArgs.slice(0, 6), 0n, ...baseArgs.slice(7)] as OracleCoordinatorConstructorArgs, - message: /initial priority fee zero/i, + message: /invalid oracle configuration/i, }, { args: [...baseArgs.slice(0, 15), false, ...baseArgs.slice(16)] as OracleCoordinatorConstructorArgs, @@ -819,35 +1029,35 @@ describe('Price Oracle Refund Security Tests', () => { }, { args: buildArgsWithSizingParameters(0n, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, ORACLE_PROTOCOL_FEE, ORACLE_FEE_PERCENTAGE), - message: /dispute gas units zero/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithSizingParameters(ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, 9999n, ORACLE_PROTOCOL_FEE, ORACLE_FEE_PERCENTAGE), - message: /open oracle security multiplier must be at least one hundred percent/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithSizingParameters(ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, 10000001n, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, ORACLE_PROTOCOL_FEE, ORACLE_FEE_PERCENTAGE), - message: /target price error cannot exceed one hundred percent/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithSizingParameters(ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, OPEN_ORACLE_SECURITY_MULTIPLIER_BPS, 490000, 10000), - message: /oracle fees must be below the target price error/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithSizingParameters(ORACLE_GAS_UNITS_FOR_ONE_DISPUTE, ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE, (2n ** 256n - 1n) / (10000000n + ORACLE_TARGET_PRICE_ERROR_FOR_DISPUTE) + 1n, ORACLE_PROTOCOL_FEE, ORACLE_FEE_PERCENTAGE), - message: /open oracle security multiplier is too large/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithRiskParameters(0n, ORACLE_MAX_SETTLEMENT_BASE_FEE_MULTIPLIER_BPS, ORACLE_MIN_LIQUIDATION_PRICE_DISTANCE_BPS), - message: /escalation multiplier zero/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithRiskParameters(ORACLE_ESCALATION_HALT_MULTIPLIER_BPS, 9999n, ORACLE_MIN_LIQUIDATION_PRICE_DISTANCE_BPS), - message: /max settlement base fee multiplier must be at least one hundred percent/i, + message: /invalid oracle configuration/i, }, { args: buildArgsWithRiskParameters(ORACLE_ESCALATION_HALT_MULTIPLIER_BPS, ORACLE_MAX_SETTLEMENT_BASE_FEE_MULTIPLIER_BPS, 10001n), - message: /minimum liquidation price distance cannot exceed one hundred percent/i, + message: /invalid oracle configuration/i, }, ] @@ -873,7 +1083,7 @@ describe('Price Oracle Refund Security Tests', () => { functionName: 'setSecurityPool', args: [configuredPool], }), - /Security pool already set/, + /Invalid coordinator setup/, ) await assert.rejects( client.writeContract({ @@ -910,7 +1120,7 @@ describe('Price Oracle Refund Security Tests', () => { args: [1n, 0n], value: costAttoEth - 1n, }), - /Oracle bounty too small/, + /Invalid oracle request/, ) await assert.rejects( client.writeContract({ @@ -920,7 +1130,7 @@ describe('Price Oracle Refund Security Tests', () => { args: [0n, 0n], value: costAttoEth, }), - /Initial oracle price zero/, + /Invalid oracle request/, ) await assert.rejects(recoverSettledPendingReport(client, priceOracle), /No report to recover/) await assert.rejects( @@ -954,7 +1164,7 @@ describe('Price Oracle Refund Security Tests', () => { args: [1n, 1n << 128n], value: costAttoEth, }), - /WETH report exceeds uint128/, + /Oracle value exceeds limit/, ) await assert.rejects( client.writeContract({ @@ -964,7 +1174,7 @@ describe('Price Oracle Refund Security Tests', () => { args: [(1n << 128n) * 10n ** 18n, 1n], value: costAttoEth, }), - /REP report exceeds uint128/, + /Oracle value exceeds limit/, ) await assert.rejects( client.writeContract({ @@ -974,11 +1184,11 @@ describe('Price Oracle Refund Security Tests', () => { args: [1n, (1n << 128n) / 10n + 1n], value: costAttoEth, }), - /Oracle escalation halt amount exceeds uint128 maximum/, + /Oracle value exceeds limit/, ) await requestPrice(client, priceOracle) - await assert.rejects(requestPriceWithValue(client, priceOracle, costAttoEth), /Oracle request already pending/) + await assert.rejects(requestPriceWithValue(client, priceOracle, costAttoEth), /Invalid oracle request/) }) test('staged operation public guards cover argument geometry, request funding, and execution prerequisites', async () => { @@ -993,11 +1203,11 @@ describe('Price Oracle Refund Security Tests', () => { value, }) - await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 0n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Staged operation amount must be non-zero/) - await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, 0n), /Staged operation timeout must be positive/) - await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS + 1n), /Staged operation timeout exceeds the maximum allowed/) - await assert.rejects(stage(OperationType.WithdrawRep, otherVault, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Self operation target mismatch/) - await assert.rejects(stage(OperationType.Liquidation, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Receiver is target/) + await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 0n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Invalid staged operation/) + await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, 0n), /Invalid staged operation/) + await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS + 1n), /Invalid staged operation/) + await assert.rejects(stage(OperationType.WithdrawRep, otherVault, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Invalid staged route/) + await assert.rejects(stage(OperationType.Liquidation, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Invalid staged route/) await assert.rejects( client.writeContract({ abi: coordinatorAbi, @@ -1014,7 +1224,7 @@ describe('Price Oracle Refund Security Tests', () => { functionName: 'stagedOperationCounter', args: [], }) - await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Not enough ETH was provided to request a fresh oracle price/) + await assert.rejects(stage(OperationType.WithdrawRep, client.account.address, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS), /Invalid oracle request/) assert.strictEqual( await client.readContract({ abi: coordinatorAbi, @@ -1122,7 +1332,7 @@ describe('Price Oracle Refund Security Tests', () => { }), costAttoEth + 1n, ), - /Oracle coordinator failed to refund excess ETH bounty/, + /ETH refund failed/, ) assert.strictEqual(await getPendingReportId(client, priceOracle), 0n, 'failed direct refund must roll back the pending report') assert.strictEqual( @@ -1155,7 +1365,7 @@ describe('Price Oracle Refund Security Tests', () => { }), costAttoEth + 1n, ), - /Oracle coordinator failed to return unused ETH/, + /ETH refund failed/, ) assert.strictEqual(await getPendingReportId(client, priceOracle), 0n, 'failed staged refund must roll back the pending report') assert.strictEqual( @@ -1184,7 +1394,7 @@ describe('Price Oracle Refund Security Tests', () => { functionName: 'getSettlementCallbackGasLimit', args: [], }), - /Callback gas exceeds uint32/, + /Oracle value exceeds limit/, ) }) @@ -1230,7 +1440,7 @@ describe('Price Oracle Refund Security Tests', () => { value: requestEthCost, gasPrice: baseFeeAttoEthPerGas, }), - /Oracle settler reward exceeds uint96 maximum/, + /Oracle value exceeds limit/, ) } finally { await mockWindow.setNextBlockBaseFeePerGasToZero() @@ -1956,7 +2166,7 @@ describe('Price Oracle Refund Security Tests', () => { await settlePendingReportWithPrice(10n ** 18n) assert.strictEqual(await getIsPriceValid(client, priceOracle), true, 'test setup should seed a fresh cached oracle price') - await assert.rejects(async () => await requestPrice(client, priceOracle), /Oracle price already fresh/i) + await assert.rejects(async () => await requestPrice(client, priceOracle), /Invalid oracle request/i) }) test('expired pending auto-execute slots do not block later valid oracle settlements', async () => { @@ -2087,7 +2297,7 @@ describe('Price Oracle Refund Security Tests', () => { const attackerClient = createWriteClient(mockWindow, TEST_ADDRESSES[1], 0) const costAttoEth = await getRequestPriceCostAttoEth(attackerClient, priceOracle) - await assert.rejects(async () => await requestPriceIfNeededAndStageOperationWithValue(attackerClient, priceOracle, OperationType.WithdrawRep, attackerClient.account.address, repDeposit, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, costAttoEth), /withdraw amount has no effect/i) + await assert.rejects(async () => await requestPriceIfNeededAndStageOperationWithValue(attackerClient, priceOracle, OperationType.WithdrawRep, attackerClient.account.address, repDeposit, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, costAttoEth), /invalid staged operation/i) const pendingReportId = await getPendingReportId(client, priceOracle) const pendingSettlementOperationCount = await getPendingSettlementOperationCount(client, priceOracle) @@ -2157,7 +2367,7 @@ describe('Price Oracle Refund Security Tests', () => { const nonLiquidationOperations = [OperationType.WithdrawRep] for (const operation of nonLiquidationOperations) { - await assert.rejects(async () => await requestPriceIfNeededAndStageOperationWithValue(client, priceOracle, operation, otherVault, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, 0n), /Self operation target mismatch/) + await assert.rejects(async () => await requestPriceIfNeededAndStageOperationWithValue(client, priceOracle, operation, otherVault, 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, 0n), /Invalid staged route/) } }) diff --git a/ui/css/index.css b/ui/css/index.css index 145e074af..df4ad0a72 100644 --- a/ui/css/index.css +++ b/ui/css/index.css @@ -2631,7 +2631,8 @@ button.wallet-asset-action-pending:disabled { min-width: 0; } -.entity-card-copy h3 { +.entity-card-copy h3, +.entity-card-copy .entity-card-title { margin: 0; font-size: var(--font-card-title); line-height: 1.1; diff --git a/ui/ts/app/App.tsx b/ui/ts/app/App.tsx index 3e10e7687..bc4ddba7b 100644 --- a/ui/ts/app/App.tsx +++ b/ui/ts/app/App.tsx @@ -215,7 +215,26 @@ export function App() { setSecurityVaultForm, withdrawRep, } = useSecurityVaultOperations({ ...walletScopedHookConfig, enabled: route === 'security-pools' && canReadOnchainData, selectedSecurityPoolAddress: securityPoolAddress }) - const { executePendingPoolOperation, loadingPoolOracleManager, loadPoolOracleManager, poolOracleActiveAction, poolOracleManagerDetails, poolOracleManagerError, poolOracleManagerErrorAddress, poolPriceOracleResult, requestPoolPrice } = usePriceOracleManager(walletScopedHookConfig) + const { + acceptPoolOperationBounty, + claimPoolOperationBounty, + clearPoolOperationBountyLookupError, + executePendingPoolOperation, + loadingPoolOracleManager, + loadingPoolOperationBounty, + loadPoolOracleManager, + loadPoolOperationBounty, + poolOracleActiveAction, + poolOracleActiveBountyId, + poolOracleManagerDetails, + poolOracleManagerError, + poolOracleManagerErrorAddress, + poolOperationBountyLookupError, + poolPriceOracleResult, + postPoolOperationBounty, + refundPoolOperationBounty, + requestPoolPrice, + } = usePriceOracleManager(walletScopedHookConfig) const { approveToken1, approveToken2, @@ -669,11 +688,18 @@ export function App() { refreshSelectedPoolData(selectedSecurityPoolAddress) }, onQueueLiquidation: (managerAddress: Address, selectedSecurityPoolAddress: Address) => void queueLiquidation(managerAddress, selectedSecurityPoolAddress), + onAcceptPoolOperationBounty: (managerAddress, bountyId) => void acceptPoolOperationBounty(managerAddress, bountyId), + onClaimPoolOperationBounty: (managerAddress, bountyId) => void claimPoolOperationBounty(managerAddress, bountyId), + onClearPoolOperationBountyLookupError: clearPoolOperationBountyLookupError, onExecutePendingPoolOperation: (managerAddress: Address, operationId: bigint, securityPoolAddress: Address) => void executePendingPoolOperation(managerAddress, operationId, securityPoolAddress), + onPostPoolOperationBounty: (managerAddress, bounty) => void postPoolOperationBounty(managerAddress, bounty), + onRefundPoolOperationBounty: (managerAddress, bountyId) => void refundPoolOperationBounty(managerAddress, bountyId), loadingPoolOracleManager, + loadingPoolOperationBounty, loadingLiquidationFundingPreview, loadingSecurityPools, onLoadPoolOracleManager: (managerAddress: Address) => void loadPoolOracleManager(managerAddress), + onLoadPoolOperationBounty: (managerAddress: Address, bountyId: bigint) => void loadPoolOperationBounty(managerAddress, bountyId), onRequestPoolPrice: (managerAddress: Address, securityPoolAddress: Address, reviewedRequestValueAttoEth: bigint) => void requestPoolPrice(managerAddress, securityPoolAddress, reviewedRequestValueAttoEth), onRefreshSelectedPoolData: refreshSelectedPoolData, onSelectedPoolViewChange: setSelectedPoolView, @@ -688,9 +714,11 @@ export function App() { securityPoolLiquidationError, securityPoolOverviewResult, poolOracleActiveAction, + poolOracleActiveBountyId, poolOracleManagerDetails, poolOracleManagerError, poolOracleManagerErrorAddress, + poolOperationBountyLookupError, poolPriceOracleResult, selectedPoolRefreshNonce, universeForkTime: zoltarUniverse?.forkTime, diff --git a/ui/ts/app/components/GlobalTransactionTray.tsx b/ui/ts/app/components/GlobalTransactionTray.tsx index 1ddafe966..0e03651a7 100644 --- a/ui/ts/app/components/GlobalTransactionTray.tsx +++ b/ui/ts/app/components/GlobalTransactionTray.tsx @@ -89,7 +89,7 @@ export function GlobalTransactionTray({ routeKey, transaction }: GlobalTransacti if (transactionOriginRef.current.transactionKey !== transactionKey) transactionOriginRef.current = { routeKey, transactionKey } if (transactionDismissKey !== undefined && transactionDismissKey === dismissedKey) return undefined const canDismiss = transaction.tone !== 'awaiting-wallet' && transaction.tone !== 'pending' && transaction.tone !== 'preparing' && transactionDismissKey !== undefined - const compact = canDismiss && routeKey !== undefined && transactionOriginRef.current.routeKey !== undefined && routeKey !== transactionOriginRef.current.routeKey + const compact = canDismiss && (transaction.tone === 'success' || (routeKey !== undefined && transactionOriginRef.current.routeKey !== undefined && routeKey !== transactionOriginRef.current.routeKey)) const dismiss = () => { if (transactionDismissKey === undefined) return if (shouldRememberDismissal(transaction)) rememberDismissal(transactionDismissKey) diff --git a/ui/ts/copy/securityPool.ts b/ui/ts/copy/securityPool.ts index 2dbb3e818..da7a5b22d 100644 --- a/ui/ts/copy/securityPool.ts +++ b/ui/ts/copy/securityPool.ts @@ -87,6 +87,74 @@ export const requestPriceReviewDescription = 'Review the selected pool and requi export const loadOracleBeforePriceReview = 'Loading price oracle details…' export const requestPricePendingReportRisk = 'This creates a pending Open Oracle report for the selected pool. Another request cannot start until that report completes.' export const requestPriceFundingRisk = 'You Pay includes the 20% request buffer transferred with this transaction. Wallet gas and any additional initial-report REP or WETH funding are checked and submitted separately.' +export const operationBounties = 'Operation Bounties' +export const operationBountiesDetail = 'Creators escrow REP or WETH for a pool operation. A permissionless operator supplies the Open Oracle reporting capital and earns the reward only after successful execution.' +export const operationBountyAcceptanceDetail = 'Acceptance revalidates the operation and, when the price is stale, requires room in the four-operation settlement batch.' +export const postOperationBounty = 'Post an operation bounty' +export const operation = 'Operation' +export const executionWindow = 'Execution Window' +export const acceptanceWindow = 'Acceptance Window' +export const rewardToken = 'Reward Token' +export const rewardAmount = 'Reward Amount' +export const minimumInitialWeth = 'Minimum Initial Report WETH' +export const maximumInitialWeth = 'Maximum Initial Report WETH' +export const noMinimum = 'No minimum' +export const noMaximum = 'No maximum' +export const initialWethBoundsDetail = "Optional bounds apply to either a proposed new report's WETH amount or the current WETH amount of an existing pending report." +export const executionWindowDetail = 'The cancellation deadline is fixed when accepted: queued time + one oracle settlement window + this execution window. Disputes do not extend it.' +export const selfTargetedBountyDetail = 'This operation targets the connected wallet’s own vault.' +export const postBounty = 'Post Bounty' +export const postingBounty = 'Posting bounty…' +export const availableOperationBounties = 'Existing bounties' +export const operationBountyLookupDetail = 'The newest 25 bounties appear automatically. Enter any bounty ID to recover an older creator or operator action.' +export const bountyId = 'Bounty ID' +export const loadBounty = 'Open Bounty' +export const loadingBounty = 'Loading bounty…' +export const enterValidBountyId = 'Enter a positive bounty ID.' +export const bountyPaid = 'Paid' +export const bountyRefunded = 'Refunded' +export const bountyOpen = 'Open' +export const bountyAcceptanceExpiredState = 'Acceptance expired' +export const bountyReadyToClaim = 'Ready to claim' +export const bountyFailed = 'Execution failed' +export const bountyInProgress = 'In progress' +export const bountyExecutionExpired = 'Execution expired' +export const creator = 'Creator' +export const operator = 'Operator' +export const reward = 'Reward' +export const acceptBy = 'Accept By' +export const report = 'Report' +export const acceptAndFund = 'Accept & Fund Report' +export const acceptingBounty = 'Accepting bounty…' +export const claimBounty = 'Claim Bounty' +export const claimingBounty = 'Claiming bounty…' +export const refundBounty = 'Refund Bounty' +export const cancelAndRefundBounty = 'Cancel & Refund' +export const refundingBounty = 'Refunding bounty…' +export const noBounties = 'No bounties' +export const noBountiesDetail = 'No operation bounties have been posted for this pool.' +export const connectWalletForBounty = 'Connect a wallet before posting an operation bounty.' +export const acceptBountyWalletReason = 'Connect a wallet to accept this bounty.' +export const waitForChainTime = 'Wait for the current chain time to load.' +export const refreshBountyTokens = 'Refresh the oracle manager to load its REP and WETH tokens.' +export const enterValidBountyTarget = 'Enter a valid target vault address.' +export const liquidationBountyTargetMustDiffer = 'The liquidation target must differ from the bounty creator’s vault.' +export const enterValidBountyAmount = 'Enter a valid operation amount.' +export const enterValidBountyReward = 'Enter a reward greater than zero.' +export const enterValidBountyTimeout = 'Enter an execution window from 1 to 5 minutes.' +export const enterValidBountyDeadline = 'Enter an acceptance window greater than zero.' +export const enterValidInitialWethBounds = 'Enter valid decimal initial-report WETH bounds or leave them blank.' +export const enterOrderedInitialWethBounds = 'The minimum initial-report WETH cannot exceed the maximum.' +export const formatOperationAmountLabel = (unit: string) => `Amount (${unit})` +export const bountyAcceptanceExpired = 'This bounty’s acceptance window has expired.' +export const bountySettlementQueueFull = 'This bounty cannot be accepted while the pending settlement queue is full.' +export const failedBountyCannotBeClaimed = 'Failed operation bounties cannot be claimed.' +export const expiredBountyCannotBeClaimed = 'Expired operation bounties cannot be claimed.' +export const waitForBountyExecution = 'Wait for the staged operation to execute successfully.' +export const waitForBountyFailure = 'The creator can refund after the operation fails.' +export const formatOperationBountyLabel = (bountyId: string, operationLabel: string) => `Bounty #${bountyId} · ${operationLabel}` +export const formatExecutionWindowDetail = (minutes: string) => `Fixed cancellation deadline: queued time + oracle settlement time + ${minutes} minutes. Disputes do not extend it.` +export const formatRefundAvailableAt = (timestamp: string) => `Cancellation becomes available after ${timestamp}.` export const liquidationWorkflowDescription = 'Inspect the liquidation quote, timeout, and execution path before queueing liquidation.' export const selectedPool = 'Selected Pool' export const changePool = 'Change pool' diff --git a/ui/ts/features/open-oracle/components/OperationBountyBoard.tsx b/ui/ts/features/open-oracle/components/OperationBountyBoard.tsx new file mode 100644 index 000000000..0822488c1 --- /dev/null +++ b/ui/ts/features/open-oracle/components/OperationBountyBoard.tsx @@ -0,0 +1,344 @@ +import { useState } from 'preact/hooks' +import type { Address } from '@zoltar/shared/ethereum' +import * as commonCopy from '../../../copy/common.js' +import * as openOracleCopy from '../../../copy/openOracle.js' +import * as securityPoolCopy from '../../../copy/securityPool.js' +import { AddressValue } from '../../../components/AddressValue.js' +import { Badge } from '../../../components/Badge.js' +import { CurrencyValue } from '../../../components/CurrencyValue.js' +import { ErrorNotice } from '../../../components/ErrorNotice.js' +import { FormInput } from '../../../components/FormInput.js' +import { MetricField } from '../../../components/MetricField.js' +import { MetricGrid } from '../../../components/MetricGrid.js' +import { SectionBlock } from '../../../components/SectionBlock.js' +import { StateHint } from '../../../components/StateHint.js' +import { TransactionActionButton } from '../../../components/TransactionActionButton.js' +import { formatTimestamp } from '../../../lib/formatters.js' +import { sameAddress } from '../../../lib/address.js' +import { tryParseAddressInput } from '../../../lib/inputs.js' +import { tryParseBigIntInput, tryParseRepAmountInput } from '../../markets/lib/marketForm.js' +import type { OpenOracleActionResult, OracleManagerDetails, OracleOperationBounty, OracleOperationBountyInput, OracleQueueOperation } from '../../../types/contracts.js' + +type OperationBountyBoardProps = { + accountAddress: Address | undefined + activeAction: OpenOracleActionResult['action'] | undefined + activeBountyId: bigint | undefined + currentTimestamp: bigint | undefined + isMainnet: boolean + loadingBounty: boolean + lookupError: string | undefined + managerDetails: OracleManagerDetails + onAccept: (managerAddress: Address, bountyId: bigint) => void + onClaim: (managerAddress: Address, bountyId: bigint) => void + onClearLookupError: () => void + onLoad: (managerAddress: Address, bountyId: bigint) => void + onPost: (managerAddress: Address, bounty: OracleOperationBountyInput) => void + onRefund: (managerAddress: Address, bountyId: bigint) => void +} + +function getOperationLabel(operation: OracleQueueOperation) { + if (operation === 'liquidation') return securityPoolCopy.liquidation + return securityPoolCopy.withdrawRep +} + +function getOperationUnit(operation: OracleQueueOperation) { + return operation === 'withdrawRep' ? commonCopy.rep : commonCopy.eth +} + +function getBountyStateLabel(bounty: OracleOperationBounty, acceptanceExpired: boolean, refundExpired: boolean) { + if (bounty.state === 'paid') return securityPoolCopy.bountyPaid + if (bounty.state === 'refunded') return securityPoolCopy.bountyRefunded + if (bounty.state === 'open') return acceptanceExpired ? securityPoolCopy.bountyAcceptanceExpiredState : securityPoolCopy.bountyOpen + if (bounty.executionStatus === 'succeeded') return securityPoolCopy.bountyReadyToClaim + if (bounty.executionStatus === 'failed') return securityPoolCopy.bountyFailed + if (refundExpired) return securityPoolCopy.bountyExecutionExpired + return securityPoolCopy.bountyInProgress +} + +function getBountyTone(bounty: OracleOperationBounty, acceptanceExpired: boolean, refundExpired: boolean) { + if (bounty.state === 'paid' || bounty.executionStatus === 'succeeded') return 'ok' as const + if (bounty.state === 'refunded') return 'muted' as const + if (bounty.executionStatus === 'failed' || (bounty.state === 'open' && acceptanceExpired) || refundExpired) return 'blocked' as const + return 'warning' as const +} + +function resolveBountyTargetVault(operation: OracleQueueOperation, targetVault: string, accountAddress: Address | undefined) { + if (operation !== 'liquidation') return accountAddress + return tryParseAddressInput(targetVault) +} + +function getAcceptGuardMessage(accountAddress: Address | undefined, isMainnet: boolean, acceptanceExpired: boolean, settlementQueueFull: boolean) { + if (accountAddress === undefined) return securityPoolCopy.acceptBountyWalletReason + if (!isMainnet) return commonCopy.mainnetRequiredReason + if (acceptanceExpired) return securityPoolCopy.bountyAcceptanceExpired + if (settlementQueueFull) return securityPoolCopy.bountySettlementQueueFull + return undefined +} + +function getClaimGuardMessage(isMainnet: boolean, executionStatus: OracleOperationBounty['executionStatus'], refundExpired: boolean) { + if (!isMainnet) return commonCopy.mainnetRequiredReason + if (executionStatus === 'succeeded') return undefined + if (executionStatus === 'failed') return securityPoolCopy.failedBountyCannotBeClaimed + if (refundExpired) return securityPoolCopy.expiredBountyCannotBeClaimed + return securityPoolCopy.waitForBountyExecution +} + +function getRefundGuardMessage(isMainnet: boolean, refundEnabled: boolean, refundAvailableAt: bigint | undefined) { + if (!isMainnet) return commonCopy.mainnetRequiredReason + if (refundEnabled) return undefined + if (refundAvailableAt === undefined) return securityPoolCopy.waitForBountyFailure + return securityPoolCopy.formatRefundAvailableAt(formatTimestamp(refundAvailableAt)) +} + +export function OperationBountyBoard({ accountAddress, activeAction, activeBountyId, currentTimestamp, isMainnet, loadingBounty, lookupError, managerDetails, onAccept, onClaim, onClearLookupError, onLoad, onPost, onRefund }: OperationBountyBoardProps) { + const [operation, setOperation] = useState('withdrawRep') + const [targetVault, setTargetVault] = useState('') + const [amount, setAmount] = useState('0') + const [validForMinutes, setValidForMinutes] = useState('5') + const [rewardToken, setRewardToken] = useState<'rep' | 'weth'>('weth') + const [rewardAmount, setRewardAmount] = useState('0') + const [acceptanceMinutes, setAcceptanceMinutes] = useState('60') + const [minimumInitialWeth, setMinimumInitialWeth] = useState('') + const [maximumInitialWeth, setMaximumInitialWeth] = useState('') + const [bountyIdInput, setBountyIdInput] = useState('') + const resolvedAmount = tryParseRepAmountInput(amount) + const resolvedRewardAmount = tryParseRepAmountInput(rewardAmount) + const resolvedValidForMinutes = tryParseBigIntInput(validForMinutes) + const resolvedAcceptanceMinutes = tryParseBigIntInput(acceptanceMinutes) + const resolvedMinimumInitialAttoWeth = minimumInitialWeth.trim() === '' ? 0n : tryParseRepAmountInput(minimumInitialWeth) + const resolvedMaximumInitialAttoWeth = maximumInitialWeth.trim() === '' ? 0n : tryParseRepAmountInput(maximumInitialWeth) + const resolvedBountyId = tryParseBigIntInput(bountyIdInput) + const resolvedTargetVault = resolveBountyTargetVault(operation, targetVault, accountAddress) + const resolvedRewardToken = rewardToken === 'rep' ? managerDetails.reputationTokenAddress : managerDetails.wethAddress + const operationUnit = getOperationUnit(operation) + const postGuardMessage = (() => { + if (accountAddress === undefined) return securityPoolCopy.connectWalletForBounty + if (!isMainnet) return commonCopy.mainnetRequiredReason + if (currentTimestamp === undefined) return securityPoolCopy.waitForChainTime + if (resolvedRewardToken === undefined) return securityPoolCopy.refreshBountyTokens + if (resolvedTargetVault === undefined) return securityPoolCopy.enterValidBountyTarget + if (operation === 'liquidation' && sameAddress(resolvedTargetVault, accountAddress)) return securityPoolCopy.liquidationBountyTargetMustDiffer + if (resolvedAmount === undefined || resolvedAmount <= 0n) return securityPoolCopy.enterValidBountyAmount + if (resolvedRewardAmount === undefined || resolvedRewardAmount <= 0n) return securityPoolCopy.enterValidBountyReward + if (resolvedValidForMinutes === undefined || resolvedValidForMinutes < 1n || resolvedValidForMinutes > 5n) return securityPoolCopy.enterValidBountyTimeout + if (resolvedAcceptanceMinutes === undefined || resolvedAcceptanceMinutes <= 0n) return securityPoolCopy.enterValidBountyDeadline + if (resolvedMinimumInitialAttoWeth === undefined || resolvedMaximumInitialAttoWeth === undefined) return securityPoolCopy.enterValidInitialWethBounds + if (resolvedMaximumInitialAttoWeth > 0n && resolvedMinimumInitialAttoWeth > resolvedMaximumInitialAttoWeth) return securityPoolCopy.enterOrderedInitialWethBounds + return undefined + })() + const postBounty = () => { + if ( + accountAddress === undefined || + currentTimestamp === undefined || + resolvedRewardToken === undefined || + resolvedTargetVault === undefined || + resolvedAmount === undefined || + resolvedRewardAmount === undefined || + resolvedValidForMinutes === undefined || + resolvedAcceptanceMinutes === undefined || + resolvedMinimumInitialAttoWeth === undefined || + resolvedMaximumInitialAttoWeth === undefined || + postGuardMessage !== undefined + ) + return + onPost(managerDetails.managerAddress, { + acceptanceDeadline: currentTimestamp + resolvedAcceptanceMinutes * 60n, + amount: resolvedAmount, + maximumInitialAttoWeth: resolvedMaximumInitialAttoWeth, + minimumInitialAttoWeth: resolvedMinimumInitialAttoWeth, + operation, + rewardAmount: resolvedRewardAmount, + rewardToken: resolvedRewardToken, + targetVault: resolvedTargetVault, + validForSeconds: resolvedValidForMinutes * 60n, + }) + } + const operationBounties = managerDetails.operationBounties ?? [] + const settlementQueueFull = !managerDetails.isPriceValid && BigInt(managerDetails.pendingSettlementOperationIds.length) >= managerDetails.pendingSettlementQueueCapacity + const bountyLookupGuardMessage = resolvedBountyId === undefined || resolvedBountyId <= 0n ? securityPoolCopy.enterValidBountyId : undefined + const loadBounty = () => { + if (resolvedBountyId === undefined || resolvedBountyId <= 0n) return + onLoad(managerDetails.managerAddress, resolvedBountyId) + } + + return ( + +

    {securityPoolCopy.operationBountiesDetail}

    +

    {securityPoolCopy.operationBountyAcceptanceDetail}

    + +
    +
    + + +
    + {operation === 'liquidation' ? ( + + ) : ( +

    {securityPoolCopy.selfTargetedBountyDetail}

    + )} +
    + + +
    +

    {securityPoolCopy.executionWindowDetail}

    +
    + + +
    +
    + + +
    +

    {securityPoolCopy.initialWethBoundsDetail}

    +
    + +
    +
    +
    + + +

    {securityPoolCopy.operationBountyLookupDetail}

    +
    + +
    + +
    + +
    +
    + {operationBounties.map(bounty => { + const isCreator = sameAddress(accountAddress, bounty.creator) + const isOperator = sameAddress(accountAddress, bounty.operator) + const acceptanceExpired = currentTimestamp !== undefined && currentTimestamp > bounty.acceptanceDeadline + const refundExpired = currentTimestamp !== undefined && bounty.refundAvailableAt !== undefined && currentTimestamp > bounty.refundAvailableAt + const refundEnabled = bounty.state === 'open' || bounty.executionStatus === 'failed' || refundExpired + const acceptGuardMessage = getAcceptGuardMessage(accountAddress, isMainnet, acceptanceExpired, settlementQueueFull) + const claimGuardMessage = getClaimGuardMessage(isMainnet, bounty.executionStatus, refundExpired) + const refundGuardMessage = getRefundGuardMessage(isMainnet, refundEnabled, bounty.refundAvailableAt) + const rewardSymbol = sameAddress(bounty.rewardToken, managerDetails.reputationTokenAddress) ? commonCopy.rep : commonCopy.weth + const bountyOperationUnit = getOperationUnit(bounty.operation) + return ( +
    +
    +
    +
    {securityPoolCopy.formatOperationBountyLabel(bounty.bountyId.toString(), getOperationLabel(bounty.operation))}
    +

    {securityPoolCopy.formatExecutionWindowDetail((bounty.validForSeconds / 60n).toString())}

    +
    + {getBountyStateLabel(bounty, acceptanceExpired, refundExpired)} +
    + + + + + + + + + + + + + + {bounty.minimumInitialAttoWeth === 0n ? securityPoolCopy.noMinimum : } + {bounty.maximumInitialAttoWeth === 0n ? securityPoolCopy.noMaximum : } + {formatTimestamp(bounty.acceptanceDeadline)} + {bounty.operator === '0x0000000000000000000000000000000000000000' ? null : ( + + + + )} + {bounty.reportId === 0n ? null : {openOracleCopy.formatReportNumberTitle(bounty.reportId.toString())}} + +
    + {bounty.state === 'open' ? ( + onAccept(managerDetails.managerAddress, bounty.bountyId)} + pending={activeAction === 'acceptOperationBounty' && activeBountyId === bounty.bountyId} + tone='secondary' + availability={{ + disabled: acceptGuardMessage !== undefined, + reason: acceptGuardMessage, + }} + /> + ) : null} + {isOperator && bounty.state === 'assigned' ? ( + onClaim(managerDetails.managerAddress, bounty.bountyId)} + pending={activeAction === 'claimOperationBounty' && activeBountyId === bounty.bountyId} + availability={{ disabled: claimGuardMessage !== undefined, reason: claimGuardMessage }} + /> + ) : null} + {isCreator && (bounty.state === 'open' || bounty.state === 'assigned') ? ( + onRefund(managerDetails.managerAddress, bounty.bountyId)} + pending={activeAction === 'refundOperationBounty' && activeBountyId === bounty.bountyId} + tone='secondary' + availability={{ disabled: refundGuardMessage !== undefined, reason: refundGuardMessage }} + /> + ) : null} +
    +
    + ) + })} +
    + {operationBounties.length === 0 ? : null} +
    +
    + ) +} diff --git a/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts b/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts index 27681d568..e0ed5abc5 100644 --- a/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts +++ b/ui/ts/features/open-oracle/hooks/useOpenOracleOperations.ts @@ -227,6 +227,14 @@ function useOpenOracleOperationsWithDependencies( currentSelectedReportIdRef.current = currentSelectedReportIdInput const getPendingTitle = (actionName: OpenOracleActionResult['action']) => { switch (actionName) { + case 'acceptOperationBounty': + return 'Accepting operation bounty' + case 'claimOperationBounty': + return 'Claiming operation bounty' + case 'postOperationBounty': + return 'Posting operation bounty' + case 'refundOperationBounty': + return 'Refunding operation bounty' case 'approveToken1': return 'Approving base token' case 'approveToken2': @@ -253,6 +261,14 @@ function useOpenOracleOperationsWithDependencies( } const getSuccessTitle = (actionName: OpenOracleActionResult['action']) => { switch (actionName) { + case 'acceptOperationBounty': + return 'Operation bounty accepted' + case 'claimOperationBounty': + return 'Operation bounty claimed' + case 'postOperationBounty': + return 'Operation bounty posted' + case 'refundOperationBounty': + return 'Operation bounty refunded' case 'approveToken1': return 'Base token approved' case 'approveToken2': @@ -279,6 +295,14 @@ function useOpenOracleOperationsWithDependencies( } const getFailureTitle = (actionName: OpenOracleActionResult['action']) => { switch (actionName) { + case 'acceptOperationBounty': + return 'Operation bounty acceptance failed' + case 'claimOperationBounty': + return 'Operation bounty claim failed' + case 'postOperationBounty': + return 'Operation bounty post failed' + case 'refundOperationBounty': + return 'Operation bounty refund failed' case 'approveToken1': return 'Base token approval failed' case 'approveToken2': diff --git a/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts b/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts index 53b954ee5..c0e7210b0 100644 --- a/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts +++ b/ui/ts/features/open-oracle/hooks/usePriceOracleManager.ts @@ -1,6 +1,6 @@ import { useSignal } from '@preact/signals' import type { Address, Hash } from '@zoltar/shared/ethereum' -import { executeOracleManagerStagedOperation, loadCoordinatorInitialReportFundingRequirement, loadOracleManagerDetails, requestOraclePrice } from '../../../protocol/index.js' +import { acceptOracleOperationBounty, claimOracleOperationBounty, executeOracleManagerStagedOperation, loadCoordinatorInitialReportFundingRequirement, loadOracleManagerDetails, loadOracleOperationBounty, postOracleOperationBounty, refundOracleOperationBounty, requestOraclePrice } from '../../../protocol/index.js' import { useLoadController } from '../../../hooks/useLoadController.js' import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' import { getErrorMessage } from '../../../lib/errors.js' @@ -8,12 +8,13 @@ import { createErrorActionFeedback, createPendingActionFeedback, createSuccessAc import type { ActionFeedback } from '../../../lib/actionFeedback.js' import { getOracleRequestEthGuardMessage } from '../lib/oracleRequestEth.js' import { formatCurrencyBalance } from '../../../lib/formatters.js' -import { createPoolOracleSuccessPresentation, createPoolOracleTransactionIntent, createPoolOracleWarningPresentation } from '../../transactionPresentations.js' +import { createOpenOracleSuccessPresentation, createOpenOracleTransactionIntent, createOpenOracleWarningPresentation, createPoolOracleSuccessPresentation, createPoolOracleTransactionIntent, createPoolOracleWarningPresentation } from '../../transactionPresentations.js' import { useRequestGuard } from '../../../lib/requestGuard.js' import { runWriteAction } from '../../../lib/writeAction.js' import { refreshWalletStateOnly } from '../../../lib/refreshState.js' +import { sameAddress } from '../../../lib/address.js' import type { WriteOperationsParameters } from '../../../types/app.js' -import type { OpenOracleActionResult, OracleManagerDetails } from '../../../types/contracts.js' +import type { OpenOracleActionResult, OracleManagerDetails, OracleOperationBountyInput } from '../../../types/contracts.js' type UsePriceOracleManagerParameters = { accountAddress: Address | undefined @@ -31,20 +32,30 @@ type PriceOracleProductionWriteClient = ReturnType> export type UsePriceOracleManagerDependencies = { + acceptOracleOperationBounty: (client: TWriteClient, managerAddress: Address, bountyId: bigint) => Promise + claimOracleOperationBounty: (client: TWriteClient, managerAddress: Address, bountyId: bigint) => Promise createConnectedReadClient: () => PriceOracleReadClient createWalletWriteClient: (accountAddress: Address, callbacks?: Parameters[1]) => TWriteClient executeOracleManagerStagedOperation: (client: TWriteClient, managerAddress: Address, operationId: bigint) => Promise loadCoordinatorInitialReportFundingRequirement: (client: TWriteClient, managerAddress: Address, walletAddress: Address) => Promise loadOracleManagerDetails: (managerAddress: Address) => Promise + loadOracleOperationBounty: (managerAddress: Address, boardAddress: Address, bountyId: bigint) => ReturnType + postOracleOperationBounty: (client: TWriteClient, managerAddress: Address, bounty: OracleOperationBountyInput) => Promise + refundOracleOperationBounty: (client: TWriteClient, managerAddress: Address, bountyId: bigint) => Promise requestOraclePrice: (client: TWriteClient, managerAddress: Address, proposedRepPerEthPrice: bigint, requestedInitialAttoWeth: bigint, reviewedRequestValueAttoEth: bigint) => Promise } const defaultUsePriceOracleManagerDependencies: UsePriceOracleManagerDependencies = { + acceptOracleOperationBounty: async (client, managerAddress, bountyId) => await acceptOracleOperationBounty(client, managerAddress, bountyId), + claimOracleOperationBounty: async (client, managerAddress, bountyId) => await claimOracleOperationBounty(client, managerAddress, bountyId), createConnectedReadClient, createWalletWriteClient, executeOracleManagerStagedOperation: async (client, managerAddress, operationId) => await executeOracleManagerStagedOperation(client, managerAddress, operationId), loadCoordinatorInitialReportFundingRequirement: async (client, managerAddress, walletAddress) => await loadCoordinatorInitialReportFundingRequirement(client, managerAddress, walletAddress), loadOracleManagerDetails: async managerAddress => await loadOracleManagerDetails(createConnectedReadClient(), managerAddress), + loadOracleOperationBounty: async (managerAddress, boardAddress, bountyId) => await loadOracleOperationBounty(createConnectedReadClient(), managerAddress, boardAddress, bountyId), + postOracleOperationBounty: async (client, managerAddress, bounty) => await postOracleOperationBounty(client, managerAddress, bounty), + refundOracleOperationBounty: async (client, managerAddress, bountyId) => await refundOracleOperationBounty(client, managerAddress, bountyId), requestOraclePrice: async (client, managerAddress, proposedRepPerEthPrice, requestedInitialAttoWeth, reviewedRequestValueAttoEth) => await requestOraclePrice(client, managerAddress, proposedRepPerEthPrice, requestedInitialAttoWeth, reviewedRequestValueAttoEth), } @@ -53,33 +64,51 @@ function usePriceOracleManagerWithDependencies( dependencies: UsePriceOracleManagerDependencies, ) { const poolOracleManagerLoad = useLoadController() + const poolOperationBountyLookupLoad = useLoadController() const poolOracleActiveAction = useSignal(undefined) + const poolOracleActiveBountyId = useSignal(undefined) const poolOracleFeedback = useSignal | undefined>(undefined) const poolOracleManagerDetails = useSignal(undefined) const poolOracleManagerError = useSignal(undefined) const poolOracleManagerErrorAddress = useSignal
    (undefined) + const poolOperationBountyLookupError = useSignal(undefined) const poolPriceOracleResult = useSignal(undefined) const nextPoolOracleManagerLoad = useRequestGuard() + const nextPoolOperationBountyLookup = useRequestGuard() const getPendingTitle = (actionName: OpenOracleActionResult['action']) => { if (actionName === 'requestPrice') return 'Requesting price' + if (actionName === 'postOperationBounty') return 'Posting operation bounty' + if (actionName === 'acceptOperationBounty') return 'Accepting operation bounty' + if (actionName === 'claimOperationBounty') return 'Claiming operation bounty' + if (actionName === 'refundOperationBounty') return 'Refunding operation bounty' return 'Executing staged operation' } const getSuccessTitle = (actionName: OpenOracleActionResult['action']) => { if (actionName === 'requestPrice') return 'Price requested' + if (actionName === 'postOperationBounty') return 'Operation bounty posted' + if (actionName === 'acceptOperationBounty') return 'Operation bounty accepted' + if (actionName === 'claimOperationBounty') return 'Operation bounty claimed' + if (actionName === 'refundOperationBounty') return 'Operation bounty refunded' return 'Staged operation executed' } const getFailureTitle = (actionName: OpenOracleActionResult['action']) => { if (actionName === 'requestPrice') return 'Price request failed' + if (actionName === 'postOperationBounty') return 'Operation bounty post failed' + if (actionName === 'acceptOperationBounty') return 'Operation bounty acceptance failed' + if (actionName === 'claimOperationBounty') return 'Operation bounty claim failed' + if (actionName === 'refundOperationBounty') return 'Operation bounty refund failed' return 'Staged operation failed' } const loadPoolOracleManager = async (managerAddress: Address) => { + nextPoolOperationBountyLookup() const isCurrent = nextPoolOracleManagerLoad() await poolOracleManagerLoad.run({ isCurrent, onStart: () => { poolOracleManagerError.value = undefined poolOracleManagerErrorAddress.value = undefined + poolOperationBountyLookupError.value = undefined }, load: async () => await dependencies.loadOracleManagerDetails(managerAddress), onSuccess: details => { @@ -92,6 +121,40 @@ function usePriceOracleManagerWithDependencies( }) } + const loadPoolOperationBounty = async (managerAddress: Address, bountyId: bigint) => { + const managerDetails = poolOracleManagerDetails.value + if (managerDetails === undefined || !sameAddress(managerDetails.managerAddress, managerAddress) || managerDetails.operationBountyBoardAddress === undefined) { + poolOperationBountyLookupError.value = 'Load this pool’s oracle details before looking up a bounty' + return + } + const boardAddress = managerDetails.operationBountyBoardAddress + const isCurrent = nextPoolOperationBountyLookup() + await poolOperationBountyLookupLoad.run({ + isCurrent, + onStart: () => { + poolOperationBountyLookupError.value = undefined + }, + load: async () => await dependencies.loadOracleOperationBounty(managerAddress, boardAddress, bountyId), + onSuccess: bounty => { + poolOperationBountyLookupError.value = undefined + const currentDetails = poolOracleManagerDetails.value + if (currentDetails === undefined || !sameAddress(currentDetails.managerAddress, managerAddress)) return + const operationBounties = [bounty, ...(currentDetails.operationBounties ?? []).filter(current => current.bountyId !== bounty.bountyId)].sort((left, right) => { + if (left.bountyId > right.bountyId) return -1 + if (left.bountyId < right.bountyId) return 1 + return 0 + }) + poolOracleManagerDetails.value = { ...currentDetails, operationBounties } + }, + onError: error => { + poolOperationBountyLookupError.value = getErrorMessage(error, 'Failed to load operation bounty') + }, + }) + } + const clearPoolOperationBountyLookupError = () => { + poolOperationBountyLookupError.value = undefined + } + const requestPoolPrice = async (managerAddress: Address, securityPoolAddress: Address, reviewedRequestValueAttoEth: bigint) => { const transactionContext = { managerAddress, securityPoolAddress } poolPriceOracleResult.value = undefined @@ -202,16 +265,81 @@ function usePriceOracleManagerWithDependencies( } } + const runOperationBountyAction = async (actionName: 'acceptOperationBounty' | 'claimOperationBounty' | 'postOperationBounty' | 'refundOperationBounty', managerAddress: Address, bountyId: bigint | undefined, write: (walletAddress: Address) => Promise) => { + poolPriceOracleResult.value = undefined + try { + poolOracleActiveAction.value = actionName + poolOracleActiveBountyId.value = bountyId + poolOracleFeedback.value = createPendingActionFeedback(actionName, getPendingTitle(actionName)) + await runWriteAction( + { + accountAddress, + missingWalletMessage: 'Connect a wallet before using operation bounties', + onRefreshError: (message, hash) => { + poolOracleFeedback.value = createWarningActionFeedback(actionName, getSuccessTitle(actionName), message, hash) + const result = poolPriceOracleResult.value + if (result !== undefined) onTransactionPresented(createOpenOracleWarningPresentation(result, message)) + }, + onTransactionFailed, + onTransactionFinished, + onTransactionRequested: () => onTransactionRequested(createOpenOracleTransactionIntent(actionName)), + onWriteError: message => { + poolOracleFeedback.value = createErrorActionFeedback(actionName, getFailureTitle(actionName), message) + }, + refreshErrorFallback: 'Operation bounty transaction succeeded, but refreshing the bounty board failed', + refreshState: async () => { + await refreshWalletStateOnly(refreshState) + await loadPoolOracleManager(managerAddress) + }, + setErrorMessage: message => { + poolOracleManagerError.value = message + }, + }, + write, + getFailureTitle(actionName), + result => { + poolPriceOracleResult.value = result + poolOracleFeedback.value = createSuccessActionFeedback(actionName, getSuccessTitle(actionName), result.hash) + onTransactionPresented(createOpenOracleSuccessPresentation(result)) + }, + ) + } finally { + poolOracleActiveAction.value = undefined + poolOracleActiveBountyId.value = undefined + } + } + + const postPoolOperationBounty = async (managerAddress: Address, bounty: OracleOperationBountyInput) => + await runOperationBountyAction('postOperationBounty', managerAddress, undefined, async walletAddress => await dependencies.postOracleOperationBounty(dependencies.createWalletWriteClient(walletAddress, { onTransactionPrepared, onTransactionSubmitted }), managerAddress, bounty)) + + const acceptPoolOperationBounty = async (managerAddress: Address, bountyId: bigint) => + await runOperationBountyAction('acceptOperationBounty', managerAddress, bountyId, async walletAddress => await dependencies.acceptOracleOperationBounty(dependencies.createWalletWriteClient(walletAddress, { onTransactionPrepared, onTransactionSubmitted }), managerAddress, bountyId)) + + const claimPoolOperationBounty = async (managerAddress: Address, bountyId: bigint) => + await runOperationBountyAction('claimOperationBounty', managerAddress, bountyId, async walletAddress => await dependencies.claimOracleOperationBounty(dependencies.createWalletWriteClient(walletAddress, { onTransactionPrepared, onTransactionSubmitted }), managerAddress, bountyId)) + + const refundPoolOperationBounty = async (managerAddress: Address, bountyId: bigint) => + await runOperationBountyAction('refundOperationBounty', managerAddress, bountyId, async walletAddress => await dependencies.refundOracleOperationBounty(dependencies.createWalletWriteClient(walletAddress, { onTransactionPrepared, onTransactionSubmitted }), managerAddress, bountyId)) + return { + acceptPoolOperationBounty, + claimPoolOperationBounty, + clearPoolOperationBountyLookupError, executePendingPoolOperation, loadingPoolOracleManager: poolOracleManagerLoad.isLoading.value, + loadingPoolOperationBounty: poolOperationBountyLookupLoad.isLoading.value, loadPoolOracleManager, + loadPoolOperationBounty, poolOracleActiveAction: poolOracleActiveAction.value, + poolOracleActiveBountyId: poolOracleActiveBountyId.value, poolOracleFeedback: poolOracleFeedback.value, poolOracleManagerDetails: poolOracleManagerDetails.value, poolOracleManagerError: poolOracleManagerError.value, poolOracleManagerErrorAddress: poolOracleManagerErrorAddress.value, + poolOperationBountyLookupError: poolOperationBountyLookupError.value, poolPriceOracleResult: poolPriceOracleResult.value, + postPoolOperationBounty, + refundPoolOperationBounty, requestPoolPrice, } } diff --git a/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx b/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx index 160c4f627..ed9f6a2c7 100644 --- a/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx +++ b/ui/ts/features/security-pools/components/SecurityPoolWorkflowSection.tsx @@ -15,6 +15,7 @@ import { LoadingText } from '../../../components/LoadingText.js' import { MetricGrid } from '../../../components/MetricGrid.js' import { MetricField } from '../../../components/MetricField.js' import { OpenOraclePriceValue } from '../../open-oracle/components/OpenOraclePriceValue.js' +import { OperationBountyBoard } from '../../open-oracle/components/OperationBountyBoard.js' import { getQuestionTitle, Question } from '../../markets/components/Question.js' import { ReportingSection } from '../../reporting/components/ReportingSection.js' import { RouteWorkflowPanel } from '../../../components/RouteWorkflowPanel.js' @@ -161,6 +162,7 @@ export function SecurityPoolWorkflowSection({ liquidationReceiverVaultSummaryResolved, liquidationTimeoutMinutes, loadingPoolOracleManager, + loadingPoolOperationBounty = false, loadingLiquidationFundingPreview, loadingLiquidationApproval, loadingLiquidationReceiverVaultSummary, @@ -174,19 +176,27 @@ export function SecurityPoolWorkflowSection({ onLoadPoolOracleManager, onBrowsePools, onCreatePool, + onLoadPoolOperationBounty = () => {}, onLoadLiquidationFundingPreview, onOpenLiquidationModal, onReturnToCurrentUniverse, onSwitchToPoolUniverse, onQueueLiquidation, + onAcceptPoolOperationBounty = () => {}, + onClaimPoolOperationBounty = () => {}, + onClearPoolOperationBountyLookupError = () => {}, onExecutePendingPoolOperation, + onPostPoolOperationBounty = () => {}, + onRefundPoolOperationBounty = () => {}, onRefreshSelectedPoolData, onRequestPoolPrice, onViewPendingReport, poolOracleActiveAction, + poolOracleActiveBountyId, poolOracleManagerDetails, poolOracleManagerError, poolOracleManagerErrorAddress, + poolOperationBountyLookupError, poolPriceOracleResult, universeForkTime, selectedPoolRefreshNonce, @@ -1155,6 +1165,24 @@ export function SecurityPoolWorkflowSection({ }} /> + {currentPoolOracleManagerDetails === undefined ? undefined : ( + + )} ) : undefined} diff --git a/ui/ts/features/types.ts b/ui/ts/features/types.ts index af73e1510..20856d9df 100644 --- a/ui/ts/features/types.ts +++ b/ui/ts/features/types.ts @@ -17,6 +17,7 @@ import type { OpenOracleReportSummaryPage, OpenOracleWithdrawableBalances, OracleManagerDetails, + OracleOperationBountyInput, ReadClient, ReportingActionResult, ReportingDetails, @@ -322,6 +323,7 @@ export type SecurityPoolWorkflowRouteContentProps = LiquidationModalStateProps & activeUniverseId: bigint checkedSecurityPoolAddress: string | undefined forkAuction: ForkAuctionRouteContentProps + loadingPoolOperationBounty?: boolean loadingSecurityPools: boolean onBrowsePools: () => void onCreatePool: () => void @@ -329,6 +331,12 @@ export type SecurityPoolWorkflowRouteContentProps = LiquidationModalStateProps & onReturnToCurrentUniverse?: () => void onSwitchToPoolUniverse?: (universeId: bigint, securityPoolAddress: Address) => void onExecutePendingPoolOperation: (managerAddress: Address, operationId: bigint, securityPoolAddress: Address) => void + onAcceptPoolOperationBounty?: (managerAddress: Address, bountyId: bigint) => void + onClaimPoolOperationBounty?: (managerAddress: Address, bountyId: bigint) => void + onClearPoolOperationBountyLookupError?: () => void + onLoadPoolOperationBounty?: (managerAddress: Address, bountyId: bigint) => void + onPostPoolOperationBounty?: (managerAddress: Address, bounty: OracleOperationBountyInput) => void + onRefundPoolOperationBounty?: (managerAddress: Address, bountyId: bigint) => void onRefreshSelectedPoolData: (securityPoolAddress?: string) => void onRequestPoolPrice: (managerAddress: Address, securityPoolAddress: Address, reviewedRequestValueAttoEth: bigint) => void onSelectedPoolViewChange: (view: string | undefined) => void @@ -336,8 +344,10 @@ export type SecurityPoolWorkflowRouteContentProps = LiquidationModalStateProps & selectedPoolRefreshNonce: number securityPoolOverviewResult: SecurityPoolOverviewActionResult | undefined poolOracleActiveAction: OpenOracleActionResult['action'] | undefined + poolOracleActiveBountyId?: bigint | undefined poolOracleManagerError: string | undefined poolOracleManagerErrorAddress: Address | undefined + poolOperationBountyLookupError?: string | undefined poolPriceOracleResult: OpenOracleActionResult | undefined universeForkTime?: bigint | undefined selectedPoolView: string diff --git a/ui/ts/protocol/deployment.ts b/ui/ts/protocol/deployment.ts index 585ef7b7e..8d739bbe7 100644 --- a/ui/ts/protocol/deployment.ts +++ b/ui/ts/protocol/deployment.ts @@ -46,11 +46,11 @@ export const EXPECTED_SEPOLIA_DEPLOYMENT_RUNTIME_CODE_HASHES: Readonly[0] const OPEN_ORACLE_PRICE_UNITS = 30n const ACTIVE_STAGED_OPERATION_PREVIEW_LIMIT = 25n +const OPERATION_BOUNTY_PREVIEW_LIMIT = 25n const COORDINATOR_PRICE_PRECISION = 10n ** 18n const OPEN_ORACLE_REPORT_MISSING_ERROR_NAME = 'OpenOracleReportMissingError' +type RawOperationBounty = { + acceptanceDeadline: bigint + amount: bigint + creator: Address + maximumInitialAttoWeth: bigint + minimumInitialAttoWeth: bigint + operation: bigint | number + operationId: bigint + operator: Address + reportId: bigint + rewardAmount: bigint + rewardToken: Address + state: bigint | number + targetVault: Address + validForSeconds: bigint +} + export function createOpenOracleReportMissingError(reportId: bigint) { const error = new Error(`Oracle report #${reportId.toString()} does not exist`) error.name = OPEN_ORACLE_REPORT_MISSING_ERROR_NAME @@ -112,6 +146,12 @@ function requireBigintValue(value: unknown, context: string) { throw new Error(`Unexpected ${context} response`) } +function requireUnsignedBigintValue(value: unknown, context: string) { + if (typeof value === 'bigint' && value >= 0n) return value + if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return BigInt(value) + throw new Error(`Unexpected ${context} response`) +} + function requireBigintArray(value: unknown, context: string) { if (!Array.isArray(value)) throw new Error(`Unexpected ${context} response`) const result: bigint[] = [] @@ -122,8 +162,169 @@ function requireBigintArray(value: unknown, context: string) { return result } +function decodeOperationBountyState(value: bigint | number): OracleOperationBountyState { + switch (BigInt(value)) { + case 1n: + return 'open' + case 2n: + return 'assigned' + case 3n: + return 'paid' + case 4n: + return 'refunded' + default: + throw new Error(`Unknown operation bounty state: ${value}`) + } +} + +function decodeOperationExecutionStatus(value: bigint | number): OracleOperationExecutionStatus { + switch (BigInt(value)) { + case 0n: + return 'none' + case 1n: + return 'pending' + case 2n: + return 'succeeded' + case 3n: + return 'failed' + default: + throw new Error(`Unknown operation execution status: ${value}`) + } +} + +async function normalizeOperationBounty(client: ReadClient, managerAddress: Address, boardAddress: Address, settlementTime: bigint, bountyId: bigint, bounty: RawOperationBounty): Promise { + let executionStatus: OracleOperationExecutionStatus = 'none' + let refundAvailableAt: bigint | undefined + if (bounty.operationId > 0n) { + const [rawExecutionStatus, stagedOperation] = await Promise.all([ + client.readContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: boardAddress, + functionName: 'operationExecutionStatuses', + args: [bountyId], + }), + client.readContract({ + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + address: managerAddress, + functionName: 'stagedOperations', + args: [bounty.operationId], + }), + ]) + executionStatus = decodeOperationExecutionStatus(rawExecutionStatus) + const stagedOperator = getTupleComponent(stagedOperation, 1, 'operator') + if (executionStatus === 'pending' && stagedOperator !== zeroAddress) { + refundAvailableAt = requireUnsignedBigintValue(getTupleComponent(stagedOperation, 5, 'queuedAt'), 'staged operation queue time') + settlementTime + requireUnsignedBigintValue(getTupleComponent(stagedOperation, 6, 'validForSeconds'), 'staged operation validity') + } + } + return { + acceptanceDeadline: bounty.acceptanceDeadline, + amount: bounty.amount, + bountyId, + creator: getAddress(bounty.creator), + executionStatus, + maximumInitialAttoWeth: bounty.maximumInitialAttoWeth, + minimumInitialAttoWeth: bounty.minimumInitialAttoWeth, + operation: decodeOracleQueueOperation(bounty.operation), + operationId: bounty.operationId, + operator: getAddress(bounty.operator), + reportId: bounty.reportId, + refundAvailableAt, + rewardAmount: bounty.rewardAmount, + rewardToken: getAddress(bounty.rewardToken), + state: decodeOperationBountyState(bounty.state), + targetVault: getAddress(bounty.targetVault), + validForSeconds: bounty.validForSeconds, + } +} + +function requireAddressValue(value: unknown, context: string) { + if (typeof value === 'string') return getAddress(value) + throw new Error(`Unexpected ${context} response`) +} + +function requireEnumValue(value: unknown, context: string) { + if (typeof value === 'bigint') return value + if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return value + throw new Error(`Unexpected ${context} response`) +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} + +function getTupleComponent(value: unknown, index: number, name: string) { + if (Array.isArray(value)) return value[index] + if (isRecord(value)) return value[name] ?? value[index.toString()] + return undefined +} + +function toRawOperationBounty(bounty: unknown): RawOperationBounty { + if (!Array.isArray(bounty) && !isRecord(bounty)) throw new Error('Unexpected operation bounty response') + const values = ['creator', 'operator', 'operation', 'targetVault', 'amount', 'validForSeconds', 'rewardToken', 'rewardAmount', 'acceptanceDeadline', 'minimumInitialAttoWeth', 'maximumInitialAttoWeth', 'operationId', 'reportId', 'state'].map((name, index) => getTupleComponent(bounty, index, name)) + return { + creator: requireAddressValue(values[0], 'operation bounty creator'), + operator: requireAddressValue(values[1], 'operation bounty operator'), + operation: requireEnumValue(values[2], 'operation bounty operation'), + targetVault: requireAddressValue(values[3], 'operation bounty target'), + amount: requireBigintValue(values[4], 'operation bounty amount'), + validForSeconds: requireBigintValue(values[5], 'operation bounty validity'), + rewardToken: requireAddressValue(values[6], 'operation bounty reward token'), + rewardAmount: requireBigintValue(values[7], 'operation bounty reward'), + acceptanceDeadline: requireBigintValue(values[8], 'operation bounty acceptance deadline'), + minimumInitialAttoWeth: requireBigintValue(values[9], 'operation bounty minimum WETH'), + maximumInitialAttoWeth: requireBigintValue(values[10], 'operation bounty maximum WETH'), + operationId: requireBigintValue(values[11], 'operation bounty operation id'), + reportId: requireBigintValue(values[12], 'operation bounty report id'), + state: requireEnumValue(values[13], 'operation bounty state'), + } +} + +export async function loadOracleOperationBounty(client: ReadClient, managerAddress: Address, boardAddress: Address, bountyId: bigint): Promise { + if (boardAddress === zeroAddress) throw new Error('This oracle coordinator does not have an operation bounty board') + if (bountyId <= 0n) throw new Error('Operation bounty ID must be positive') + const nextBountyId = await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: boardAddress, functionName: 'nextOperationBountyId', args: [] }) + if (bountyId >= nextBountyId) throw new Error(`Operation bounty #${bountyId} does not exist`) + const [settlementTime, bounty] = await Promise.all([ + client.readContract({ abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, address: managerAddress, functionName: 'settlementTime', args: [] }), + client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: boardAddress, functionName: 'operationBounties', args: [bountyId] }), + ]) + return await normalizeOperationBounty(client, managerAddress, boardAddress, requireUnsignedBigintValue(settlementTime, 'settlement time'), bountyId, toRawOperationBounty(bounty)) +} + +async function loadOperationBounties(client: ReadClient, managerAddress: Address, boardAddress: Address, settlementTime: bigint): Promise { + if (boardAddress === zeroAddress) return [] + const nextBountyId = await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: boardAddress, functionName: 'nextOperationBountyId', args: [] }) + if (nextBountyId <= 1n) return [] + const bountyCount = nextBountyId - 1n < OPERATION_BOUNTY_PREVIEW_LIMIT ? nextBountyId - 1n : OPERATION_BOUNTY_PREVIEW_LIMIT + const startId = nextBountyId - bountyCount + const [bountyIds, bounties] = await client.readContract({ abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, address: boardAddress, functionName: 'getOperationBounties', args: [startId, bountyCount] }) + const normalizedBounties = await Promise.all( + bounties.map(async (bounty, index) => { + const bountyId = bountyIds[index] + if (bountyId === undefined) throw new Error('Missing operation bounty id') + return await normalizeOperationBounty(client, managerAddress, boardAddress, settlementTime, bountyId, toRawOperationBounty(bounty)) + }), + ) + return normalizedBounties.reverse() +} + export async function loadOracleManagerDetails(client: ReadClient, managerAddress: Address, openOracleAddress?: Address): Promise { - const [lastPrice, pendingOperationSlotId, pendingSettlementOperationIds, pendingSettlementQueueCapacity, pendingReportId, queuedOperationCostAttoEth, requestPriceCostAttoEth, rawIsPriceValid, lastSettlementTimestamp, activeStagedOperationCount, settlementTime] = await readRequiredMulticall(client, [ + const [ + lastPrice, + pendingOperationSlotId, + pendingSettlementOperationIds, + pendingSettlementQueueCapacity, + pendingReportId, + queuedOperationCostAttoEth, + requestPriceCostAttoEth, + rawIsPriceValid, + lastSettlementTimestamp, + activeStagedOperationCount, + settlementTime, + rawOperationBountyBoardAddress, + rawReputationTokenAddress, + rawWethAddress, + ] = await readRequiredMulticall(client, [ { abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'lastPrice', @@ -190,11 +391,34 @@ export async function loadOracleManagerDetails(client: ReadClient, managerAddres address: managerAddress, args: [], }, + { + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + functionName: 'operationBountyBoard', + address: managerAddress, + args: [], + }, + { + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + functionName: 'reputationToken', + address: managerAddress, + args: [], + }, + { + abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, + functionName: 'weth', + address: managerAddress, + args: [], + }, ]) const normalizedPendingSettlementOperationIds = requireBigintArray(pendingSettlementOperationIds, 'pending settlement operation ids') const normalizedPendingSettlementQueueCapacity = requireBigintValue(pendingSettlementQueueCapacity, 'pending settlement queue capacity') const normalizedQueuedOperationEthCost = requireBigintValue(queuedOperationCostAttoEth, 'queued operation ETH cost') const normalizedRequestPriceEthCost = requireBigintValue(requestPriceCostAttoEth, 'request price ETH cost') + const normalizedSettlementTime = requireUnsignedBigintValue(settlementTime, 'settlement time') + const operationBountyBoardAddress = getAddress(rawOperationBountyBoardAddress) + const reputationTokenAddress = getAddress(rawReputationTokenAddress) + const wethAddress = getAddress(rawWethAddress) + const operationBounties = await loadOperationBounties(client, managerAddress, operationBountyBoardAddress, normalizedSettlementTime) const resolvedOracleAddress = openOracleAddress ?? getInfraContractAddresses().openOracle let callbackStateHash: Hex | undefined let exactToken1Report: bigint | undefined @@ -270,6 +494,8 @@ export async function loadOracleManagerDetails(client: ReadClient, managerAddres lastSettlementTimestamp, managerAddress, openOracleAddress: resolvedOracleAddress, + operationBounties, + operationBountyBoardAddress, pendingOperation, pendingOperationSlotId, pendingSettlementOperationIds: normalizedPendingSettlementOperationIds, @@ -277,11 +503,13 @@ export async function loadOracleManagerDetails(client: ReadClient, managerAddres pendingReportId, priceValidUntilTimestamp: getOracleManagerPriceValidUntilTimestamp(lastSettlementTimestamp), queuedOperationCostAttoEth: normalizedQueuedOperationEthCost, + reputationTokenAddress, requestPriceCostAttoEth: normalizedRequestPriceEthCost, - settlementTime, + settlementTime: normalizedSettlementTime, stagedOperations, token1, token2, + wethAddress, } } function compareStagedOperationIdsDescending(left: { operationId: bigint }, right: { operationId: bigint }) { @@ -801,6 +1029,104 @@ export async function requestOraclePrice(client: WriteClient, managerAddress: Ad hash, } satisfies OpenOracleActionResult } + +export async function postOracleOperationBounty(client: WriteClient, managerAddress: Address, bounty: OracleOperationBountyInput) { + const boardAddress = getAddress(await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'operationBountyBoard', args: [] })) + if (boardAddress === zeroAddress) throw new Error('This oracle coordinator does not have an operation bounty board') + if (sameAddress(bounty.rewardToken, getWethAddress())) { + const currentWethBalance = await client.readContract({ address: getWethAddress(), abi: ABIS.mainnet.erc20, functionName: 'balanceOf', args: [client.account.address] }) + if (currentWethBalance < bounty.rewardAmount) await wrapWeth(client, bounty.rewardAmount - currentWethBalance) + } + await writeContractAndWait(client, () => ({ address: bounty.rewardToken, abi: ABIS.mainnet.erc20, functionName: 'approve', args: [boardAddress, bounty.rewardAmount] })) + const hash = await writeContractAndWait(client, () => ({ + address: boardAddress, + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + functionName: 'postOperationBounty', + args: [encodeOracleQueueOperation(bounty.operation), bounty.targetVault, bounty.amount, bounty.validForSeconds, bounty.rewardToken, bounty.rewardAmount, bounty.acceptanceDeadline, bounty.minimumInitialAttoWeth, bounty.maximumInitialAttoWeth], + })) + return { action: 'postOperationBounty', hash } satisfies OpenOracleActionResult +} + +async function loadOracleOperationBountyAcceptanceSnapshot(client: WriteClient, managerAddress: Address, boardAddress: Address, bountyId: bigint) { + const [bounty, isPriceValid, pendingReportId, rawPendingReportSponsor, pendingSettlementOperationIds, pendingSettlementQueueCapacity, block] = await Promise.all([ + client.readContract({ address: boardAddress, abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, functionName: 'operationBounties', args: [bountyId] }), + client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'isPriceValid', args: [] }), + client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'pendingReportId', args: [] }), + client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'pendingReportSponsor', args: [] }), + client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'getPendingSettlementOperationIds', args: [] }), + client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'MAX_PENDING_SETTLEMENT_OPERATIONS', args: [] }), + client.getBlock(), + ]) + if (!hasTimestampAndNumber(block)) throw new Error('Unexpected block response') + if (bounty[13] !== 1n) throw new Error('Operation bounty is not open') + if (block.timestamp > bounty[8]) throw new Error('Operation bounty acceptance deadline has passed') + if (!isPriceValid && BigInt(pendingSettlementOperationIds.length) >= pendingSettlementQueueCapacity) throw new Error('Operation bounty cannot fit in the pending settlement queue') + return { bounty, isPriceValid, pendingReportId, pendingReportSponsor: getAddress(rawPendingReportSponsor) } +} + +function assertOracleOperationBountyInitialAttoWethBounds(initialAttoWeth: bigint, minimumInitialAttoWeth: bigint, maximumInitialAttoWeth: bigint) { + if (initialAttoWeth < minimumInitialAttoWeth) throw new Error('The current initial report WETH amount is below this bounty’s minimum') + if (maximumInitialAttoWeth > 0n && initialAttoWeth > maximumInitialAttoWeth) throw new Error('The current initial report WETH amount exceeds this bounty’s maximum') +} + +async function loadPendingOpenOracleInitialAttoWeth(client: WriteClient, managerAddress: Address, pendingReportId: bigint) { + const rawOpenOracleAddress = await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'openOracle', args: [] }) + const storedGame = await client.readContract({ address: getAddress(rawOpenOracleAddress), abi: peripherals_openOracle_OpenOracle_OpenOracle.abi, functionName: 'storedGame', args: [pendingReportId] }) + return storedGame[0] +} + +export async function acceptOracleOperationBounty(client: WriteClient, managerAddress: Address, bountyId: bigint) { + const boardAddress = getAddress(await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'operationBountyBoard', args: [] })) + if (boardAddress === zeroAddress) throw new Error('This oracle coordinator does not have an operation bounty board') + const initialSnapshot = await loadOracleOperationBountyAcceptanceSnapshot(client, managerAddress, boardAddress, bountyId) + let proposedRepPerEthPrice = 0n + let requestedInitialAttoWeth = 0n + let value = 0n + if (!initialSnapshot.isPriceValid) { + if (initialSnapshot.pendingReportId === 0n) { + requestedInitialAttoWeth = initialSnapshot.bounty[9] + const fundingRequirement = await loadCoordinatorInitialReportFundingRequirement(client, managerAddress, client.account.address, undefined, requestedInitialAttoWeth) + const initialReportAttoWeth = requestedInitialAttoWeth > fundingRequirement.minimumToken1ReportAttoEth ? requestedInitialAttoWeth : fundingRequirement.minimumToken1ReportAttoEth + assertOracleOperationBountyInitialAttoWethBounds(initialReportAttoWeth, initialSnapshot.bounty[9], initialSnapshot.bounty[10]) + proposedRepPerEthPrice = fundingRequirement.proposedRepPerEthPrice + await fundCoordinatorInitialReport(client, managerAddress, proposedRepPerEthPrice, requestedInitialAttoWeth) + } else { + if (!sameAddress(initialSnapshot.pendingReportSponsor, client.account.address)) throw new Error('Only the operator funding the pending OpenOracle report can accept another bounty before settlement') + assertOracleOperationBountyInitialAttoWethBounds(await loadPendingOpenOracleInitialAttoWeth(client, managerAddress, initialSnapshot.pendingReportId), initialSnapshot.bounty[9], initialSnapshot.bounty[10]) + } + } + const finalSnapshot = await loadOracleOperationBountyAcceptanceSnapshot(client, managerAddress, boardAddress, bountyId) + if (finalSnapshot.isPriceValid !== initialSnapshot.isPriceValid || finalSnapshot.pendingReportId !== initialSnapshot.pendingReportId || !sameAddress(finalSnapshot.pendingReportSponsor, initialSnapshot.pendingReportSponsor)) throw new Error('Oracle bounty acceptance state changed; retry') + if (!finalSnapshot.isPriceValid) { + if (finalSnapshot.pendingReportId === 0n) { + const minimumToken1ReportAttoEth = await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'minimumToken1ReportAttoEth', args: [] }) + const initialReportAttoWeth = requestedInitialAttoWeth > minimumToken1ReportAttoEth ? requestedInitialAttoWeth : minimumToken1ReportAttoEth + assertOracleOperationBountyInitialAttoWethBounds(initialReportAttoWeth, finalSnapshot.bounty[9], finalSnapshot.bounty[10]) + value = await loadBufferedOracleRequestEthCost(client, managerAddress) + } else { + if (!sameAddress(finalSnapshot.pendingReportSponsor, client.account.address)) throw new Error('Only the operator funding the pending OpenOracle report can accept another bounty before settlement') + assertOracleOperationBountyInitialAttoWethBounds(await loadPendingOpenOracleInitialAttoWeth(client, managerAddress, finalSnapshot.pendingReportId), finalSnapshot.bounty[9], finalSnapshot.bounty[10]) + } + } + const hash = await writeContractAndWait(client, () => ({ address: boardAddress, abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, functionName: 'acceptOperationBounty', args: [bountyId, proposedRepPerEthPrice, requestedInitialAttoWeth], value })) + return { action: 'acceptOperationBounty', hash } satisfies OpenOracleActionResult +} + +async function settleOracleOperationBounty(client: WriteClient, managerAddress: Address, bountyId: bigint, action: 'claimOperationBounty' | 'refundOperationBounty') { + const boardAddress = getAddress(await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'operationBountyBoard', args: [] })) + if (boardAddress === zeroAddress) throw new Error('This oracle coordinator does not have an operation bounty board') + const hash = await writeContractAndWait(client, () => ({ address: boardAddress, abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, functionName: action, args: [bountyId] })) + return { action, hash } satisfies OpenOracleActionResult +} + +export async function claimOracleOperationBounty(client: WriteClient, managerAddress: Address, bountyId: bigint) { + return await settleOracleOperationBounty(client, managerAddress, bountyId, 'claimOperationBounty') +} + +export async function refundOracleOperationBounty(client: WriteClient, managerAddress: Address, bountyId: bigint) { + return await settleOracleOperationBounty(client, managerAddress, bountyId, 'refundOperationBounty') +} + export async function executeOracleManagerStagedOperation(client: WriteContractClient, managerAddress: Address, operationId: bigint) { const { hash, receipt } = await writeContractAndWaitForReceipt(client, () => ({ address: managerAddress, diff --git a/ui/ts/simulation/bootstrap.ts b/ui/ts/simulation/bootstrap.ts index f69449d8a..4070317a6 100644 --- a/ui/ts/simulation/bootstrap.ts +++ b/ui/ts/simulation/bootstrap.ts @@ -882,6 +882,7 @@ async function seedSecurityPoolX2AuctionScenario({ await reportBootstrapProgress(onProgress, 'Creating and funding Yes child universe', 0.99) await createChildUniverseFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, 'yes') + await reportBootstrapProgress(onProgress, 'Funding Yes child universe', 0.991) await migrateRepToZoltarFromSecurityPool(writeClient, parentPool.securityPoolAddress, parentPool.universeId, ['yes']) await advanceSimulationTime(memoryClient, FORK_MIGRATION_TIME_SECONDS + DAY_IN_SECONDS) diff --git a/ui/ts/tests/app/globalTransactionTray.test.tsx b/ui/ts/tests/app/globalTransactionTray.test.tsx index 88aa781b0..15e653723 100644 --- a/ui/ts/tests/app/globalTransactionTray.test.tsx +++ b/ui/ts/tests/app/globalTransactionTray.test.tsx @@ -54,7 +54,9 @@ describe('GlobalTransactionTray', () => { expect(documentQueries.getByText('Question ID')).not.toBeNull() expect(documentQueries.getByText('0x0b')).not.toBeNull() expect(documentQueries.getByRole('link', { name: '0x1234000000000000000000000000000000000000000000000000000000000000' })).not.toBeNull() - expect(documentQueries.getByRole('button', { name: 'Dismiss' })).not.toBeNull() + expect(document.body.querySelector('.global-transaction-notice-compact')).not.toBeNull() + expect(documentQueries.getByText('View transaction details', { selector: 'summary' })).not.toBeNull() + expect(documentQueries.getByRole('button', { name: 'Close transaction status' })).not.toBeNull() }) test('reserves the transaction tray height in the viewport scroll area', async () => { @@ -67,7 +69,7 @@ describe('GlobalTransactionTray', () => { expect(document.documentElement.style.scrollPaddingBottom).not.toBe('') await act(() => { - fireEvent.click(within(document.body).getByRole('button', { name: 'Dismiss' })) + fireEvent.click(within(document.body).getByRole('button', { name: 'Close transaction status' })) }) expect(document.documentElement.style.scrollPaddingBottom).toBe('') }) @@ -330,7 +332,7 @@ describe('GlobalTransactionTray', () => { const renderedComponent = await renderIntoDocument() cleanupRenderedComponent = renderedComponent.cleanup await act(() => { - fireEvent.click(within(renderedComponent.container).getByRole('button', { name: 'Dismiss' })) + fireEvent.click(within(renderedComponent.container).getByRole('button', { name: 'Close transaction status' })) }) await renderedComponent.cleanup() cleanupRenderedComponent = undefined @@ -362,7 +364,7 @@ describe('GlobalTransactionTray', () => { render(, renderedComponent.container) }) await act(() => { - fireEvent.click(within(renderedComponent.container).getByRole('button', { name: 'Dismiss' })) + fireEvent.click(within(renderedComponent.container).getByRole('button', { name: 'Close transaction status' })) }) } await renderedComponent.cleanup() @@ -413,9 +415,10 @@ describe('GlobalTransactionTray', () => { expect(within(document.body).getByText('Confirmed')).not.toBeNull() expect(within(document.body).getByText('Question Created')).not.toBeNull() + expect(document.body.querySelector('.global-transaction-notice-compact')).not.toBeNull() }) - test('compacts a completed transaction after navigation while keeping details available', async () => { + test('keeps a completed transaction compact across navigation while retaining details', async () => { const transaction = { dismissKey: 'question-created-route-handoff', hash: '0x7234000000000000000000000000000000000000000000000000000000000000' as const, @@ -426,7 +429,7 @@ describe('GlobalTransactionTray', () => { const renderedComponent = await renderIntoDocument() cleanupRenderedComponent = renderedComponent.cleanup - expect(document.body.querySelector('.global-transaction-notice-compact')).toBeNull() + expect(document.body.querySelector('.global-transaction-notice-compact')).not.toBeNull() await act(() => { render(, renderedComponent.container) diff --git a/ui/ts/tests/features/open-oracle/openOracle.test.ts b/ui/ts/tests/features/open-oracle/openOracle.test.ts index 221e85571..47ad965a9 100644 --- a/ui/ts/tests/features/open-oracle/openOracle.test.ts +++ b/ui/ts/tests/features/open-oracle/openOracle.test.ts @@ -3,6 +3,7 @@ import { beforeAll, beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test' import { getAddress, maxUint256, zeroAddress, type Address, type Hash } from '@zoltar/shared/ethereum' import { + acceptOracleOperationBounty, createOpenOracleReportInstance, executeOracleManagerStagedOperation, getOpenOracleAddress, @@ -12,6 +13,8 @@ import { loadOpenOracleReportDetails, loadOpenOracleReportSummaries, loadOracleManagerDetails, + loadOracleOperationBounty, + postOracleOperationBounty, queueOracleManagerOperation, queueSecurityPoolLiquidation, requestOraclePrice, @@ -40,19 +43,20 @@ import { getDefaultOpenOracleCreateFormState } from '../../../features/markets/l import { ORACLE_MANAGER_PRICE_VALID_FOR_SECONDS } from '../../../features/security-pools/lib/securityVault.js' import { createConnectedReadClient, createWalletWriteClient } from '../../../lib/clients.js' import { ETH_ADDRESS, REP_ADDRESS, UNISWAP_V4_QUOTER_ADDRESS, USDC_ADDRESS } from '../../../protocol/uniswapQuoter.js' -import { peripherals_openOracle_OpenOracle_OpenOracle } from '../../../contractArtifact.js' +import { peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator, peripherals_openOracle_OpenOracle_OpenOracle } from '../../../contractArtifact.js' import type { InjectedEthereum } from '../../../injectedEthereum.js' import type { WriteContractClient } from '../../../protocol/core.js' import { DAY, GENESIS_REPUTATION_TOKEN, WETH_ADDRESS, TEST_ADDRESSES } from '../../../../../solidity/ts/testSupport/simulator/utils/constants' import { addressString } from '../../../../../solidity/ts/testSupport/simulator/utils/bigint' -import { setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' +import { approveToken, setupTestAccounts, ensureProxyDeployerDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/utilities' import { AnvilWindowEthereum } from '../../../../../solidity/ts/testSupport/simulator/AnvilWindowEthereum' import { TEST_TIMEOUT_MS, useIsolatedAnvilNode } from '../../../../../solidity/ts/testSupport/simulator/useIsolatedAnvilNode' import { createWriteClient, type WriteClient } from '../../../../../solidity/ts/testSupport/simulator/utils/clients' +import { peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard } from '../../../contractArtifact.js' import { deployOriginSecurityPool, ensureInfraDeployed, getSecurityPoolAddresses } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/deployPeripherals' import { ensureZoltarDeployed } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltar' import { createQuestion, getQuestionId } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/zoltarQuestionData' -import { getOpenOracleExtraData, getRequestPriceCostAttoEth, requestPriceWithValue } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/peripherals' +import { getOpenOracleExtraData, getRequestPriceCostAttoEth, requestPriceWithValue, wrapWeth as wrapWethTestHelper } from '../../../../../solidity/ts/testSupport/simulator/utils/contracts/peripherals' setDefaultTimeout(TEST_TIMEOUT_MS) @@ -222,6 +226,15 @@ describe('Open Oracle helpers', () => { uiWriteClient = createWalletWriteClient(addressString(TEST_ADDRESSES[0])) }) + const getUiAccountTransactionCount = async () => { + const rawTransactionCount = await mockWindow.request({ + method: 'eth_getTransactionCount', + params: [uiWriteClient.account.address, 'latest'], + }) + if (typeof rawTransactionCount !== 'string') throw new Error('Expected the account transaction count to be a hexadecimal string') + return BigInt(rawTransactionCount) + } + test('getOpenOracleAddress returns the deterministic non-zero oracle address', () => { expect(getOpenOracleAddress()).not.toBe(zeroAddress) }) @@ -879,6 +892,143 @@ describe('Open Oracle helpers', () => { expect(details.lastSettlementTimestamp).toBe(0n) expect(details.isPriceValid).toBe(false) expect(details.priceValidUntilTimestamp).toBe(undefined) + expect(details.operationBountyBoardAddress).not.toBe(zeroAddress) + expect(details.operationBounties).toEqual([]) + }) + + test('posts and accepts a WETH operation bounty through the UI protocol client', async () => { + const currentTimestamp = await mockWindow.getTime() + const postResult = await postOracleOperationBounty(uiWriteClient, managerAddress, { + acceptanceDeadline: currentTimestamp + DAY, + amount: 1n, + maximumInitialAttoWeth: 0n, + minimumInitialAttoWeth: 0n, + operation: 'liquidation', + rewardAmount: 10n ** 18n, + rewardToken: WETH_ADDRESS, + targetVault: addressString(TEST_ADDRESSES[1]), + validForSeconds: DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, + }) + expect(postResult.action).toBe('postOperationBounty') + + let managerDetails = await loadOracleManagerDetails(uiReadClient, managerAddress) + const postedBounty = managerDetails.operationBounties?.[0] + expect(postedBounty?.state).toBe('open') + expect(postedBounty?.creator).toBe(uiWriteClient.account.address) + expect(postedBounty?.rewardToken).toBe(WETH_ADDRESS) + + uiWriteClient.simulateContract = async () => ({ result: [1n, 100000n], request: {} as never }) as never + const acceptResult = await acceptOracleOperationBounty(uiWriteClient, managerAddress, 1n) + expect(acceptResult.action).toBe('acceptOperationBounty') + managerDetails = await loadOracleManagerDetails(uiReadClient, managerAddress) + expect(managerDetails.operationBounties?.[0]?.state).toBe('assigned') + expect(managerDetails.operationBounties?.[0]?.operator).toBe(uiWriteClient.account.address) + expect(managerDetails.operationBounties?.[0]?.executionStatus).toBe('pending') + expect(managerDetails.pendingReportId).toBeGreaterThan(0n) + }) + + test('rejects a closed operation bounty before submitting funding or acceptance transactions', async () => { + const currentTimestamp = await mockWindow.getTime() + await postOracleOperationBounty(uiWriteClient, managerAddress, { + acceptanceDeadline: currentTimestamp + DAY, + amount: 1n, + maximumInitialAttoWeth: 0n, + minimumInitialAttoWeth: 0n, + operation: 'liquidation', + rewardAmount: 1n, + rewardToken: WETH_ADDRESS, + targetVault: addressString(TEST_ADDRESSES[1]), + validForSeconds: DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, + }) + const boardAddress = (await loadOracleManagerDetails(uiReadClient, managerAddress)).operationBountyBoardAddress + if (boardAddress === undefined) throw new Error('Expected operation bounty board address') + const refundHash = await client.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: boardAddress, + functionName: 'refundOperationBounty', + args: [1n], + }) + await client.waitForTransactionReceipt({ hash: refundHash }) + const transactionCountBefore = await getUiAccountTransactionCount() + uiWriteClient.simulateContract = async () => ({ result: [1n, 100000n], request: {} as never }) as never + + await expect(acceptOracleOperationBounty(uiWriteClient, managerAddress, 1n)).rejects.toThrow('Operation bounty is not open') + + expect(await getUiAccountTransactionCount()).toBe(transactionCountBefore) + }) + + test('rejects an expired operation bounty before submitting funding or acceptance transactions', async () => { + const currentTimestamp = await mockWindow.getTime() + await postOracleOperationBounty(uiWriteClient, managerAddress, { + acceptanceDeadline: currentTimestamp + 60n, + amount: 1n, + maximumInitialAttoWeth: 0n, + minimumInitialAttoWeth: 0n, + operation: 'liquidation', + rewardAmount: 1n, + rewardToken: WETH_ADDRESS, + targetVault: addressString(TEST_ADDRESSES[1]), + validForSeconds: DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, + }) + await mockWindow.advanceTime(61n) + const transactionCountBefore = await getUiAccountTransactionCount() + uiWriteClient.simulateContract = async () => ({ result: [1n, 100000n], request: {} as never }) as never + + await expect(acceptOracleOperationBounty(uiWriteClient, managerAddress, 1n)).rejects.toThrow('Operation bounty acceptance deadline has passed') + + expect(await getUiAccountTransactionCount()).toBe(transactionCountBefore) + }) + + test('rejects an operation bounty when the pending settlement queue is full without submitting a transaction', async () => { + const currentTimestamp = await mockWindow.getTime() + await postOracleOperationBounty(uiWriteClient, managerAddress, { + acceptanceDeadline: currentTimestamp + DAY, + amount: 1n, + maximumInitialAttoWeth: 0n, + minimumInitialAttoWeth: 0n, + operation: 'liquidation', + rewardAmount: 1n, + rewardToken: WETH_ADDRESS, + targetVault: addressString(TEST_ADDRESSES[1]), + validForSeconds: DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, + }) + const minimumToken1ReportAttoEth = await client.readContract({ address: managerAddress, abi: peripherals_OpenOraclePriceCoordinator_OpenOraclePriceCoordinator.abi, functionName: 'minimumToken1ReportAttoEth', args: [] }) + for (let index = 0; index < 4; index += 1) await queueOracleManagerOperation(uiWriteClient, managerAddress, 'liquidation', addressString(TEST_ADDRESSES[1]), 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, index === 0 ? minimumToken1ReportAttoEth : undefined) + const transactionCountBefore = await getUiAccountTransactionCount() + + await expect(acceptOracleOperationBounty(uiWriteClient, managerAddress, 1n)).rejects.toThrow('Operation bounty cannot fit in the pending settlement queue') + + expect(await getUiAccountTransactionCount()).toBe(transactionCountBefore) + }) + + test('loads bounty one directly after newer bounties push it out of the preview', async () => { + const currentTimestamp = await mockWindow.getTime() + const managerDetails = await loadOracleManagerDetails(uiReadClient, managerAddress) + const boardAddress = managerDetails.operationBountyBoardAddress + if (boardAddress === undefined) throw new Error('Expected operation bounty board address') + await wrapWethTestHelper(client, 26n) + await approveToken(client, WETH_ADDRESS, boardAddress) + for (let index = 0; index < 26; index += 1) { + const hash = await client.writeContract({ + abi: peripherals_OpenOracleOperationBountyBoard_OpenOracleOperationBountyBoard.abi, + address: boardAddress, + functionName: 'postOperationBounty', + args: [0, addressString(TEST_ADDRESSES[1]), 1n, DEFAULT_SELF_OPERATION_TIMEOUT_SECONDS, WETH_ADDRESS, 1n, currentTimestamp + DAY, 0n, 0n], + }) + await client.waitForTransactionReceipt({ hash }) + } + + const preview = await loadOracleManagerDetails(uiReadClient, managerAddress) + expect(preview.operationBounties).toHaveLength(25) + expect(preview.operationBounties?.[0]?.bountyId).toBe(26n) + expect(preview.operationBounties?.[24]?.bountyId).toBe(2n) + expect(preview.operationBounties?.some(bounty => bounty.bountyId === 1n)).toBe(false) + + const oldestBounty = await loadOracleOperationBounty(uiReadClient, managerAddress, boardAddress, 1n) + expect(oldestBounty.bountyId).toBe(1n) + expect(oldestBounty.creator).toBe(client.account.address) + expect(oldestBounty.state).toBe('open') + await expect(loadOracleOperationBounty(uiReadClient, managerAddress, boardAddress, 27n)).rejects.toThrow('Operation bounty #27 does not exist') }) test('requestOraclePrice creates a pending report visible via loadOpenOracleReportDetails', async () => { diff --git a/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx b/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx index a9499b1e2..98f9a8f32 100644 --- a/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx +++ b/ui/ts/tests/features/open-oracle/usePriceOracleManager.test.tsx @@ -63,6 +63,12 @@ describe('usePriceOracleManager', () => { } }) const dependencies: UsePriceOracleManagerDependencies = { + acceptOracleOperationBounty: async () => { + throw new Error('acceptOracleOperationBounty should not be called in this test') + }, + claimOracleOperationBounty: async () => { + throw new Error('claimOracleOperationBounty should not be called in this test') + }, createConnectedReadClient: () => ({ getBalance: async () => 100n, }), @@ -82,6 +88,15 @@ describe('usePriceOracleManager', () => { wethShortfallAttoEth: 0n, }), loadOracleManagerDetails, + loadOracleOperationBounty: async () => { + throw new Error('loadOracleOperationBounty should not be called in this test') + }, + postOracleOperationBounty: async () => { + throw new Error('postOracleOperationBounty should not be called in this test') + }, + refundOracleOperationBounty: async () => { + throw new Error('refundOracleOperationBounty should not be called in this test') + }, requestOraclePrice, } let hookState: UsePriceOracleManagerState | undefined diff --git a/ui/ts/tests/protocol/openOracle.test.ts b/ui/ts/tests/protocol/openOracle.test.ts index de3760fc4..8510fffcb 100644 --- a/ui/ts/tests/protocol/openOracle.test.ts +++ b/ui/ts/tests/protocol/openOracle.test.ts @@ -203,7 +203,7 @@ describe('openOracle protocol client', () => { for (const contract of request.contracts) { requestedFunctionNames.push(getContractFunctionName(contract)) } - return [1n, pendingOperationSlotId, [pendingOperationSlotId, 13n], 4n, 0n, 1n, 5n, true, 10n, 40n, 60n] + return [1n, pendingOperationSlotId, [pendingOperationSlotId, 13n], 4n, 0n, 1n, 5n, true, 10n, 40n, 60n, zeroAddress, token1Address, wethAddress] }, readContract: async request => { if (request.functionName === 'getActiveStagedOperations') { @@ -261,6 +261,9 @@ describe('openOracle protocol client', () => { 'lastSettlementTimestamp', 'getActiveStagedOperationCount', 'settlementTime', + 'operationBountyBoard', + 'reputationToken', + 'weth', ]) expect(capturedActiveOperationArgs).toEqual([0n, 25n]) expect(details.activeStagedOperationCount).toBe(40n) diff --git a/ui/ts/types/contracts.ts b/ui/ts/types/contracts.ts index ce5d31e9c..d1732bf96 100644 --- a/ui/ts/types/contracts.ts +++ b/ui/ts/types/contracts.ts @@ -62,6 +62,38 @@ export type ForkAuctionAction = | 'forkUniverse' export type TruthAuctionSettlementMode = 'claim' | 'mixed' | 'refund' export type OracleQueueOperation = 'liquidation' | 'withdrawRep' +export type OracleOperationBountyState = 'open' | 'assigned' | 'paid' | 'refunded' +export type OracleOperationExecutionStatus = 'none' | 'pending' | 'succeeded' | 'failed' +export type OracleOperationBounty = { + acceptanceDeadline: bigint + amount: bigint + bountyId: bigint + creator: Address + executionStatus: OracleOperationExecutionStatus + maximumInitialAttoWeth: bigint + minimumInitialAttoWeth: bigint + operation: OracleQueueOperation + operationId: bigint + operator: Address + reportId: bigint + refundAvailableAt: bigint | undefined + rewardAmount: bigint + rewardToken: Address + state: OracleOperationBountyState + targetVault: Address + validForSeconds: bigint +} +export type OracleOperationBountyInput = { + acceptanceDeadline: bigint + amount: bigint + maximumInitialAttoWeth: bigint + minimumInitialAttoWeth: bigint + operation: OracleQueueOperation + rewardAmount: bigint + rewardToken: Address + targetVault: Address + validForSeconds: bigint +} export type StagedOracleOperation = { amount: bigint operator: Address @@ -232,6 +264,8 @@ export type OracleManagerDetails = { lastSettlementTimestamp: bigint managerAddress: Address openOracleAddress: Address + operationBounties?: OracleOperationBounty[] + operationBountyBoardAddress?: Address pendingOperation: StagedOracleOperation | undefined pendingOperationSlotId: bigint pendingSettlementOperationIds: bigint[] @@ -241,13 +275,15 @@ export type OracleManagerDetails = { queuedOperationCostAttoEth: bigint requestPriceCostAttoEth: bigint settlementTime?: bigint + reputationTokenAddress?: Address stagedOperations?: StagedOracleOperation[] token1: Address | undefined token2: Address | undefined + wethAddress?: Address } export type OpenOracleActionResult = ActionResult & { - action: 'approveToken1' | 'approveToken2' | 'createReportInstance' | 'dispute' | 'executeStagedOperation' | 'queueOperation' | 'requestPrice' | 'settle' | 'withdrawBalance' | 'wrapWeth' + action: 'acceptOperationBounty' | 'approveToken1' | 'approveToken2' | 'claimOperationBounty' | 'createReportInstance' | 'dispute' | 'executeStagedOperation' | 'postOperationBounty' | 'queueOperation' | 'refundOperationBounty' | 'requestPrice' | 'settle' | 'withdrawBalance' | 'wrapWeth' queuedOperation?: StagedOracleQueuedResult stagedExecution?: StagedOracleExecutionResult }