fix(campaigns): stop silent raisedAmount overwrite in getContractBalance (closes #1)#41
Open
GWorld57 wants to merge 1 commit into
Open
Conversation
…nce (closes OrbitChainLabs#1) - getContractBalance is now READ-ONLY; no longer mutates Campaign.raisedAmount - Per-asset breakdown exposed via `perAsset`; multi-asset balances are never folded into a single mixed-denomination aggregate - Canonical `netAvailableByAssetTotal` sums only native (XLM) entries so it is safe to compare against the XLM-denominated `Campaign.raisedAmount` - APPROVED/RELEASED FundRelease amounts are netted into the XLM bucket via a new `sumApprovedReleasedAmount` helper (single XLM bucket today; per-asset netting requires a future migration to add assetCode/assetIssuer to FundRelease) - New admin-only endpoint POST /admin/campaigns/:id/reconcile-balance with body { force: boolean, reason?: string }; only mutates raisedAmount when force === true AND discrepancyDetected; always writes an AuditLog row with kind=BALANCE_RECONCILED, mode=WRITE|DRY_RUN, adminEmail, contractId, and the projections - Module wiring: AdminModule now imports CampaignsModule - 14 unit tests cover NotFound, BadRequest, XLM-only no-discrepancy, multi-asset no-mixed-denom, post-release netted, drained-account, and silent-write refusal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves OrbitChain-API critical issue #1:
CampaignsService.getContractBalancewas aggregating native XLM balances and issued-asset balances into a single numeric sum and silently overwritingCampaign.raisedAmountwhenever a discrepancy was detected — with no audit log, no admin gate, and no notification.Why this is critical
Campaign.raisedAmount→ progress is wrong for any multi-asset campaign.raisedAmount→ dashboards report ghost totals.What this PR changes
getContractBalanceis now read-only with respect toCampaign.raisedAmount. The silentprisma.campaign.updateis gone.perAsset[]). It then computesnetAvailableByAssetTotalby summing only native (XLM) entries so the figure is safe to compare against the XLM-denominatedCampaign.raisedAmount. Non-XLM balances appear inperAssetbut are never folded into the canonical total (the fix to the mixed-denomination sum).FundRelease.amountvalues are aggregated and added back to the XLM net (released funds have already left the contract account).POST /admin/campaigns/:id/reconcile-balanceaccepts{ force: boolean, reason?: string }. It only mutatesCampaign.raisedAmountwhenforce === true && discrepancyDetected, and always writes anAuditLogrow withaction = ADMIN_ACTION,resourceType = campaign_balance_reconciliation,details.kind = BALANCE_RECONCILED,details.mode = WRITE|DRY_RUN, plusadminEmail,contractId, stored/new figures, andwrittenAmount.AdminModuleimportsCampaignsModuleso the new service wiring resolves cleanly.Caveats documented in code
FundReleasePrisma model lacksassetCode/assetIssuercolumns. Per-asset release netting is conservatively treated as a single XLM bucket today. A follow-up migration adding those columns would unlock true per-asset netting without changing this endpoint contract.Tests
src/campaigns/campaigns.service.spec.ts:contractIdis null50)prisma.campaign.updateis never callednpx tsc --noEmitis clean.npx jest src/campaigns/campaigns.service.spec.ts→ 14 / 14 passing.Closes #1