Skip to content

feat(vault): reconcile an epoch once it settles - #252

Open
hpmaxi wants to merge 7 commits into
feat/request-allowlistfrom
feat/settlement-dust
Open

hpmaxi wants to merge 7 commits into
feat/request-allowlistfrom
feat/settlement-dust

Conversation

@hpmaxi

@hpmaxi hpmaxi commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #283

Flooring the epoch total and then each claim left a remainder that was never reconciled. It is now. Each epoch records what pricing promised and what has been paid; when its last request settles, the remainder leaves pending_mint_shares and committed, stays in the reserve as fund money, and EpochReconciled says how much. Every subtraction from a running total is checked.

Changes

  • Per-epoch settlement record, view get_epoch_settlement, event EpochReconciled.
  • Checked subtraction everywhere a total goes down (AccountingMismatch, 6070).
  • Two tests pin why the wind-down remainder stays owed.
  • A test pins that an epoch with a request that can never settle stays open.

Deviations

  • The first plan gave wind-down owed "the same treatment on the last claim". That is not safe: a later round needs those units. The remainder stays (at most one unit per holder), and a test shows why.

Acceptance criteria

  • Each epoch records promised and settled shares and assets, and its open requests
  • The last settled request clears the remainder and emits EpochReconciled
  • An epoch with no requests reconciles when priced
  • Every subtraction from a running total is checked
  • A test shows the wind-down remainder stays bounded and is needed later

Test plan

Automated tests

  1. SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 nix develop -c cargo test --locked -p async-vault settlement wind_down
  2. The property test runs 24 random epochs, with the claims in a random order.

Manual verification

None.

Breaking changes

None for callers; a new view and a new event. This version must be deployed fresh, or reached by upgrade only when no request is open: the settlement record counts requests from the moment it exists, so upgrading a vault in place while a request is open would under-count its open epochs, reverting claims from epochs priced before the upgrade and risking an early reconcile of a mixed epoch.

Checklist

  • Self-reviewed my own diff
  • Tests added or updated
  • Docs updated (if applicable)
  • No unrelated changes bundled in

Screenshots

None.

@vercel

vercel Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
strata-vault-kit-app Ready Ready Preview Oct 20, 2026 5:43pm UTC

Request Review

@hpmaxi
hpmaxi marked this pull request as ready for review October 17, 2026 05:16

@luchobonatti luchobonatti left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is L-01's suggestion as written, and reduce covers the clamping half of L-08. One doc thought: the design doc only states the rounding rule for wind-down rounds; the epoch rule (remainder stays in the reserve as fund money) lives in kit-spec alone. Could it get a line in the pricing section too?

Comment thread docs/kit-spec.md
- **Settlement**: pricing records what each epoch promised (`shares_promised`, `assets_promised`, see `get_epoch_settlement`). Claims floor one by one, so together they pay at most that. When the last request of a priced epoch settles, the remainder leaves `pending_mint_shares` and `committed` and the vault publishes `EpochReconciled { epoch, share_dust, cash_dust }`. The cash stays in the reserve, owed to nobody, and reaches every holder through the NAV. A running total that would go below zero fails with `AccountingMismatch` (6070) instead of clamping.
- **A request that can never settle** keeps its epoch open, and the two cases leave different amounts behind. A zero-asset redemption whose holder the share token refuses (`a_request_that_cannot_settle_keeps_its_epoch_open`) leaves only the epoch's ordinary rounding remainder in `committed` — fewer units than the epoch has open requests of that kind — because that request was never owed any cash. A priced deposit whose mint a compliance module refuses (`a_refused_mint_keeps_a_priced_deposit_epoch_open`) is not rounding dust: the mint reverts, so that deposit's whole share entitlement stays in `pending_mint_shares`, on top of the epoch's own remainder, until the mint is allowed. Either way it is still owed to nobody.

**Settlement and upgrades.** The settlement record counts a request only from the moment it exists. A vault upgraded in place to this version while any request is open would under-count its open epochs: claims in epochs priced before the upgrade would revert, and a mixed epoch could reconcile early. This version must therefore be deployed fresh, or reached by upgrade only once no request is open anywhere in the vault.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply upgrades without checking for open requests, so this rule is enforced only by the doc. Could apply_upgrade refuse while any request is open, or could the migration count them?

This branch was successfully deployed

1 active deployment
Preview — c47c6fee Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants