Skip to content

fix(accounting): value foreign-fiat owed openings via the currency asset mark#4251

Closed
TaprootFreak wants to merge 1 commit into
developfrom
fix/cutover-unpriced-owed-suspense
Closed

fix(accounting): value foreign-fiat owed openings via the currency asset mark#4251
TaprootFreak wants to merge 1 commit into
developfrom
fix/cutover-unpriced-owed-suspense

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Incident

Since the ledger went live on prod, the cutover has been rolling back every 5 minutes in an endless fail-loud retry loop, so the whole ledger stayed inert.

Root cause

openBuyFiatOwed valued a non-CHF owed opening with fiatMark(row.outputAsset.id). But BuyFiat.outputAsset is a Fiat entity — its id is a fiat-table id, not an asset-table id. getMarkAt expects an asset id, so for EUR (fiat id 2) it looked up asset id 2 = a delisted DeFiChain token, found no snapshot mark and threw. The 32 open CHF owed rows passed (hardcoded mark 1); the 6 open EUR rows blocked the entire cutover.

Fix

  • fiatMark resolves the fiat currency name to its currency asset(s) via AssetService.getAssetsByName and takes the first candidate carrying a snapshot mark — the same currency-asset route bankMark already uses. A currency with no snapshot mark stays fail-loud (never a silent default).
  • Independently hardens the genuinely delisted-crypto case (buy-crypto owed / manual-debt): an opening whose crypto output asset has no mark books asset-backed (native amount + needsMark) so mark-to-market revalues it once a price returns, instead of blocking the whole cutover; the count + assets are warn-logged.
  • AssetService is injected as a required dependency (not @Optional() with a runtime throw).

Test plan

  • new unit tests: EUR owed valued via the currency-asset mark, CHF via mark 1, no-mark currency stays fail-loud, first-candidate-with-a-mark wins; asset-backed crypto/manual-debt openings + mark-to-market revaluation
  • full jest run green, type-check / lint / format:check clean

…set mark

The cutover valued a non-CHF buy-fiat owed opening with fiatMark(outputAsset.id),
but BuyFiat.outputAsset is a Fiat entity - its id is a fiat-table id, not an
asset-table id. getMarkAt then looked up an unrelated asset (fiat id 2 = EUR hit
asset id 2 = a delisted DeFiChain token), found no snapshot mark and threw, so
every open EUR owed row rolled the whole cutover back in an endless retry loop on
prod (32 CHF rows passed via the hardcoded mark 1, the 6 EUR rows blocked).

fiatMark now resolves the fiat currency name to its currency asset(s) via
AssetService and takes the first candidate carrying a snapshot mark - the same
currency-asset route bankMark already uses. A currency with no snapshot mark
stays fail-loud.

Also hardens the genuinely delisted-crypto case: an owed/manual-debt opening
whose crypto output asset has no mark books asset-backed (native amount +
needsMark) so mark-to-market revalues it if a price returns, instead of blocking
the whole cutover; a summary is warn-logged.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #4253 (already merged to develop).

This PR and #4253 were parallel fixes for the same incident (the cutover fail-loud rollback loop) with the same root cause (buy_fiat.outputAsset is a Fiat whose id collides with asset ids — fiat 2 = EUR, asset 2 = DeFiChain/dBTC — while the mark cache is asset-keyed). #4253 landed first and resolves it via currencyMark / a markAssetByCurrency map threaded through all foreign-currency paths (buyFiat-owed and bankTx return/repeat/unattributed). This PR's fiatMark-by-name is therefore redundant and would now conflict (its diff is against the pre-#4253 base).

The only delta this PR still had over develop was the asset-backed crypto/manual-debt hardening, which:

  • is not exercised by the current cutover (0 feedless open buy_crypto owed rows in the window; both balanceLogDebtPositions assets carry finite snapshot prices), and
  • as written, books the owed opening on a per-asset account LIABILITY/{qualifier}/{asset.uniqueName} that no forward discharge path recognises (payout-order/bank-tx consumers match the CHF-bucket name only), so on settlement/return the per-asset liability is never closed and the CHF bucket is over-debited — a latent, silent owed sub-ledger mis-posting.

That hardening, done correctly (per-asset discharge + tests), is tracked in #4270 — non-urgent, defensive/future-proofing for delisted assets.

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