fix(accounting): value foreign-fiat owed openings via the currency asset mark#4251
fix(accounting): value foreign-fiat owed openings via the currency asset mark#4251TaprootFreak wants to merge 1 commit into
Conversation
…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.
|
Closing as superseded by #4253 (already merged to This PR and #4253 were parallel fixes for the same incident (the cutover fail-loud rollback loop) with the same root cause ( The only delta this PR still had over
That hardening, done correctly (per-asset discharge + tests), is tracked in #4270 — non-urgent, defensive/future-proofing for delisted assets. |
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
openBuyFiatOwedvalued a non-CHF owed opening withfiatMark(row.outputAsset.id). ButBuyFiat.outputAssetis a Fiat entity — its id is afiat-table id, not anasset-table id.getMarkAtexpects 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
fiatMarkresolves the fiat currency name to its currency asset(s) viaAssetService.getAssetsByNameand takes the first candidate carrying a snapshot mark — the same currency-asset routebankMarkalready uses. A currency with no snapshot mark stays fail-loud (never a silent default).needsMark) so mark-to-market revalues it once a price returns, instead of blocking the whole cutover; the count + assets are warn-logged.AssetServiceis injected as a required dependency (not@Optional()with a runtime throw).Test plan
type-check/lint/format:checkclean