Seks pengefejl i udgiftsdeling på tværs af valutaer
En revision den 2026-08-21 fandt seks valutafejl, som vores tests havde været grønne henover, og årsagen var én sætning i et dokument, der stille var holdt op med at være sand.
Én forældet sætning i et dokument kostede seks pengefejl. En revision af Dimesum på tværs af valutaer den 2026-08-21 fandt dem i kode, som vores testsuiter havde været grønne henover, herunder en kravside der viste en gæld på ¥6,000 som -₹60.00. Sætningen lød "grupper er INR-fastlåste". Sand indtil W7, falsk i det øjeblik den blev udrullet, og stadig placeret i FOLLOWUPS.md en uge senere.
En forældet sætning er farligere end ingen dokumentation. En senere revision tror på den og springer de stier over, den dækker, så en forkert linje gør skade, som tavshed aldrig kunne. Et tomt dokument sender dig til kilden. Et forkert sender dig et helt andet sted hen.
En opfølgning der er gået i opfyldelse er værre end en åben
Dimesum parkerer udskudt arbejde i FOLLOWUPS.md, én række per beslutning med grunden til, at den blev parkeret. Punkt A17 sagde, at grupper var INR-fastlåste, at en rejse til udlandet ikke kunne registreres i den valuta, den fandt sted i, og at rettelsen ventede på en beslutning fra grundlæggeren. W7 udrullede alligevel understøttelse af flere valutaer: en udgift kan være i en hvilken som helst valuta, og balancer nøgles på (group_id, member_id, currency) af migreringen ledger/00005. Ingen strøg rækken.
Den næste revision læste A17, konkluderede at området ikke var bygget, og åbnede aldrig koden til afregning, krav eller landingssider. Tre valutablinde pengestier blev ved med at blive udrullet i kraft af én sætning. Ingen svær opgave stod i vejen. Docstringen i app/amount.py bar den samme påstand, så en læser fik det at vide to gange.
Stryg en opfølgning i den samme commit, der lukker den. En række, der beskriver kode, som holdt op med at virke sådan, er en omdirigering væk fra den fil, du havde brug for at læse, hvilket koster mere end at sige ingenting.
Den samme hardcodede 100 dukkede op i tre forskellige filer
Penge er her int64 minor units plus en ISO 4217-kode, og flydende tal rører dem aldrig. Heltal er eksakte af natur, så den eneste risikable aritmetik, der er tilbage, er omregningen mellem major og minor units. Minor unit er ikke altid en hundrededel. JPY har slet ingen, KWD har tre decimaler, og hver hardcodet 100 er et væddemål om, at brugeren blev hjemme.
Python-sidecarens app/amount.py rummede den første observation, fjernet før revisionen som en landmine snarere end en aktiv fejl. Den gangede hvilket som helst tal, den læste ud af en sætning, med 100, så en kvittering på ¥1,200 blev til 120,000 minor units. Det læses som ¥120,000. På en valuta uden minor unit puster konstanten nogens regning op hundrede gange, og funktionen slår nu eksponenten op for den forespurgte valuta i stedet.
Revisionen fandt den samme konstant på den side, hvor en person beslutter, om en balance skal accepteres. formatMinor, bag kravlandingssiden på /m/{token}, dividerede med 100 og klistrede et rupee-tegn foran. En gæld på ¥6,000 blev vist som -₹60.00: forkert symbol, en hundrededel af beløbet. Dens JSON-søskende /v1/claims fejlede ved siden af den og fladede et medlems rækker per valuta ud til én og beholdt den valuta, som map'et skrev sidst.
Den tredje fil er internal/ingestion/store.go, og W8 fandt den, mens den udvidede dubletregistrering, ikke revisionen. Dens tolerance på "plus eller minus én rupee" var konstanten 100, én rupee talt i paise, et bånd på plus eller minus ¥100, hvor JPY slet ingen minor unit har. Alle tre slår nu eksponenten op.
At sammenligne rene heltal fik en yen til at ligne en rupee
Dimesums dedupe-lag tjekker en ny registrering mod nylige udgifter, så en importeret kvittering ikke kan dobbeltbelaste en ordre, som nogen har tastet ind i hånden. Forespørgslen ved siden af det tolerancebånd havde slet intet valutafilter. Så ¥1,000 matchede ₹1,000, og en import tilbød at erstatte en udgift, den intet havde med at gøre. Grupper havde understøttet flere valutaer siden ledger/00005, så tilfældet var opnåeligt snarere end teoretisk.
Rene minor units kan ikke sammenlignes på tværs af valutaer, og det kan tolerancer heller ikke. Projektionen filtrerer nu på valuta og udleder sit bånd fra én major unit af den valuta, der sammenlignes.
To motorer besvarede "hvem betaler hvem", og den anden var valutablind
Den dyre fejl er strukturel, ikke aritmetisk. Skrivestien for afregning planlagde over internal/platform/simplify, en anden min-cash-flow-motor, hvis Balance-struct intet valutafelt havde. Nulsum per valuta gør også den flade sum nul, så en gæld på ¥300,000 og en gæld på ₹500 udlignede hinanden til ingenting, og ingen kontrol afviste planen. Planen parrede så en kreditor i yen med en debitor i rupee.
Skrivningen gjorde det værre. Tjenesten stemplede Currency: g.DefaultCurrency på hver afregning, så en gæld på ¥300,000 godkendte en INR-postering, og gælden i yen kunne slet ikke afvikles.
simplify er slettet, og /settle-plan er udfaset. internal/platform/settle er den eneste motor nu: den opdeler valutaer i konvertible og ikke-konvertible, omregner nettobalancer én gang og dirigerer hver ikke-konvertibel valuta i dens egen møntfod. En afregning angiver den valuta, den afvikler, og det felt er påkrævet, så snart en gruppe rummer mere end én.
En grænse på nul blev læst som ingen grænse
Overbetalingskontrollen afviser en betaling, der er større end den gæld, den afvikler. Kontrollen læste outstanding > 0 && amount > outstanding, så en grænse på nul sprang sammenligningen helt over. En person, der registrerer en betaling mod en gæld, der ikke findes, er netop det tilfælde, reglen findes for, og det var det tilfælde, der gled igennem.
Rettelsen er en type, ikke en betingelse. OutstandingMinor er nu en *int64, så "ingen har beregnet dette" ikke kan staves på samme måde som "svaret er nul". Nil springer tjekket over og betyder reelt ukendt; enhver kalder med adgang til hovedbogen sender et rigtigt tal.
Hvorfor en grøn testsuite intet beviste
Alle fixtures i disse stier brugte INR. En valutablind sammenligning er usynlig under en test med én valuta, for med kun én valuta er der intet at forveksle. Testsuiterne var ikke svage, de var snævre, og den revision, der ville have gjort dem bredere, var blevet sendt væk af A17.
Den end-to-end hovedbogsverificering delte blindheden, hvilket er den del, der er værd at bemærke. Verificeringen lagde posteringer sammen per medlem uden at gruppere efter valuta, så den råbte ulven kommer over en sund gruppe med to valutaer og summede til nul på tværs af en gruppe, der var i stykker to gange. Et falsk negativ er den farlige retning. En kontrol bygget på den samme antagelse som koden vil altid være enig med koden.
| Fejl | Hvor | Hvad en JPY-gruppe fik | Rettelse |
|---|---|---|---|
En Balance-struct uden valuta på | platform/simplify | en kreditor i yen parret med en debitor i rupee | simplify slettet; settle opdeler efter valuta |
| Gruppens standard stemplet på hver afregning | afregningstjenesten | en gæld på ¥300,000 godkendte en INR-postering | en afregning angiver den valuta, den afvikler |
outstanding > 0 i overbetalingskontrollen | afregningstjenesten | en grænse på nul blev til slet ingen grænse | *int64: nil betyder ukendt, nul betyder nul |
| Dividér med 100 med et rupee-tegn | gateway formatMinor | en gæld på ¥6,000 vist som -₹60.00 | symbol og decimaler fra valutaen |
| Balancer per valuta fladet ud til én række | /v1/claims | den valuta, som map'et skrev sidst | én post per valuta, svarende til GET /balances |
| Posteringer summeret per medlem, valuta droppet | end-to-end hovedbogsverificering | en dobbelt-ødelagt gruppe rapporteret som balanceret | gruppér efter medlem og valuta |
Grep din pengekode for det bogstavelige 100
Søg den igennem for den konstant og for hver sammenligning, der stiller to beløb side om side uden en valuta ved siden af dem. Ret så det billigere, det der forhindrer de næste seks: stryg en opfølgning i den samme commit, der lukker den. Og slet en dubleret motor i stedet for at reparere den. To svar på "hvem betaler hvem" er sådan et af dem forbliver forkert.
Ofte stillede spørgsmål
Hvad er udgiftsdeling på tværs af valutaer?
Udgiftsdeling på tværs af valutaer registrerer hver udgift i den valuta, den fandt sted i, og holder en separat balance per valuta i stedet for at omregne alt til én. Dimesum nøgler balancer på gruppe, medlem og valuta, så en gæld i yen og en gæld i rupee aldrig smelter sammen til ét tal. Omregning er en visning brugt til en afregningsplan, aldrig et gemt beløb.
Hvorfor er en hardcodet 100 farlig i pengekode?
En hardcodet 100 antager, at hver valuta har to decimaler, og flere har ikke. JPY har ingen minor unit, så en multiplikation med 100 gør en kvittering på ¥1,200 til 120,000 minor units, en hundredfold oppustning snarere end en afrundingsfejl. KWD har tre decimaler, så den samme konstant er ti gange forkert. Læs ISO 4217-eksponenten i stedet.
Hvordan forhindrer man to afregningsmotorer i at være uenige?
Slet den ene af de to motorer i stedet for at afstemme dem, for et andet svar på hvem der betaler hvem er sådan det første forbliver forkert. Dimesum kørte simplify og settle side om side, indtil en revision fandt, at den første ingen valuta havde på sin balancetype, hvilket lod en plan parre en kreditor i yen med en debitor i rupee. simplify blev fjernet og /settle-plan udfaset i stedet for lappet.
Hvorfor forblev testsuiten grøn gennem seks pengefejl?
Testsuiten forblev grøn, fordi hver fixture i de berørte stier brugte én valuta, og en valutablind sammenligning kan ikke fejle, når der kun er én valuta. Den end-to-end hovedbogsverificering holdt den samme antagelse: den summede posteringer per medlem uden at gruppere efter valuta, så den rapporterede en dobbelt-ødelagt gruppe som balanceret. En kontrol bygget på kodens egen antagelse er enig med koden.
Hvad skal man gøre med en opfølgning, når funktionen er udrullet?
Stryg en opfølgning i den samme commit, der lukker det arbejde, den beskriver. En opfølgning, der stille er gået i opfyldelse, er værre end en åben, for den peger en senere læser væk fra den kode, den plejede at beskrive. Dimesum efterlod en post om, at grupper var INR-fastlåste en uge efter, at flere valutaer var udrullet, og den næste revision sprang tre pengestier over på dens ord.
Populære indlæg
- Append-only-hovedbog for præcise delte balancer8 min læsning
- Derfor skal en redigering angive fordelingen igen8 min læsning
- Gør op: ryd fælles udgifter med færre overførsler5 min læsning
- Del regningen når én ret ikke blev delt8 min læsning
- Del huslejen retfærdigt med dine bofæller5 min læsning