Truth Auction
When Zoltar forks, Statoblast evaluates each resulting child universe after migration. If a child pool lacks the ETH needed to fully collateralize its open interest, Statoblast initiates a truth auction for that pool. The fork may have been triggered either by a direct Zoltar fork or by the Statoblast Escalation Game.
During the migration period, security vault holders 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 collateral, migrated REP state, and converted child-universe REP backing that actually move into it.
Because collateral follows migration rather than being copied in full, a child pool can retain 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.
The auction has an ETH raise target and a child-universe REP sale cap.
Statoblast runs the resulting dual-cap process through
the UniformPriceDualCapBatchAuction contract, then
writes winning claims back into vault state rather than paying out
simple token transfers, so bidders buy into the child pool
and its matching security-bond allowance instead of merely receiving
REP.
Operational Lifecycle
Security assumptions: A02 viable operating costs, A11 practical migration, A03 independent bidders, A12 efficient REP conversion, A16 timely inclusion, A06 lifecycle executors, A20 bid-data availability, A25 safe immutable parameters, and A22 native-ETH recipient compatibility.
For any child pool with a collateral shortfall, the truth auction can
only begin after fork migration has closed. The migration window lasts
through
forkActivationTime + SecurityPoolUtils.MIGRATION_TIME,
inclusive. forkActivationTime is recorded when this parent
pool enters PoolForked; it is pool-local and can be later
than Zoltar's universe forkTime. See
FORK-05 for the
canonical timing invariant. Until that window ends,
vault holders can still choose which child universe to support, so the
protocol cannot know the final collateral shortfall.
After migration closes, anyone may call startTruthAuction.
The forker then starts the underlying auction for the affected child pool,
using the pool's ETH repair target and REP sale cap. Bidders submit ETH
directly to the auction during the one-week bidding window.
When bidding ends, anyone may call finalizeTruthAuction.
Finalization clears the batch auction, sends the raised ETH into the
repair path, and computes the accounting rates used to settle bidders.
The child activates with legitimate migration 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. This is the security model's
EG02 full-repair exclusion. Finalization rejects caller contribution ETH,
and forced ETH remains unaccounted surplus. On the underfunded path,
qualifying bidders collectively receive maxRepBeingSold; when
no bid qualifies, no REP is sold. Any unsold REP remains behind inherited
ownership. A zero-migration full-cap sale is the special case where every
auctionable REP is sold and the inherited denominator has no live child
owner; see
Zero-migration full-cap ownership.
After finalization, winning bids are written into child-pool vault accounting. Winners receive ownership in the child pool plus their proportional share of the auctioned security-bond allowance, rather than a direct REP token transfer.
Auction Lifecycle After migration closes, a child-pool shortfall can be repaired through auction. ETH bids restore collateral, while winning claims are settled into vault accounting. 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.
| Phase | External call | Auction call | Who can call it | Effect |
|---|---|---|---|---|
| Start auction | startTruthAuction |
startAuction |
Anyone may call the wrapper; the forker calls the owner-only auction function. | Opens the auction using the child pool's repair target and REP sale cap. |
| Submit bid | Bidder calls the auction directly | submitBid |
Any bidder during the one-week auction window, with
msg.value >= minBidSize.
|
Supplies ETH demand for the repair. The minimum bid is
max(ethRaiseCap / 100000, 1 wei); final bid treatment is
defined by clearing.
|
| Finalize auction | finalizeTruthAuction |
finalize |
Anyone may call the wrapper after the auction window has passed; the forker calls the owner-only auction function. | Clears the auction, routes raised ETH into the repair path, and computes bidder settlement rates. |
| Refund losing bids | settleAuctionBids |
refundLosingBidsFor |
Anyone may fetch a bounded bid page with
getBidderBidPage or getBidPageAtTick,
then filter those results down to one bidder or vault and pass the
selected (tick, bidIndex) references into the
forker's refund batch.
|
Allows currently losing bids to exit before the auction is finalized. |
| Settle winning bids |
claimAuctionProceeds or
settleAuctionBids
|
withdrawBids |
Anyone may fetch bounded bid pages, group the returned bids for
one bidder or vault, then settle the selected
(tick, bidIndex) entries in one or more bounded
batches instead of settling the entire bid set at once.
|
Writes winning claims into child-pool vault accounting as pool ownership plus proportional security-bond allowance. |
| Pull a deferred refund | None | withdrawPendingEthRefund |
The bidder calls the auction after a prior positive refund push was rejected and once its address can accept ETH. |
Transfers the bidder's complete pendingEthRefunds
balance without replaying or changing the settled bid.
|
The direct auction finalize path allows finalization at
auctionStarted + AUCTION_TIME. The public
finalizeTruthAuction wrapper is slightly stricter and
requires the timestamp to be greater than
truthAuctionStarted + SecurityPoolUtils.AUCTION_TIME.
How Clearing Works
Security assumptions: A01 bidder incentives, A12 efficient REP conversion, and A13 REP liquidity.
Bidders submit ETH at discrete price ticks, where each tick encodes an ETH-per-REP limit price. The auction then 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.
The tick math follows the contract's tickToPrice
function. Tick 0 means 1 ETH/REP. Each step
up multiplies price by roughly 1.0001, and each step down
divides by the same factor, so the price at tick
tickIndex is approximately
1.0001tickIndex ETH/REP. In other words, a higher tick
means the bidder is willing to pay more ETH per REP, which is why
higher ticks are evaluated first during clearing.
Two caps define the uniform-clearing stopping point. The first cap is
ethRaiseCap, the amount of ETH the child pool is trying to
restore. The second cap is maxRepBeingSold, the maximum
child-universe REP inventory the pool is willing to sell. 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. The contract finds that
result by descending aggregate subtree totals, not by looping over
every bid one by one.
Underfunded Clearing
Intentional loss allocation. A truth auction is both
a collateral-repair mechanism and a penalty for security capital that
does not migrate into the child. The cap-implied
underfundedThreshold qualifies bids; it is not an
execution-price floor.
When demand never reaches a full clearing price, the auction switches
into the underfunded branch. Finalization computes
underfundedThreshold = ceil(ethRaiseCap · 1e18 /
maxRepBeingSold), 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.
The child activates with the ETH actually routed during migration plus the ETH retained by the auction. It may remain undercollateralized, and open-interest holders are not guaranteed to be made whole. That shortfall is the intended consequence of insufficient demand: non-migrating security positions lose the auctioned economic value, while users of the affected child bear any remaining collateral impairment.
Underfunded ThresholdPRICE_PRECISION = 1e18. The ceiling tick is the bid-eligibility
boundary, not an execution-price floor. Solidity assigns the REP cap by
differencing cumulative floor allocations at fixed bid positions.
The interactive example below is where those branches become
concrete. The Binding condition output reports one of four
outcomes: ETH cap, REP cap,
both caps, or underfunded. The first three
are uniform-clearing outcomes, while underfunded is the
weak-demand path.
The comparison table and examples apply the branch rule above at the
binding level and its boundaries. After clearing,
SecurityPoolForker.claimAuctionProceeds maps
that purchased REP into the corresponding pool-ownership credit and
auctioned allowance share for the child branch.
| Bid position | Uniform-clearing branch | Underfunded branch |
|---|---|---|
| Above the binding level | Wins in full at the uniform clearing price. | Qualifies only if its tick is at or above the cap-implied qualification clearingTick; qualifying bids share the complete REP sale cap. |
| At the binding level | Shares the marginal fill FIFO inside that tick. | Qualifies at the threshold boundary, then shares the complete REP sale cap by retained ETH. |
| Below the binding level | Loses and is refunded. | Loses and is refunded. |
Worked Underfunded Examples
The examples below use maxRepBeingSold = 10 REP and
ethRaiseCap = 20 ETH, so the formula above gives
2 ETH/REP. Figures use human-unit approximations; the contract
floors per-bid allocations in atomic units.
| Example | Bid ladder | Threshold qualification | Outcome |
|---|---|---|---|
| Two-bidder underfunded prefix |
Bob: 6 ETH @ 4 ETH/REP
Alice: 4 ETH @ 2 ETH/REP
|
At Bob's higher tick, cumulative demand is
6 / 4 = 1.5 REP, so the REP cap is not reached.
At Alice's lower tick, cumulative demand is 10 / 2 = 5 REP, so funded clearing still fails.
Both limits meet the 2 ETH/REP qualification threshold.
|
The system keeps 10 ETH.
Bob and Alice purchase 10 REP pro rata by
winning ETH.
Bob receives 6 REP and Alice receives 4 REP,
both at an effective price of 1 ETH/REP.
|
| Three-bidder underfunded prefix |
Alice: 5 ETH @ 5 ETH/REP
Bob: 3 ETH @ 3 ETH/REP
Carol: 2 ETH @ 2 ETH/REP
|
At the active ticks, cumulative demand is
5 / 5 = 1 REP, then
8 / 3 ≈ 2.67 REP, then
10 / 2 = 5 REP.
All of those remain below the 10 REP cap, so
the funded branch never triggers.
All three bid limits meet the 2 ETH/REP qualification threshold.
|
The system keeps 10 ETH.
Alice, Bob, and Carol share 10 REP pro rata
by winning ETH.
Alice receives 5 REP, Bob 3 REP, and Carol
2 REP, all at 1 ETH/REP.
|
| Tiny-demand edge case |
Alice: 1 ETH @ 0.11 ETH/REP
No other positive-price bids. |
At Alice's tick, cumulative demand is
1 / 0.11 ≈ 9.09 REP, so funded clearing
remains below the 10 REP cap and fails.
Alice's 0.11 ETH/REP limit is below the
2 ETH/REP qualification threshold.
|
The system refunds 1 ETH.
Alice receives no REP; the inventory remains unsold. |
Boundary note: if a lower positive bid would make cumulative demand reach the REP cap at that lower tick, the auction no longer stays in the underfunded branch. It becomes a normal funded clearing at that tick, with the marginal lower-price tick filled only up to the REP cap. If that tick contributes more ETH than the cap accepts, the unfilled remainder is refunded.
Gas and Spam Bounds
Security assumption: A02 viable operating costs.
The main spam risk is a bidder trying to fill bid slots or tick levels cheaply enough to block later demand. The auction avoids that failure mode by not capping total bids or active price levels at all.
Finalization remains bounded without those caps because it does not
iterate over raw bids. Same-tick bids append to per-tick arrays for
later batched withdrawal. Read helpers expose bounded offset/limit
slices of bids at one tick or for one bidder, and settlement later
uses selected TickIndex references from those reads to
process bids in bounded batches. Clearing itself uses aggregate ETH
totals stored in an AVL tree keyed by tick. The tree exists so the
contract can keep all valid price levels open during bidding, yet
still find the highest winning region by descending aggregate subtree
totals instead of scanning every submitted bid. Finalization
therefore pays for tree descent across the finite tick domain rather
than for the total
number of bids in the auction. The valid tick range is finite:
[-524288, 524288], or 1,048,577 possible
price levels. An AVL tree containing every possible tick has maximum
height 28, so finalize() descends bounded
tree paths and pruned subtrees instead of risking an out-of-gas loop
over every submitted bid.
The gas tests cover both funded and underfunded finalization over a
synthetic max-depth tree for the full tick domain. They assert each
path remains below 20,000,000 gas and print measured gas
values in the test output, so the documented worst case is a max-depth
AVL-tree finalization path that still stays under that bound.
Settlement Modes
Security assumption: A22 native-ETH recipient compatibility.
Currently losing bids can be refunded before finalization once current demand is enough to find a clearing tick. Only ticks below that found clearing tick can be withdrawn through the pre-finalization refund path; binding or potentially winning bids stay in the auction.
Refund delivery is independent from bid settlement. The auction first
marks each selected bid and fixes its REP and allowance result, then
attempts to push any positive ETH refund with an explicit
gas: 30,000 call argument. Because the call transfers
nonzero value, the EVM adds its 2,300-gas value-transfer
stipend, giving recipient code an effective maximum of
32,300 gas. If the bidder rejects, reverts, or exhausts
that bounded callback budget, settlement remains complete and the ETH
is added to pendingEthRefunds. The bidder can later pull the
complete balance with withdrawPendingEthRefund; that
caller-initiated transfer is not subject to the push cap, and a
rejected pull leaves the balance unchanged. The auction emits the
balance clear before that callback, so a reentrant settlement's new
deferred refund follows the clear in receipt order.
| Finalized case | Bid treatment | Why it works this way |
|---|---|---|
| Losing bids | Receive ETH refunds. | They did not contribute to the accepted clearing demand. |
| Normal winning bids above clearing tick | Convert all accepted ETH into purchased REP at the uniform clearing price. | Higher bids should receive the same execution price as the marginal winner. |
| Normal marginal clearing tick | May be partially filled and partially refunded; same-tick bids fill in First in First Out submission order. | First in First Out gives deterministic marginal allocation without iterating over all same-tick bids during finalization. |
| Underfunded winning ticks | Settle the fixed outcome produced by clearing; non-winners refund. | Settlement does not recompute branch selection or qualification. |
That settlement is why a winning bid buys more than a token transfer.
The forker withdraws finalized bid results, converts purchased REP into
pool ownership with auctionPoolOwnershipPerRep, and adds
the matching slice of auctionedSecurityBondAllowance to
the bidder's vault. Any auction that purchases REP assigns the complete
unmigrated allowance among its winners; a zero-REP auction assigns none.
Allowance uses the same fixed cumulative retained-ETH ordering, so its
integer remainder also has a deterministic recipient independent of claim order.
Because REP and allowance use independent integer numerators, a winning
dust bid whose REP share rounds to zero can still receive a positive
allowance share; settlement credits that allowance without requiring
positive REP in the same claim.
Finalization rejects explicit repair contributions. In other words,
the bidder is buying a share of the
repaired child-pool vault state, including the debt-bearing allowance
that still has to be backed.
Before a winner claims, its reserved auction allowance remains part of
the child's fork balance sheet but is not assigned to a responsible
vault. It therefore does not accrue fees, affect retention utilization,
or secure newly minted complete sets. Claim settlement adds that
allowance to the bidder vault and to
feeEligibleSecurityBondAllowance atomically.
Zero-migration full-cap ownership. When no vault REP
migrated into the child and the auction sells every auctionable REP,
there is no live child owner behind the parent's inherited denominator.
The forker therefore sets
auctionPoolOwnershipPerRep to
PRICE_PRECISION and sets the final denominator to
repAvailable * PRICE_PRECISION. Each winner's ownership
then round-trips through poolOwnershipToRep 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 ownership rate from that residual REP.
Zero-effective-price ticks are rejected when bids are submitted, so underfunded settlement cannot assign REP to bids whose rounded ETH/REP price is zero.
Both finalized allocation paths derive each bid's REP from its fixed cumulative winning-ETH position. The bid receives the difference between rounded cumulative REP after and before that position. The companion allowance allocation differences cumulative floors over the same position. This reconciles paged withdrawals to both aggregate allocations while making rounding independent of claim order.
Interactive Clearing Example
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.
Interactive Demand CurveThe 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.