+ 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 mainnet manifest lists this contract directly, so operators and frontends need a
+ precise explanation of the returned bitmask and the built-in 256-step ceiling.
+
+
+
+
+
What getDeploymentMask() Returns
+
+ 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.
+
+
+
+
+ Deployment Status MaskEach configured deployment
+ step owns one bit position in the returned word. The oracle sets that bit only if
+ the step's address currently contains code.
+
+
+
+
+
+ Deployment BitmaskEach configured deployment
+ step contributes one bit position to the returned word.
+
+
+
+
+
+
How Bits Map To Deployment Steps
+
+ 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 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.
+
+
+ With the current mainnet manifest, the low-order mask mapping therefore begins:
+
+
+
+
+
Bit
+
Mainnet step id
+
Label
+
+
+
+
+
0
+
proxyDeployer
+
Proxy Deployer
+
+
+
1
+
multicall3
+
Multicall3
+
+
+
2
+
uniformPriceDualCapBatchAuctionFactory
+
Uniform Price Dual Cap Batch Auction Factory
+
+
+
3
+
scalarOutcomes
+
Scalar Outcomes
+
+
+
4
+
securityPoolUtils
+
Security Pool Utils
+
+
+
+
+ The same pattern continues through the remaining deployment-status step addresses:
+ openOracle, zoltarQuestionData, zoltar,
+ shareTokenFactory,
+ priceOracleManagerAndOperatorQueuerFactory,
+ securityPoolForker, escalationGameFactory, and
+ securityPoolFactory. If that constructor order changes, the bit meanings
+ change with it.
+
+
+ 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.
+
+
+
+
+
Why The Cap Is 256 Steps
+
+ 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.
+
value without touching storage, it belongs in the proof verifier, a
free function, or a test helper instead of widening the mutable stack.
+
+ For the carry-proof data structure itself, including leaf fields, peak bagging, the
+ 64-peak bound, and nullifier replay protection, see
+ Merkle Mountain Range carry proofs.
+
+ Placeholder 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.
+
+
+
+
+
Leaf Shape And Hash Order
+
+ 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 to the beneficiary vault.
+
+
+
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.
+
+
+
+
+
Peaks And The 64-Peak Bound
+
+ 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.
+
+
+
+
+ MMR PeaksThe 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().
+
+
+
+
+ Carry Snapshot BoundThe 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.
+
+
+
+
+
Proof Structure
+
+ 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.
+
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 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.
+
+
+ The verifier requires
+ merkleMountainRangeSiblings.length == peakHeight + peakCount - 1 and
+ 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.
+
+
+
+
+
Snapshots In Child Continuations
+
+ 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.
+
+
+
inheritedUnresolvedTotal and localUnresolvedTotal
+
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.
+
+
+
+
+
diff --git a/docs/operator-reference.md b/docs/operator-reference.md
index a022a1f1..7f129b64 100644
--- a/docs/operator-reference.md
+++ b/docs/operator-reference.md
@@ -51,11 +51,14 @@ contract-first form.
| 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](../solidity/contracts/peripherals/factories/ShareTokenFactory.sol), [ShareToken.sol](../solidity/contracts/peripherals/tokens/ShareToken.sol) |
| Complete-set minting | Complete-set minting checks the next collateral amount against available bond capacity before minting shares, then updates pool accounting before the share-token mint call. | [`SecurityPool.createCompleteSet`](../solidity/contracts/peripherals/SecurityPool.sol) |
| 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](../solidity/contracts/peripherals/SecurityPool.sol) |
+| Fee-index and fee-owed dust | Fee accrual intentionally carries denominator-sensitive remainder in `feeIndexRemainder` and sub-wei ETH remainder in `totalFeesOwedRemainder`. Those values preserve exact value across later accrual steps instead of dropping division dust, and the allowance-scoped `feeIndexRemainder` is cleared whenever allowance ownership changes so old-denominator dust is not reassigned to new allowance holders. | [`SecurityPool.updateCollateralAmount`](../solidity/contracts/peripherals/SecurityPool.sol), [`SecurityPool._clearFeeIndexRemainder`](../solidity/contracts/peripherals/SecurityPool.sol) |
| Retention-rate updates | Retention-rate updates no-op when total bond allowance is zero or when the pool is not `Operational`. | [SecurityPool.sol](../solidity/contracts/peripherals/SecurityPool.sol) |
| Escrowed REP withdrawal lock | `performWithdrawRep` rejects withdrawal while the vault still has REP escrowed in an escalation game; the vault must settle those locks first. | [SecurityPool.sol](../solidity/contracts/peripherals/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 and the vault must migrate forked locks. | [SecurityPool.sol](../solidity/contracts/peripherals/SecurityPool.sol) |
+| REP-to-ownership round-up | `repToPoolOwnershipRoundUp` uses ceiling division when the pool removes vault ownership for an escalation deposit. That intentionally burns enough ownership to cover the requested REP even when the ownership conversion is fractional. | [`SecurityPool.repToPoolOwnershipRoundUp`](../solidity/contracts/peripherals/SecurityPool.sol), [`SecurityPool.depositToEscalationGame`](../solidity/contracts/peripherals/SecurityPool.sol) |
| Escalation deposit wrapper | `depositToEscalationGame` deploys the game on the first valid post-end deposit, previews the accepted amount, removes vault REP ownership with round-up accounting, checks local and global solvency, transfers REP into the game, and records the deposit. | [`SecurityPool.depositToEscalationGame`](../solidity/contracts/peripherals/SecurityPool.sol), [`EscalationGame.recordDepositFromSecurityPool`](../solidity/contracts/peripherals/EscalationGame.sol) |
| Direct ETH receiver | The pool accepts direct ETH only from the forker, its truth auction, or its parent pool. | [SecurityPool.sol](../solidity/contracts/peripherals/SecurityPool.sol) |
+| Vault enumeration | `getVaults(startIndex, count)` pages the append-only vault list in insertion order. `getActiveVaults(startIndex, count)` pages only active vaults in newest-first order by walking the active-vault linked list from `latestActiveVault`. Both return an empty array when `count == 0` or the start index is out of range. | [`SecurityPool.getVaults`](../solidity/contracts/peripherals/SecurityPool.sol), [`SecurityPool.getActiveVaults`](../solidity/contracts/peripherals/SecurityPool.sol) |
## Share Migration
@@ -150,3 +153,18 @@ callback behavior, stale-operation handling, and liquidation boundaries.
| 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](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
| Liquidation snapshot | A staged liquidation becomes stale if target ownership decreases or target allowance changes. A target vault can deposit more REP without invalidating the staged liquidation. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
| Liquidation distance | A staged liquidation must remain at least `minLiquidationPriceDistanceBps` beyond the liquidation threshold when it executes. | [OpenOraclePriceCoordinator.sol](../solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol) |
+
+## Support Module Inventory
+
+The whitepapers focus on protocol flow. The table below names the remaining
+support contracts that matter for integrators, code reviewers, and interface
+inventory work.
+
+| Area | Named contracts and modules | Role |
+| --- | --- | --- |
+| Deployment tracking | [`DeploymentStatusOracle.sol`](../solidity/contracts/DeploymentStatusOracle.sol) | Reports one code-presence bit per configured deployment-step address. See [Deployment Status Oracle](./deployment-status.html). |
+| Carry proof hashing | [`MerkleMountainRange.sol`](../solidity/contracts/peripherals/MerkleMountainRange.sol), [`EscalationGameProofVerifier.sol`](../solidity/contracts/peripherals/EscalationGameProofVerifier.sol), [`EscalationGameTypes.sol`](../solidity/contracts/peripherals/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](./merkle-mountain-range.html). |
+| ERC-20 support | [`ERC20.sol`](../solidity/contracts/ERC20.sol), [`IERC20.sol`](../solidity/contracts/IERC20.sol), [`IERC20Metadata.sol`](../solidity/contracts/IERC20Metadata.sol), [`Context.sol`](../solidity/contracts/Context.sol), [`SafeERC20Ops.sol`](../solidity/contracts/SafeERC20Ops.sol), [`ReputationToken.sol`](../solidity/contracts/ReputationToken.sol) | Base REP token implementation, interfaces, metadata, execution context, and safe transfer wrappers. |
+| ERC-1155 and token ids | [`ERC1155.sol`](../solidity/contracts/peripherals/tokens/ERC1155.sol), [`ShareToken.sol`](../solidity/contracts/peripherals/tokens/ShareToken.sol), [`TokenId.sol`](../solidity/contracts/peripherals/tokens/TokenId.sol), [`IERC1155.sol`](../solidity/contracts/peripherals/interfaces/IERC1155.sol), [`IERC1155Receiver.sol`](../solidity/contracts/peripherals/interfaces/IERC1155Receiver.sol), [`IERC165.sol`](../solidity/contracts/peripherals/interfaces/IERC165.sol), [`IShareToken.sol`](../solidity/contracts/peripherals/interfaces/IShareToken.sol) | Outcome-share token plumbing, interface support, and token-id encoding across universes and outcomes. |
+| Factories and deployers | [`EscalationGameFactory.sol`](../solidity/contracts/peripherals/factories/EscalationGameFactory.sol), [`SecurityPoolFactory.sol`](../solidity/contracts/peripherals/factories/SecurityPoolFactory.sol), [`SecurityPoolDeployer.sol`](../solidity/contracts/peripherals/factories/SecurityPoolDeployer.sol), [`ShareTokenFactory.sol`](../solidity/contracts/peripherals/factories/ShareTokenFactory.sol), [`UniformPriceDualCapBatchAuctionFactory.sol`](../solidity/contracts/peripherals/factories/UniformPriceDualCapBatchAuctionFactory.sol), [`PriceOracleManagerAndOperatorQueuerFactory.sol`](../solidity/contracts/peripherals/factories/PriceOracleManagerAndOperatorQueuerFactory.sol) | Deterministic deployment entrypoints for pool, share-token, oracle-coordinator, escalation-game, and auction instances. |
+| Migration delegates and storage modules | [`SecurityPoolMigrationProxy.sol`](../solidity/contracts/peripherals/SecurityPoolMigrationProxy.sol), [`SecurityPoolForker.sol`](../solidity/contracts/peripherals/SecurityPoolForker.sol), [`SecurityPoolForkerBase.sol`](../solidity/contracts/peripherals/SecurityPoolForkerBase.sol), [`SecurityPoolForkerStorage.sol`](../solidity/contracts/peripherals/SecurityPoolForkerStorage.sol), [`SecurityPoolForkerTypes.sol`](../solidity/contracts/peripherals/SecurityPoolForkerTypes.sol), [`SecurityPoolForkerVaultMigrationBase.sol`](../solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol), [`SecurityPoolForkerVaultMigrationDelegate.sol`](../solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationDelegate.sol) | Split fork-time state, migration execution, and stable proxy identity used while routing parent state into child pools. |
diff --git a/docs/start-here.html b/docs/start-here.html
index 7cdbed10..5df86088 100644
--- a/docs/start-here.html
+++ b/docs/start-here.html
@@ -513,6 +513,63 @@
Documentation Map
Understand how the local dispute contract is split across Solidity modules.
+ The whitepapers focus on protocol flow. The modules below round out the contract map
+ for readers who need the support-layer pieces named explicitly.
+
Continuation withdrawals use Merkle Mountain Range proofs against
the inherited carry snapshot. Each accepted proof advances the
nullifier root, so a carried parent deposit cannot be replayed in
- the child.
+ the child. The exact leaf fields, peak bagging order, proof-length
+ rules, and 64-peak bound are documented in
+ Merkle Mountain Range carry proofs.
If an unrelated external Zoltar fork interrupts the local escalation
diff --git a/docs/whitepaper_zoltar.html b/docs/whitepaper_zoltar.html
index 41885d78..265451e1 100644
--- a/docs/whitepaper_zoltar.html
+++ b/docs/whitepaper_zoltar.html
@@ -738,6 +738,12 @@
6. Questions and Outcome Encoding
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.
+
Categorical Questions
@@ -770,7 +776,7 @@
Scalar Questions
Scalar validity rule
-
The two 120-bit payout numerators must sum to numTicks; otherwise the answer is malformed, not invalid.
+
Bits 254...240 are reserved and must be zero. Any nonzero reserved bit, or any payout pair whose two 120-bit numerators fail to sum to numTicks, is malformed rather than invalid.
@@ -794,8 +800,8 @@
Scalar Questions
first payout numeratorbits 119...0second payout numerator
- Unused high-order padding bits are not part
- of this scalar payload.
+ Bits 254...240 are reserved and must be zero.
+ Nonzero reserved bits are malformed.
@@ -821,7 +827,7 @@
Scalar Questions
First payoutSecond payout
-
Packed Scalar AnswerA scalar answer is easier to read as a namespace flag plus two payout fields: 0 means invalid namespace, while 1 means the payout fields must sum to numTicks.
+
Packed Scalar AnswerA 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.
@@ -841,6 +847,12 @@
Scalar Questions
Scalar ValidityA 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: