Add more Amount::split_with_fee tests (#1058)

This commit is contained in:
David Caseria
2025-09-10 09:54:44 -04:00
committed by GitHub
parent c7f6af0749
commit f2f5425395

View File

@@ -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]