Skip to content

fix(cardano): move the pots when a treasury withdrawal enacts - #1227

Merged
scarmuega merged 1 commit into
mainfrom
fix/treasury-withdrawal-pot-movement
Aug 19, 2026
Merged

fix(cardano): move the pots when a treasury withdrawal enacts#1227
scarmuega merged 1 commit into
mainfrom
fix/treasury-withdrawal-pot-movement

Conversation

@scarmuega

@scarmuega scarmuega commented Aug 18, 2026

Copy link
Copy Markdown
Member

Plan

plans/dolos-governance-treasury-withdrawal-pots.md — divergence B of the mainnet governance oracle replay (the plan was renamed when divergence A was carved into plans/dolos-governance-drep-snapshot-timing.md): dolos held in treasury what db-sync held in rewards, off by exactly 649,764,674,000,000 lovelace — the sum of every TreasuryWithdrawal enacted at or before epoch 645.

Code read: confirmed

The plan's reading holds on main:

  • ewrap/enactment.rs — enacting a TreasuryWithdrawal emitted one per-account delta per (credential, amount) and nothing else.
  • model/accounts.rs — that delta's apply does exactly one thing: credit stake.rewards_sum. Per-account value lands, which is why the 17 mainnet recipients reconcile individually.
  • pots.rsPotDelta had no field for enacted withdrawals, so apply_shelley_delta never debited pots.treasury nor credited pots.rewards. Later transaction withdrawals subtract from pots.rewards, so the miss shows as rewards short / treasury long by exactly the enacted total.

Two additions to the read, found while implementing:

  1. The effective split is real, and it is MIR-shaped. The ledger's applyEnactedWithdrawals domain-restricts the enacted withdrawal map to credentials present in the rewards UMap — i.e. registered at the boundary — and discards the rest: no account credit and no pot movement. Dolos previously credited any existing account row unconditionally (registered or not) and panicked (expect("existing account")) on a credential with no account row at all. Both are fixed by the same gate.
  2. The enacted proposal's deposit refund is scheduled for the opening epoch (scheduled_or_default), while the withdrawal credit lands on the live position — noted here because the harness test pins both.

What changed

