Selaa lähdekoodia

Add more Amount::split_with_fee tests (#1058)

David Caseria 1 kuukausi sitten
vanhempi
säilyke
f2f5425395
1 muutettua tiedostoa jossa 131 lisäystä ja 3 poistoa
  1. 131 3
      crates/cashu/src/amount.rs

+ 131 - 3
crates/cashu/src/amount.rs

@@ -132,10 +132,10 @@ impl Amount {
     /// Splits amount into powers of two while accounting for the swap fee
     pub fn split_with_fee(&self, fee_ppk: u64) -> Result<Vec<Self>, Error> {
         let without_fee_amounts = self.split();
-        let fee_ppk = fee_ppk
+        let total_fee_ppk = fee_ppk
             .checked_mul(without_fee_amounts.len() as u64)
             .ok_or(Error::AmountOverflow)?;
-        let fee = Amount::from(fee_ppk.div_ceil(1000));
+        let fee = Amount::from(total_fee_ppk.div_ceil(1000));
         let new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
 
         let split = new_amount.split();
@@ -456,7 +456,135 @@ mod tests {
         let fee_ppk = 1000;
 
         let split = amount.split_with_fee(fee_ppk).unwrap();
-        assert_eq!(split, vec![Amount(32)]);
+        // With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
+        // to cover both the amount (3) and fees (~2 for 2 proofs)
+        assert_eq!(split, vec![Amount(4), Amount(1)]);
+    }
+
+    #[test]
+    fn test_split_with_fee_reported_issue() {
+        // Test the reported issue: mint 600, send 300 with fee_ppk=100
+        let amount = Amount(300);
+        let fee_ppk = 100;
+
+        let split = amount.split_with_fee(fee_ppk).unwrap();
+
+        // Calculate the total fee for the split
+        let total_fee_ppk = (split.len() as u64) * fee_ppk;
+        let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
+
+        // The split should cover the amount plus fees
+        let split_total = Amount::try_sum(split.iter().copied()).unwrap();
+        assert!(
+            split_total >= amount + total_fee,
+            "Split total {} should be >= amount {} + fee {}",
+            split_total,
+            amount,
+            total_fee
+        );
+    }
+
+    #[test]
+    fn test_split_with_fee_edge_cases() {
+        // Test various amounts with fee_ppk=100
+        let test_cases = vec![
+            (Amount(1), 100),
+            (Amount(10), 100),
+            (Amount(50), 100),
+            (Amount(100), 100),
+            (Amount(200), 100),
+            (Amount(300), 100),
+            (Amount(500), 100),
+            (Amount(600), 100),
+            (Amount(1000), 100),
+            (Amount(1337), 100),
+            (Amount(5000), 100),
+        ];
+
+        for (amount, fee_ppk) in test_cases {
+            let result = amount.split_with_fee(fee_ppk);
+            assert!(
+                result.is_ok(),
+                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
+                amount,
+                fee_ppk,
+                result.err()
+            );
+
+            let split = result.unwrap();
+
+            // Verify the split covers the required amount
+            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
+            let fee_for_split = (split.len() as u64) * fee_ppk;
+            let total_fee = Amount::from(fee_for_split.div_ceil(1000));
+
+            // The net amount after fees should be at least the original amount
+            let net_amount = split_total.checked_sub(total_fee);
+            assert!(
+                net_amount.is_some(),
+                "Net amount calculation failed for amount {} with fee_ppk {}",
+                amount,
+                fee_ppk
+            );
+            assert!(
+                net_amount.unwrap() >= amount,
+                "Net amount {} is less than required {} for amount {} with fee_ppk {}",
+                net_amount.unwrap(),
+                amount,
+                amount,
+                fee_ppk
+            );
+        }
+    }
+
+    #[test]
+    fn test_split_with_fee_high_fees() {
+        // Test with very high fees
+        let test_cases = vec![
+            (Amount(10), 500),  // 50% fee
+            (Amount(10), 1000), // 100% fee
+            (Amount(10), 2000), // 200% fee
+            (Amount(100), 500),
+            (Amount(100), 1000),
+            (Amount(100), 2000),
+        ];
+
+        for (amount, fee_ppk) in test_cases {
+            let result = amount.split_with_fee(fee_ppk);
+            assert!(
+                result.is_ok(),
+                "split_with_fee failed for amount {} with fee_ppk {}: {:?}",
+                amount,
+                fee_ppk,
+                result.err()
+            );
+
+            let split = result.unwrap();
+            let split_total = Amount::try_sum(split.iter().copied()).unwrap();
+
+            // With high fees, we just need to ensure we can cover the amount
+            assert!(
+                split_total > amount,
+                "Split total {} should be greater than amount {} for fee_ppk {}",
+                split_total,
+                amount,
+                fee_ppk
+            );
+        }
+    }
+
+    #[test]
+    fn test_split_with_fee_recursion_limit() {
+        // Test that the recursion doesn't go infinite
+        // This tests the edge case where the method keeps adding Amount::ONE
+        let amount = Amount(1);
+        let fee_ppk = 10000; // Very high fee that might cause recursion
+
+        let result = amount.split_with_fee(fee_ppk);
+        assert!(
+            result.is_ok(),
+            "split_with_fee should handle extreme fees without infinite recursion"
+        );
     }
 
     #[test]