filecoin-project / filecoin-project/solstice

Devnet/calibnet test checklist: Solstice (FIP-0118)

Open
#41 0 comments 0 reactions 1 assignee Claimed by @irenegia View on GitHub
Dominant language
Solidity
Stars
0
Forks
2
Avg merge
3d 3h
Merged PRs (30d)
10

Description

v1 draft 2026-08-28 by irene; moved here from the working gdoc on 2026-09-04. Comment below; the body is updated in place, with numbers never reused.

Reference: [FIP-0118](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0118.md) on FIPs master; § numbers refer to its sections. v1 was drafted against FIPs PRs [filecoin-project/FIPs#1277](https://github.com/filecoin-project/FIPs/pull/1277) (head d9221ed) and [filecoin-project/FIPs#1279](https://github.com/filecoin-project/FIPs/pull/1279) (head 7f3a2f6), both merged since.

Tags: [D] devnet, [C] calibnet, [D+C] both.

## 1. Miner actor flows

- [ ] **1.1** [D+C] (**Onboard sector with/without deals**) Onboard a new sector on each onboarding path:
- [ ] `ProveCommitSectors3` (with per-sector proofs and with an aggregate proof)
- Run twice: with and without piece manifest.
- When with piece manifest, two sub-cases: with and without the verified allocation key (when with, choose a random value).
- [ ] `ProveCommitSectorsNI`
- Without piece manifest (with piece manifest already aborts).
- → In all cases, the same outcome: sectors get 10x quality, with the `FULL_QA_POWER` flag set, without consulting the verified registry, and pledge and the FIP-0100 daily fee are both set at the 10x level (§1.2).
- [ ] **1.2** [D+C] (**Update content, empty → deals**) SnapDeals (`ProveReplicaUpdate3`):
- [ ] (old still work) on a legacy CC sector (legacy, deal weight = 0):
- Run it twice: with piece manifests carrying `verified_allocation_key` (accepted and ignored) and without, same result.
- → Outcome: 10x quality (once the proof verifies), pledge topped up and the FIP-0100 daily fee added or scaled up with the power change; no datacap is minted, allocated or consumed (none can be).
- [ ] (new) on a CC sector already at 10x quality and still empty of data/deal (deal weight = 0):
- Two variants: a legacy sector already upgraded via `UpgradeSectorQuality`, and a sector onboarded after the upgrade with the `FULL_QA_POWER` flag set at onboarding.
- → Outcome: the snap succeeds, no QAP change, no pledge change, no FIP-0100 fee change.
- [ ] (old and new) on a sector with data (i.e. deal weight ≠ 0, legacy or new sector)
- → Outcome: fails.
- [ ] **1.3** [D+C] (**Increase power, new flow**) `UpgradeSectorQuality`:
- [ ] on a sector already at 10x (a new sector, or a legacy one already upgraded)
- → Outcome: not upgraded again, pledge not raised a second time even if the pledge requirement has risen since the first upgrade, no FIP-0100 daily fee change, not a batch failure, and still extended if its declaration carries a new expiration. The batch is atomic: one invalid declaration aborts the whole call. Same outcome whether the sector reached 10x via `UpgradeSectorQuality` or via SnapDeals.
- [ ] on a legacy sector with quality < 10, with and without extensions
- → Outcome: 10x quality, pledge topped up and the FIP-0100 daily fee added or scaled up with the power change.
- [ ] Run it for a miner with fee debt: fee debt must clear from unlocked balance in the same message, otherwise the whole call aborts.
- [ ] **1.4** [D+C] `ExtendSectorExpiration2`:
- [ ] on a legacy sector or a new sector
- → Same outcome: no verified-registry (f06) lookups, quality unchanged for any sector of any vintage, recorded ratio carried forward. Claim declarations in the call are accepted and ignored (§1.5).
- [ ] **1.5** Test that calling `PreCommit` with `deals_id` → fails.

## 2. Other L1 flows

- [ ] **2.1** DataCap actor (f07):
- [ ] [D] Every state-mutating method rejects with `USR_FORBIDDEN`: `Mint`, `Destroy`, `Transfer`, `TransferFrom`, `Burn`, `BurnFrom`, `IncreaseAllowance`, `DecreaseAllowance`, `RevokeAllowance` (§1.5).
- [ ] [D] Read methods still serve the frozen state: `Name`, `Symbol`, `Granularity`, `TotalSupply`, `Balance`, `Allowance` (§1.5).
- [ ] [D] Existing minted but unused datacap balances are inert: they cannot become allocations (§1.1).
- [ ] **2.2** Verified registry (f06):
- [ ] [D] Every state-mutating method rejects: `AddVerifier`, `RemoveVerifier`, `AddVerifiedClient`, `RemoveVerifiedClientDataCap`, `RemoveExpiredAllocations`, `ClaimAllocations`, `ExtendClaimTerms`, `RemoveExpiredClaims`, and the universal receiver hook (§1.5).
- [ ] [D] `GetClaims` still serves; existing claims are historical records the miner actor no longer reads (§1.1, §1.5).
- [ ] [D] Pending (unclaimed) allocations cannot be claimed (§1.1).
- [ ] [D] No verified-registry event is emitted after the upgrade, because every emitting path rejects (§1.5).
- [ ] **2.3** Market actor (f05):
- [ ] [D] `PublishStorageDeals` with `verified_deal = true`: accepted, the flag is ignored, it stays in state and `GetDealVerified` still returns it (§1.5).
- [ ] **2.4** Reward actor (f02):
- [ ] [D+C] Each block reward flows through `AwardBlockReward`: the consensus stream's portion pays the winning miner at once together with its gas reward, the service stream's portion accrues inside f02 for the share map, the residual burns to f099, and penalties work as today (§2.4.3).
- [ ] [D+C] Send an in-flight datacap message across the upgrade boundary: a direct call fails on its own; a miner or market call proceeds as a no-op, allocation and claim references accepted and ignored (§1.5). On calibnet this is a one-shot test at the real upgrade epoch.

## 3. SRA, Orchestrator flows

Caller in this section: the admitted Orchestrator identity address (after the #34 identity/wallet split, the identity posts and registers; the payout wallet only receives).

- [ ] **3.1** [D+C] (**Bootstrap seating**) Deploy with the initial Orchestrator seated by the activation migration, not through `AddOrchestrator` (§2.2, §2.4.10; #34: needs the deploy-time seed). Then, in quarter 1, read the registry views at the activation epoch, then make one admitted-only call from the seated identity (`RegisterPairs` is enough).
- → Outcome: the views report it admitted with its payout wallet and no bindings from Q1's first epoch; the call succeeds, which proves the seat counts as admitted with `AddOrchestrator` never called.
- [ ] **3.2** [D+C] (**Register pairs**) `RegisterPairs`, three sub-cases:
- [ ] fresh pairs;
- [ ] a batch where one pair is already bound to an admitted Orchestrator;
- [ ] a pair whose previous Orchestrator was removed.
- → Outcome: fresh pairs bind at once and apply from the call epoch, covering the pair's unbound past inside the quarter (back-cover, §2.2); the already-bound pair reverts the whole batch, no partial registration (§3.2); the released pair is claimable and its re-registration covers back to the release epoch only, never the span the removed Orchestrator served (§3.2).
- [ ] **3.3** [D+C] (**Registration near the boundary**) Register a pair less than `REGISTRATION_CUTOFF` before a quarter boundary.
- → Outcome: the registration takes effect only at the next quarter's first epoch. The pair's settlements in the ending quarter do not count for this Orchestrator; counting starts with the new quarter.
- [ ] **3.4** [D+C] (**Post volume**) `PostVolume(Q, fpv)` inside the posting period, plus the rejects: outside the window, and a second post in the same quarter.
- → Outcome: the call carries one USD total, no print references or pricing periods (§3.2); the value binds when the verification window closes, as posted if nobody corrects; both rejects revert (§2.2).
- [ ] **3.5** [D+C] (**Missed window**) An admitted Orchestrator posts nothing for Q.
- → Outcome: its value binds as 0 and the quarter is not blocked (§2.2).
- **3.6** Removed 2026-09-04: off-chain indexer duty (FIL-to-USD conversion at qualifying prints), moved to the separate indexer test plan agreed on the 2026-09-04 rehearsal call. Its calibnet dependency (keeper scripts claiming a fee auction each quarter, a few-USD `MIN_LOT_FLOOR` from the start) moves with it.
- **3.7** Removed 2026-09-04: off-chain pricing rule (`MIN_LOT` fallback), moved with 3.6 to the indexer test plan.
- [ ] **3.8** [D+C] (**Getting paid**) After `SubmitShares`, run a claim from the payout wallet.
- → Outcome: rewards accrue in f02 to the registered wallet in proportion to bound FPV and the wallet claims them (§2.2, §2.4.4, §2.4.5); the identity address cannot claim and the wallet cannot post (#34: the identity/wallet split).

## 4. SRA, Registry-signer flows and permissionless cranks

Caller in this section: both Registry multisigs jointly, except the cranks at the end, which anyone may call. Registry actions bind at once: no hold, no pending queue, no cancellation path (§4.2); the second multisig's approval executes in the same transaction (#34: solstice main still applies a hold). The only cancelable registry state is a half-approved task, and `cancelPending` stays for that and for the held code-upgrade path.

- [ ] **4.1** [D+C] (**Admit**) `AddOrchestrator(orch, wallet)` with two distinct addresses (#34: code takes one).
- → Outcome: binds at once and applies at once: the Orchestrator may register pairs and post volume from the admission epoch, its registrations applying under the §2.2 rules (back-cover to the quarter's first epoch, the cutoff); an Orchestrator admitted in quarter Q posts `FPV(Q)` in Q's posting period and enters the share map that quarter's `SubmitShares` installs, about ten days into Q+1 (§3.2 as of [filecoin-project/FIPs#1286](https://github.com/filecoin-project/FIPs/pull/1286) b45a6e9; the one-quarter admission lag was dropped 2026-09-09). Sub-cases [D]: a duplicate admit reverts; the 65th admit reverts, and succeeds again after one removal.
- [ ] **4.2** [D+C] (**Remove**) `RemoveOrchestrator(orch)`, three sub-cases:
- [ ] during a running quarter;
- [ ] while an ended quarter still awaits its share map;
- [ ] then re-admit the same address.
- → Outcome: the running-quarter removal binds at once through an immediate f02 `ReplaceAddress` naming f099, which drops the entry and leaves every other share untouched, survivors gaining only at the next `SubmitShares` (§2.4.4, §3.2 as of [filecoin-project/FIPs#1286](https://github.com/filecoin-project/FIPs/pull/1286) 7f80715; code: `removeOrchestrator` calls it on main); the guarded removal reverts, and the signers clear their own path by cranking `SubmitShares` first and removing in the next message (§3.2); the removed Orchestrator can no longer post, register, or be corrected, accrued balances stay claimable by its wallet, its pairs are released (tested from the SO side, section 3), and re-admission creates a fresh identity with no bindings and no volume.
- [ ] **4.3** [D+C] (**Replace wallet**) `ReplaceWallet(old, new)`, on the Orchestrator's public request. No extradata argument: dropped 2026-09-02, FIP edit in [filecoin-project/FIPs#1286](https://github.com/filecoin-project/FIPs/pull/1286) (d2ebe47).
- → Outcome: immediate f02 `ReplaceAddress(old wallet, new wallet)`, the standing entry pays the new wallet at once (§2.4.4, §3.2 as of #1286 7f80715; code: `replaceWallet` calls it on main); strictly prospective, the identity does not move and accrued balances stay claimable by the old wallet (§3.2).
- [ ] **4.4** [D] (**Reassign a binding**) `ReassignBinding(pair, orch, inherit)` in both scopes.
- → Outcome: inherit = false applies from the execution epoch, the previous holder keeps the span it served; inherit = true adopts the displaced registration's call epoch under the application rules, and is honored only when the displaced application epoch falls in the current quarter or later, otherwise the move is prospective anyway; the flag rides in the event (#34: flag missing in code) (§3.2). The batch variant is agreed but its FIP bullet is pending; pin cap and atomicity when it lands.
- [ ] **4.5** [D] (**Cancel a binding**) `CancelBinding(pair)`, by the bound Orchestrator for itself (FIP §3.2 as of [filecoin-project/FIPs#1286](https://github.com/filecoin-project/FIPs/pull/1286) 5bebb22; code still governance-only, #48). Two sub-cases: the holder cancels its own pair; a non-holder (another Orchestrator, a Registry multisig) calls it. Text updated 2026-09-09 after the FIP decision; the earlier sketch had governance cancelling too.
- → Outcome: the holder's call deletes the binding and the release applies at the execution epoch, like a release by removal: the Orchestrator keeps the span it served, and a later `RegisterPairs` by any admitted Orchestrator covers back to the release epoch only (§2.2, §3.2); the non-holder's call reverts. Governance releases a pair only through `ReassignBinding` or `RemoveOrchestrator`.
- [ ] **4.6** [D+C] (**Correct a volume**) `CorrectVolume(orch, Q, value)` inside the verification window, four sub-cases: lower a posted value, raise it, set it to zero, and supply the figure for an Orchestrator that did not post.
- → Outcome: each lands and the bound value follows the correction; a `CorrectVolume` outside the window reverts (§2.2, §3.2).
- [ ] **4.7** [D+C] (**Admitted lists**) `SetAdmittedLists(stablecoins, filecoin_pay_contracts)`.
- → Outcome: binds at once, applies from the next quarter boundary, so no quarter being measured changes under the Orchestrators; stores nothing, the event carries both full lists and replaces both entirely, the latest event is the whole allowlist (§3.2). Sub-case [D]: 65 entries in either list reverts.
- [ ] **4.8** [D+C] (**Pricing parameters**) `SetPricingParams(min_lot_floor, min_lot_alpha, price_band, registration_cutoff)` (#34: code has the old two-parameter signature and stores values).
- → Outcome: stores nothing, the single event carries all four values, application from the next quarter boundary, same rule as the lists (§2.3, §3.2); no getter serves them.
- [ ] [D+C] (**Pre-activation installs**) Run `SetAdmittedLists` and `SetPricingParams` before the activation epoch.
- → Outcome: each emits its ordinary event, and the values apply from quarter 1's first epoch, the first boundary after a pre-activation bind (§2.2).
- [ ] **4.9** [D+C] (**Rotate a multisig**) `ReplaceOwner(prev_owner, new_owner)` on the Registry multisigs.
- → Outcome: cooperative rotation binds at once with an event; a call naming a wrong prev_owner reverts (§3.2). Sub-cases [D]: a task half-approved by the replaced owner does not execute after rotation until the new owner approves; the new owner must have code, an EOA reverts (#34: code still requires a Safe).
- [ ] **4.10** [D] (**Calldata identity footgun, documentation test**) Both multisigs submit `setAdmittedLists` with the same sets in different array order.
- → Outcome: two different task ids, never unanimity; pins the byte-identical-calldata requirement of the dual-multisig scheme.

**Permissionless cranks, callable by anyone:**

- [ ] **4.11** [D+C] (**Submit the shares**) `SubmitShares` from an arbitrary address, three sub-cases: the latest bound quarter; the same quarter again; a quarter nobody posted in.
- → Outcome: only the latest bound quarter can be submitted, and only after its verification window closes; skipped quarters are superseded and never submitted late; a resubmission reverts; the zero-total quarter is a benign no-op that still counts as submitted, so the remove guard lifts (§3.2). The submitted map pays the wallets current at submission time.
- [ ] **4.12** [D+C] (**Read gate**) `aggregatedFilecoinPayVolume(Q)` before and after binding.
- → Outcome: reverts until Q's verification window closes, then serves the bound USD sum; a bound quarter where nobody posted reads 0, not an error (§2.2). The SWA's gate consumes this read (section 5).
- [ ] **4.13** [D+C] (**Quarter 1 pipeline**) Run posting, verification and `SubmitShares` end to end in quarter 1.
- → Outcome: the full pipeline works in Q1; the gate check is section 5's business (§2.2).
- [ ] **4.14** [D+C] (**Lagging SubmitShares, skipped quarter**) Let two quarters bind with no submission, then call `SubmitShares` once.
- → Outcome: the call installs the newer quarter's map; the skipped quarter's map is superseded and can never be installed afterwards; during the lag the standing map kept paying, with no retroactive correction (§2.2, §3.2).
- [ ] **4.15** [D+C] (**Empty registry**) Both Registry multisigs remove the last admitted Orchestrator; run a full quarter with the registry empty; then re-admit. (Added 2026-09-04, from the rehearsal-doc review.)
- → Outcome: the removal drops the last entry through `ReplaceAddress` naming f099, so the entire service stream burns from the epoch it binds (§3.2, §2.4.4 as of #1286 7f80715); with nobody to post, the quarter binds 0 and the gate fails (§2.2, §3.1.1); `SubmitShares` is the benign zero no-op: the emptied map stands, the quarter counts as submitted, the remove guard lifts (§3.2); the burn ends only when a re-admitted Orchestrator's volume enters a fresh map: a successor admitted in quarter Q posts `FPV(Q)` in Q's posting period and enters the map about ten days into Q+1, through that quarter's `SubmitShares` (§3.2 and Security as of #1286 b45a6e9). Calibnet reaches this only if the run extends past Q9.

## 5. SWA flows

- [ ] **5.1** [D+C] (**Bootstrap ramp**) Watch w2 across quarter 1 with no gate action.
- → Outcome: w2 ramps 5% to 10% ungated during quarter 1 (§3.1.1).
- [ ] **5.2** [D+C] (**Gate met**) Pass a gate: bound volume at or above the current target.
- → Outcome: w2 steps to `W2_BASE + steps * W2_STEP`, always on the 5pp grid (§3.1.1).
- [ ] **5.3** [D+C] (**Gate failed**) Fail a gate: bound volume below the current target.
- → Outcome: w2 holds, the target stays where it was (the ladder is step-indexed), and the residual burns (§3.1.1). On calibnet run this early, in the first days after activation: the burn grows only while w1 is still declining, w1 stops moving forever after day 9, and calibnet cannot be reset.
- [ ] **5.4** [D+C] (**Sequential latch**) Skip cranking `QuarterlyGateCheck` for a quarter (on calibnet: a no-action weekend quarter; `SubmitShares` for the newer quarter may already have run), then crank.
- → Outcome: quarters are checked in order, one gate per quarter; a late check still evaluates its own quarter's bound volume and catches up without replay, independent of how far `SubmitShares` has advanced (§3.1.1).
- [ ] **5.5** [D+C] (**Gate write rejected at queue time**) Trigger a gate write that fails f02's schedule validation: reuse 5.7's temporary stream, sized to fit at the current w2 but leave no headroom for the next 5pp step (2026-09-04 rehearsal call; parameters: Irene's design pass).
- → Outcome: the queued gate write is rejected and the whole `QuarterlyGateCheck` reverts, staying re-attemptable; once the stream is removed (5.7), the re-attempted check passes (§2.4.8, §3.1.1).
- [ ] **5.6** [D+C] (**Timelock**) Queue a discretionary write and let it apply; queue another and cancel it inside the window; attempt to cancel a pending gate write.
- → Outcome: a queued discretionary write applies only after the hold, either owner cancels it inside the window, and `StepWeightRecords` cannot be cancelled (§2.4.7).
- [ ] **5.7** [D+C] (**Stream lifecycle**) `RegisterStream`, `SetDistribution`, `RemoveStream`: register a temporary stream sized under the current headroom for one quarter, then remove it (2026-09-04 rehearsal call; parameters: Irene's design pass).
- → Outcome: weights re-derive and the residual returns to burn (§2.4.6). The same stream instance serves 5.5's rejection before its removal.
- [ ] **5.8** [D+C] (**Rotate an SWA multisig**) `ReplaceOwner` on the SWA multisigs.
- → Outcome: binds at once (§3.1).
- [ ] **5.9** [D] (**Contract upgrade**) Upgrade the SWA and SRA contracts.
- → Outcome: authorized, held, cancellable, observable on chain, and f02 keeps identity-only authorization across the upgrade (§4.3, Test Cases).
- [ ] **5.10** [D] (**Crank order independence**) For one bound quarter, run `QuarterlyGateCheck` and `SubmitShares` in one order, reset, then in the other order.
- → Outcome: both orders give the same step decision and the same map, since both read the same bound values (§2.2). Needs a reset, so devnet-only; gate-side lateness is 5.4.
- [ ] **5.11** [D+C] (**Overweight stream write**) Propose a discretionary stream write allocating more than the available headroom.
- → Outcome: rejected at queue time by f02's schedule validation (§2.4.8); nothing queues, block production and reward payment continue untouched (§2.4.3).
- [ ] **5.12** [D+C] (**Stream across a step-up boundary**) A discretionary write reduces w2 with the matching `steps` adjustment and moves the freed weight into a new stream lasting more than a quarter; then pass a gate.
- → Outcome: the gate steps w2 +5pp from the reduced level, on the grid (§3.1.1, the counter is the authority); the added stream is untouched, w0 re-derives, weights keep summing to at most 1 (§2.4.8). Note: the call's sketch (10% down to 5%, then back up) leaves the 5pp grid, so the concrete values wait on Irene's design pass.
- [ ] **5.13** [D+C] (**Terminal cap**) After the eighth gate step lands w2 at `W2_CAP`, call `QuarterlyGateCheck` again with volume above any target.
- → Outcome: the call reverts on the steps bound; w2 rests at 50% and no volume can push past it (§3.1.1). Calibnet reaches this only if the rehearsal extends past the catch-up quarters.
- [ ] **5.14** [D] (**Application-time discard and self-heal**) Cancel a queued entry that an admitted gate write depended on, so the due gate write fails validation at application.
- → Outcome: the write is discarded with its event, processing continues, the award path never fails; the next passing gate restores the full correct level, w2 sitting one level low until then (§2.4.8).
- [ ] **5.15** [D+C] (**Occupied slot**) Queue a discretionary write, then queue a second write into the same (id, op) slot before the first applies; then cancel and requeue. Added 2026-09-09.
- → Outcome: the second write is rejected at queue time; after the cancel the requeue succeeds with a fresh timelock, no path changes a pending payload while keeping its effective epoch (§2.4.7).
- [ ] **5.16** [D+C] (**Activation floor**) `RegisterStream` with `activation_epoch` below queue epoch + `swa_timelock_epochs`, then with one at or above it. Added 2026-09-09.
- → Outcome: the first is rejected; the second queues with the caller-supplied `activation_epoch` as its effective epoch (§2.4.7).

## 6. Adversarial, wip

- [ ] **6.1** [D] Non-owner calls to every governance method on SWA and SRA are rejected (§3.1, §3.2).
- [ ] **6.2** [D] Garbage parameters to each new method (wrong types, absurd values, empty batches): clean rejection, no state change.
- [ ] **6.3** [D] Overclaimed `PostVolume`: any observer detects the mismatch by recomputation; correction lands inside the window; after binding the remedy is audit or removal and the share effect stays bounded to that quarter (§2.2).
- [ ] **6.4** [D] Duplicate and contested registrations: re-register a bound pair, register an unknown pair, race a registration against the cutoff (§2.2, §3.2).
- [ ] **6.5** [D] Boundary races: settlement and print events at the last epoch of a quarter land in the right quarter (§2.2, §2.3).

## 7. Observability and data validation, wip

- [ ] **7.1** [D+C] Every registry and parameter change emits its event, and the binding timeline reconstructs from events alone (§2.2, §3.2).
- [ ] **7.2** [D+C] The reference indexer recomputes every posted FPV to the same figure (§2.2, determinism rule). Run with the reference indexer during the rehearsal; the indexer's own correctness suite stays its team's work.
- [ ] **7.3** [D+C] All five f02 queue event types fire, including explicit failure rollback (§2.4.7, Test Cases).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.