commit 880298110d53f7a053c8929e4627aac2dd346c94
parent 4d22292628163e1268ad1579150722c398d13e35
Author: Florian Dold <dold@taler.net>
Date: Mon, 20 Jul 2026 13:58:59 +0200
util: report amount currency mismatch early
Diffstat:
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/packages/taler-util/src/amounts.test.ts b/packages/taler-util/src/amounts.test.ts
@@ -46,6 +46,12 @@ test("amount addition (saturation)", (t) => {
assert.ok(res.saturated);
});
+test("Amounts.add validates all currencies even when the first argument already overflows", (t) => {
+ assert.throws(() =>
+ Amounts.add(jAmt(amountMaxValue + 1, 0, "EUR"), jAmt(5, 0, "USD")),
+ );
+});
+
test("amount subtraction (simple)", (t) => {
const a1 = jAmt(2, 5, "EUR");
const a2 = jAmt(1, 0, "EUR");
diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts
@@ -343,6 +343,17 @@ export class Amounts {
static add(first: AmountLike, ...rest: AmountLike[]): AmountResult {
const firstJ = Amounts.jsonifyAmount(first);
const currency = firstJ.currency;
+
+ // Validate all currencies before any numeric accumulation/overflow
+ // check, so a mismatched currency in `rest` is always reported, even
+ // if the first argument alone would already saturate the result.
+ const restJ = rest.map((x) => Amounts.jsonifyAmount(x));
+ for (const xJ of restJ) {
+ if (xJ.currency.toUpperCase() !== currency.toUpperCase()) {
+ throw Error(`Mismatched currency: ${xJ.currency} and ${currency}`);
+ }
+ }
+
let value =
firstJ.value + Math.floor(firstJ.fraction / amountFractionalBase);
if (value > amountMaxValue) {
@@ -356,12 +367,7 @@ export class Amounts {
};
}
let fraction = firstJ.fraction % amountFractionalBase;
- for (const x of rest) {
- const xJ = Amounts.jsonifyAmount(x);
- if (xJ.currency.toUpperCase() !== currency.toUpperCase()) {
- throw Error(`Mismatched currency: ${xJ.currency} and ${currency}`);
- }
-
+ for (const xJ of restJ) {
value =
value +
xJ.value +