Mirrors the treasury_mirs pair in the same four places:

  • ewrap/loading.rs — the finalize pass records which withdrawal targets are registered at the boundary (deliverable_withdrawal_targets), read at proposal-classification time like the MIR registration check.
  • ewrap/enactment.rsenactment_deltas only emits per-account credits for deliverable targets; the visitor accumulates effective_treasury_withdrawals / invalid_treasury_withdrawals on the boundary work, warning on each discard.
  • model/epochs.rsEndStats carries treasury_withdrawals (#[n(15)]) and invalid_treasury_withdrawals (#[n(16)]), both #[cbor(default)] so pre-fix rows decode.
  • pots.rs + estart/reset.rsPotDelta.treasury_withdrawals (#[n(25)], #[cbor(default)]), wired from EndStats; apply_shelley_delta subtracts it from pots.treasury and adds it to pots.rewards. Byron is unaffected (apply_byron_delta zeroes both pots).

Tests

  • pots::tests::treasury_withdrawal_moves_pots_across — unit: the pair moves, supply conserved (uses the mainnet delta figure).
  • ewrap::loading::ratification_tests::enacted_withdrawal_moves_pots_across_the_boundary — harness (ToyDomain, real EWRAP finalize + ESTART shard/finalize commit path): an enacted withdrawal debits pots.treasury, credits pots.rewards, the account is credited, and Pots::is_consistent() holds across the transition — done criterion 1.
  • ewrap::enactment::tests::undeliverable_withdrawal_emits_no_account_delta — the discard case.

Verification run: cargo test --all-features (all suites green, 253 in dolos-cardano), cargo clippy --all-targets --all-features -- -D warnings clean, cargo +nightly fmt --all -- --check clean.

Consumer-visible surface change

Every Conway-era boundary's EpochState treasury and rewards figures change value on a rebuilt/resynced store — minibf, the epochs log, and Stelae snapshots published pre-fix will disagree with post-fix ones. Flagging here for the v1.7 release notes and the breaking-change sweep.

Plan criteria

All met. Divergence A (DRep voting powers, 14 credentials + Abstain bucket) was carved into plans/dolos-governance-drep-snapshot-timing.md by owner ruling; this PR ships B alone per the plan's scope decision, and the mainnet re-read below is that plan's baseline.

🤖 Generated with Claude Code

Preprod verification (plan criterion 3) — done

  • dolos doctor rebuild-state --rewrite-logs --force in place on the governance preprod instance (17G archive, epoch 307), with this PR's binary (824240e): 3066 s ≈ 51 min wall-clock, exit 0 — in line with the ~1 h datum.
  • Full-width dolos data check on the resulting store: cursors / archive-continuity / account-epochs / epoch-log / totals — 0 issues, "no consistency issues found".
  • No-regression proof: preprod and preview have zero enacted treasury withdrawals (Koios: 79 resp. 435 proposed, 0 enacted), so on those networks the fix must move nothing — and it doesn't: the post-fix rebuilt EpochState is content-identical to a pre-fix rebuild of the same archive (diff clean up to HashSet iteration order). Mainnet is the only network where the pot movement is observable on-chain; the harness test carries the mechanism proof.

Divergence A — mechanism findings (tracked on the plan, deliberately not fixed here)

Three code-anchored mechanisms, all one class: dolos's distribution accumulation reads live stake in the EWRAP shard passes, before the finalize-pass account mutations, while the ledger's pulser snapshots after all of them (Conway Epoch.hs ordering: applyEnactedWithdrawalsreturnProposalDeposits → pool reap refunds → setFreshDRepPulsingState):

  1. Same-boundary enacted-withdrawal credits are missing from dolos's snapshot; the mainnet recipients all delegate always-abstain → the Abstain-bucket shortfall class.
  2. PoolDepositRefund lands on the scheduled stake position, so a same-boundary pool-deposit refund misses the snapshot the ledger includes — matches the one dolos-low offset of exactly −500,000,000.
  3. A proposal resolved at boundary B still passes is_active(B+1) (was_enacted requires current > ratified + 1), so its deposit stays in the snapshot deposit-set for one extra boundary while its refund already sits in live rewards — a one-epoch double count matching the +100,000,000,000 dolos-high offset exactly.

These have their own blast radius (every boundary's DRep voting powers) and are proposed as a carve into their own plan.

Mainnet verification (plan criteria 3–4) — done

The retained mainnet-gov instance (full genesis replay on shipped v1.7.0-alpha.0, 81f7ba7d) had its state re-derived from its own archive with this PR's binary — dolos doctor rebuild-state --target … --stop-epoch 646 --force, no network, no snapshot re-import, no archive rewrite. 26,206 s ≈ 7.28 h, against the ~23.5 h the original genesis replay cost. Cursor landed on the archive tip, slot 193,535,999. The instance's own stores were not written; the read below is from the rebuilt store.

Rows 18 and 19 — exact

field pre-fix rebuilt db-sync/Koios
pot treasury 2123528392394011 1473763718394011 1473763718394011
pot rewards 153772737636833 803537411636833 803537411636833

Both move by exactly 649,764,674,000,000 — the sum of every TreasuryWithdrawal enacted at or before epoch 645 — in opposite directions, supply conserved to the lovelace.

Regression — the fix moves two pots and nothing else

The rebuilt store was diffed against the pre-fix instance store, dump for dump, at the same tip:

namespace result
epochs only pot treasury and pot rewards differ; reserves, utxos, deposits, fees, gathered fees, pparams count, protocol version and epoch nonce are byte-identical
proposals identical
dreps identical
gov (committee, constitution, DRep distribution) identical

Rows 7 and 9 — baseline for the carved plan

Unchanged by this fix, as expected: Abstain 9,412,108,368,313,004 and NoConfidence 176,451,764,046,729 in the rebuilt store, identical to pre-fix. The oracle's divergences against Koios (14 of 893 DRep powers; Abstain short by 5,996,004,893,013) therefore carry over untouched as the baseline for plans/dolos-governance-drep-snapshot-timing.md.

CI note

cargo deny (advisories, bans) fails on this branch and equally on main — the advisories check flags RUSTSEC-2026-0258 (h2, unbounded empty DATA frames), which reaches the tree transitively as both h2 0.3.26 and h2 0.4.9. This PR touches no dependencies, so the failure is pre-existing and unrelated; clearing it is a dependency-bump of its own. Every other check is green.

(Corrected: an earlier revision of this note attributed the failure to a toolchain-install error, which is a non-fatal line earlier in the same job log.)

Summary by CodeRabbit

  • New Features

    • Treasury withdrawals to registered accounts are now delivered from the treasury to rewards.
    • Withdrawals to unregistered accounts are rejected and remain in the treasury.
    • Epoch accounting now reports delivered and invalid treasury withdrawals.
  • Bug Fixes

    • Corrected pot transitions and supply accounting for enacted treasury withdrawals.
    • Improved handling of invalid withdrawal targets during governance enactment.

An enacted TreasuryWithdrawal credited the recipient account and left
both pots untouched, so every Conway boundary that enacted one drifted
treasury high and rewards low by the enacted total — 649,764,674 ADA by
mainnet epoch 645, the exact misclassification the governance oracle
measured against db-sync.

Mirror the treasury-MIR pair end to end: the finalize pass records which
withdrawal targets are registered at the boundary (the ledger's
applyEnactedWithdrawals restricts the enacted map to the rewards UMap
and discards the rest, so an undeliverable withdrawal now moves nothing
and no longer panics on a missing account row), the enactment visitor
accumulates the effective total on the boundary work, EndStats carries
it next to effective_treasury_mirs, and apply_shelley_delta debits
treasury and credits rewards by it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ef3cb26-f51f-4d95-8edd-369f2e724e79

📥 Commits

Reviewing files that changed from the base of the PR and between f3ef1ee and 824240e.

📒 Files selected for processing (7)
  • crates/cardano/src/estart/reset.rs
  • crates/cardano/src/ewrap/enactment.rs
  • crates/cardano/src/ewrap/loading.rs
  • crates/cardano/src/ewrap/mod.rs
  • crates/cardano/src/ewrap/wrapup.rs
  • crates/cardano/src/model/epochs.rs
  • crates/cardano/src/pots.rs

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Changes

Treasury withdrawal accounting

Layer / File(s) Summary
Accounting contracts and pot application
crates/cardano/src/model/epochs.rs, crates/cardano/src/pots.rs
EndStats and PotDelta store treasury withdrawal values. Effective withdrawals move value from the treasury pot to the rewards pot.
Boundary target loading and state
crates/cardano/src/ewrap/mod.rs, crates/cardano/src/ewrap/loading.rs
Boundary processing records registered withdrawal credentials and tracks effective and invalid withdrawal totals.
Enactment classification and filtering
crates/cardano/src/ewrap/enactment.rs
Enactment processing emits deltas only for deliverable withdrawal credentials and updates withdrawal totals.
Epoch statistics and reset integration
crates/cardano/src/ewrap/wrapup.rs, crates/cardano/src/estart/reset.rs, crates/cardano/src/ewrap/loading.rs
Epoch finalization records withdrawal statistics. Epoch reset applies effective withdrawals and end-to-end tests verify pot consistency.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 82424

The change corrects treasury and rewards pot accounting for enacted withdrawals, with targeted tests and preprod/mainnet verification reported as passing. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant BoundaryLoading
  participant BoundaryWork
  participant Enactment
  participant EndStats
  participant PotDelta
  BoundaryLoading->>BoundaryWork: load registered withdrawal targets
  Enactment->>BoundaryWork: classify effective and invalid withdrawals
  BoundaryWork->>EndStats: record withdrawal totals
  EndStats->>PotDelta: provide effective withdrawals
  PotDelta->>PotDelta: transfer treasury value to rewards
Loading

Possibly related PRs

  • txpipe/dolos#806: Both changes modify PotDelta and epoch pot accounting for withdrawal-related transfers.

Suggested labels: area:minibf

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving treasury and rewards pots when a treasury withdrawal is enacted.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/treasury-withdrawal-pot-movement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@scarmuega

Copy link
Copy Markdown
Member Author

Status update (plan trail):

  • The plan (plans/dolos-governance-pot-and-power-divergences.md) is now blocked on two founder rulings, recorded as escalation records on the plan: (1) carving divergence A — the three distribution-snapshot-timing mechanisms documented in the body — into its own plan; (2) approving the mainnet --target rebuild of the retained mainnet-gov instance (exact execution plan in the escalation record; est. 8–15 h vs the 23.5 h full-replay datum).
  • Preprod criterion 3 is complete (evidence in the body). A parallel session is gathering the plan's A line-3 per-credential evidence (Koios account_history pinning for the divergent DReps) — its results will feed the A ruling.
  • This PR stays draft until the mainnet confirmation (plan criteria 4–5) lands.

@scarmuega
scarmuega marked this pull request as ready for review August 19, 2026 08:59
@scarmuega
scarmuega merged commit d9a847d into main Aug 19, 2026
16 of 17 checks passed
@scarmuega
scarmuega deleted the fix/treasury-withdrawal-pot-movement branch August 19, 2026 11:05
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.

1 participant