|
@@ -3,6 +3,7 @@
|
|
|
//! Is any unit and will be treated as the unit of the wallet
|
|
//! Is any unit and will be treated as the unit of the wallet
|
|
|
|
|
|
|
|
use std::cmp::Ordering;
|
|
use std::cmp::Ordering;
|
|
|
|
|
+use std::collections::HashMap;
|
|
|
use std::fmt;
|
|
use std::fmt;
|
|
|
use std::str::FromStr;
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
@@ -11,6 +12,7 @@ use serde::{Deserialize, Serialize};
|
|
|
use thiserror::Error;
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
use crate::nuts::CurrencyUnit;
|
|
use crate::nuts::CurrencyUnit;
|
|
|
|
|
+use crate::Id;
|
|
|
|
|
|
|
|
/// Amount Error
|
|
/// Amount Error
|
|
|
#[derive(Debug, Error)]
|
|
#[derive(Debug, Error)]
|
|
@@ -41,6 +43,40 @@ pub enum Error {
|
|
|
#[serde(transparent)]
|
|
#[serde(transparent)]
|
|
|
pub struct Amount(u64);
|
|
pub struct Amount(u64);
|
|
|
|
|
|
|
|
|
|
+/// Fees and and amount type, it can be casted just as a reference to the inner amounts, or a single
|
|
|
|
|
+/// u64 which is the fee
|
|
|
|
|
+#[derive(Debug, Clone)]
|
|
|
|
|
+pub struct FeeAndAmounts {
|
|
|
|
|
+ fee: u64,
|
|
|
|
|
+ amounts: Vec<u64>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl From<(u64, Vec<u64>)> for FeeAndAmounts {
|
|
|
|
|
+ fn from(value: (u64, Vec<u64>)) -> Self {
|
|
|
|
|
+ Self {
|
|
|
|
|
+ fee: value.0,
|
|
|
|
|
+ amounts: value.1,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl FeeAndAmounts {
|
|
|
|
|
+ /// Fees
|
|
|
|
|
+ #[inline(always)]
|
|
|
|
|
+ pub fn fee(&self) -> u64 {
|
|
|
|
|
+ self.fee
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Amounts
|
|
|
|
|
+ #[inline(always)]
|
|
|
|
|
+ pub fn amounts(&self) -> &[u64] {
|
|
|
|
|
+ &self.amounts
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Fees and Amounts for each Keyset
|
|
|
|
|
+pub type KeysetFeeAndAmounts = HashMap<Id, FeeAndAmounts>;
|
|
|
|
|
+
|
|
|
impl FromStr for Amount {
|
|
impl FromStr for Amount {
|
|
|
type Err = Error;
|
|
type Err = Error;
|
|
|
|
|
|
|
@@ -60,31 +96,38 @@ impl Amount {
|
|
|
pub const ONE: Amount = Amount(1);
|
|
pub const ONE: Amount = Amount(1);
|
|
|
|
|
|
|
|
/// Split into parts that are powers of two
|
|
/// Split into parts that are powers of two
|
|
|
- pub fn split(&self) -> Vec<Self> {
|
|
|
|
|
- let sats = self.0;
|
|
|
|
|
- (0_u64..64)
|
|
|
|
|
|
|
+ pub fn split(&self, fee_and_amounts: &FeeAndAmounts) -> Vec<Self> {
|
|
|
|
|
+ fee_and_amounts
|
|
|
|
|
+ .amounts
|
|
|
|
|
+ .iter()
|
|
|
.rev()
|
|
.rev()
|
|
|
- .filter_map(|bit| {
|
|
|
|
|
- let part = 1 << bit;
|
|
|
|
|
- ((sats & part) == part).then_some(Self::from(part))
|
|
|
|
|
|
|
+ .fold((Vec::new(), self.0), |(mut acc, total), &amount| {
|
|
|
|
|
+ if total >= amount {
|
|
|
|
|
+ acc.push(Self::from(amount));
|
|
|
|
|
+ }
|
|
|
|
|
+ (acc, total % amount)
|
|
|
})
|
|
})
|
|
|
- .collect()
|
|
|
|
|
|
|
+ .0
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Split into parts that are powers of two by target
|
|
/// Split into parts that are powers of two by target
|
|
|
- pub fn split_targeted(&self, target: &SplitTarget) -> Result<Vec<Self>, Error> {
|
|
|
|
|
|
|
+ pub fn split_targeted(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ target: &SplitTarget,
|
|
|
|
|
+ fee_and_amounts: &FeeAndAmounts,
|
|
|
|
|
+ ) -> Result<Vec<Self>, Error> {
|
|
|
let mut parts = match target {
|
|
let mut parts = match target {
|
|
|
- SplitTarget::None => self.split(),
|
|
|
|
|
|
|
+ SplitTarget::None => self.split(fee_and_amounts),
|
|
|
SplitTarget::Value(amount) => {
|
|
SplitTarget::Value(amount) => {
|
|
|
if self.le(amount) {
|
|
if self.le(amount) {
|
|
|
- return Ok(self.split());
|
|
|
|
|
|
|
+ return Ok(self.split(fee_and_amounts));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let mut parts_total = Amount::ZERO;
|
|
let mut parts_total = Amount::ZERO;
|
|
|
let mut parts = Vec::new();
|
|
let mut parts = Vec::new();
|
|
|
|
|
|
|
|
// The powers of two that are need to create target value
|
|
// The powers of two that are need to create target value
|
|
|
- let parts_of_value = amount.split();
|
|
|
|
|
|
|
+ let parts_of_value = amount.split(fee_and_amounts);
|
|
|
|
|
|
|
|
while parts_total.lt(self) {
|
|
while parts_total.lt(self) {
|
|
|
for part in parts_of_value.iter().copied() {
|
|
for part in parts_of_value.iter().copied() {
|
|
@@ -92,7 +135,7 @@ impl Amount {
|
|
|
parts.push(part);
|
|
parts.push(part);
|
|
|
} else {
|
|
} else {
|
|
|
let amount_left = *self - parts_total;
|
|
let amount_left = *self - parts_total;
|
|
|
- parts.extend(amount_left.split());
|
|
|
|
|
|
|
+ parts.extend(amount_left.split(fee_and_amounts));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
parts_total = Amount::try_sum(parts.clone().iter().copied())?;
|
|
parts_total = Amount::try_sum(parts.clone().iter().copied())?;
|
|
@@ -115,7 +158,7 @@ impl Amount {
|
|
|
}
|
|
}
|
|
|
Ordering::Greater => {
|
|
Ordering::Greater => {
|
|
|
let extra = *self - values_total;
|
|
let extra = *self - values_total;
|
|
|
- let mut extra_amount = extra.split();
|
|
|
|
|
|
|
+ let mut extra_amount = extra.split(fee_and_amounts);
|
|
|
let mut values = values.clone();
|
|
let mut values = values.clone();
|
|
|
|
|
|
|
|
values.append(&mut extra_amount);
|
|
values.append(&mut extra_amount);
|
|
@@ -130,17 +173,18 @@ impl Amount {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Splits amount into powers of two while accounting for the swap fee
|
|
/// 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 total_fee_ppk = fee_ppk
|
|
|
|
|
|
|
+ pub fn split_with_fee(&self, fee_and_amounts: &FeeAndAmounts) -> Result<Vec<Self>, Error> {
|
|
|
|
|
+ let without_fee_amounts = self.split(fee_and_amounts);
|
|
|
|
|
+ let total_fee_ppk = fee_and_amounts
|
|
|
|
|
+ .fee
|
|
|
.checked_mul(without_fee_amounts.len() as u64)
|
|
.checked_mul(without_fee_amounts.len() as u64)
|
|
|
.ok_or(Error::AmountOverflow)?;
|
|
.ok_or(Error::AmountOverflow)?;
|
|
|
let fee = Amount::from(total_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 new_amount = self.checked_add(fee).ok_or(Error::AmountOverflow)?;
|
|
|
|
|
|
|
|
- let split = new_amount.split();
|
|
|
|
|
|
|
+ let split = new_amount.split(fee_and_amounts);
|
|
|
let split_fee_ppk = (split.len() as u64)
|
|
let split_fee_ppk = (split.len() as u64)
|
|
|
- .checked_mul(fee_ppk)
|
|
|
|
|
|
|
+ .checked_mul(fee_and_amounts.fee)
|
|
|
.ok_or(Error::AmountOverflow)?;
|
|
.ok_or(Error::AmountOverflow)?;
|
|
|
let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
|
|
let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
|
|
|
|
|
|
|
@@ -151,7 +195,7 @@ impl Amount {
|
|
|
}
|
|
}
|
|
|
self.checked_add(Amount::ONE)
|
|
self.checked_add(Amount::ONE)
|
|
|
.ok_or(Error::AmountOverflow)?
|
|
.ok_or(Error::AmountOverflow)?
|
|
|
- .split_with_fee(fee_ppk)
|
|
|
|
|
|
|
+ .split_with_fee(fee_and_amounts)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Checked addition for Amount. Returns None if overflow occurs.
|
|
/// Checked addition for Amount. Returns None if overflow occurs.
|
|
@@ -192,6 +236,11 @@ impl Amount {
|
|
|
) -> Result<Amount, Error> {
|
|
) -> Result<Amount, Error> {
|
|
|
to_unit(self.0, current_unit, target_unit)
|
|
to_unit(self.0, current_unit, target_unit)
|
|
|
}
|
|
}
|
|
|
|
|
+ ///
|
|
|
|
|
+ /// Convert to u64
|
|
|
|
|
+ pub fn to_u64(self) -> u64 {
|
|
|
|
|
+ self.0
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/// Convert to i64
|
|
/// Convert to i64
|
|
|
pub fn to_i64(self) -> Option<i64> {
|
|
pub fn to_i64(self) -> Option<i64> {
|
|
@@ -376,34 +425,43 @@ mod tests {
|
|
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
|
fn test_split_amount() {
|
|
fn test_split_amount() {
|
|
|
- assert_eq!(Amount::from(1).split(), vec![Amount::from(1)]);
|
|
|
|
|
- assert_eq!(Amount::from(2).split(), vec![Amount::from(2)]);
|
|
|
|
|
|
|
+ let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
|
|
+
|
|
|
assert_eq!(
|
|
assert_eq!(
|
|
|
- Amount::from(3).split(),
|
|
|
|
|
|
|
+ Amount::from(1).split(&fee_and_amounts),
|
|
|
|
|
+ vec![Amount::from(1)]
|
|
|
|
|
+ );
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ Amount::from(2).split(&fee_and_amounts),
|
|
|
|
|
+ vec![Amount::from(2)]
|
|
|
|
|
+ );
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ Amount::from(3).split(&fee_and_amounts),
|
|
|
vec![Amount::from(2), Amount::from(1)]
|
|
vec![Amount::from(2), Amount::from(1)]
|
|
|
);
|
|
);
|
|
|
let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
|
|
let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
|
|
|
- assert_eq!(Amount::from(11).split(), amounts);
|
|
|
|
|
|
|
+ assert_eq!(Amount::from(11).split(&fee_and_amounts), amounts);
|
|
|
let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
|
|
let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
|
|
|
.iter()
|
|
.iter()
|
|
|
.map(|a| Amount::from(*a))
|
|
.map(|a| Amount::from(*a))
|
|
|
.collect();
|
|
.collect();
|
|
|
- assert_eq!(Amount::from(255).split(), amounts);
|
|
|
|
|
|
|
+ assert_eq!(Amount::from(255).split(&fee_and_amounts), amounts);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
|
fn test_split_target_amount() {
|
|
fn test_split_target_amount() {
|
|
|
|
|
+ let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
let amount = Amount(65);
|
|
let amount = Amount(65);
|
|
|
|
|
|
|
|
let split = amount
|
|
let split = amount
|
|
|
- .split_targeted(&SplitTarget::Value(Amount(32)))
|
|
|
|
|
|
|
+ .split_targeted(&SplitTarget::Value(Amount(32)), &fee_and_amounts)
|
|
|
.unwrap();
|
|
.unwrap();
|
|
|
assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
|
|
assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
|
|
|
|
|
|
|
|
let amount = Amount(150);
|
|
let amount = Amount(150);
|
|
|
|
|
|
|
|
let split = amount
|
|
let split = amount
|
|
|
- .split_targeted(&SplitTarget::Value(Amount::from(50)))
|
|
|
|
|
|
|
+ .split_targeted(&SplitTarget::Value(Amount::from(50)), &fee_and_amounts)
|
|
|
.unwrap();
|
|
.unwrap();
|
|
|
assert_eq!(
|
|
assert_eq!(
|
|
|
vec![
|
|
vec![
|
|
@@ -423,7 +481,7 @@ mod tests {
|
|
|
let amount = Amount::from(63);
|
|
let amount = Amount::from(63);
|
|
|
|
|
|
|
|
let split = amount
|
|
let split = amount
|
|
|
- .split_targeted(&SplitTarget::Value(Amount::from(32)))
|
|
|
|
|
|
|
+ .split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
|
|
|
.unwrap();
|
|
.unwrap();
|
|
|
assert_eq!(
|
|
assert_eq!(
|
|
|
vec![
|
|
vec![
|
|
@@ -440,22 +498,21 @@ mod tests {
|
|
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
|
fn test_split_with_fee() {
|
|
fn test_split_with_fee() {
|
|
|
|
|
+ let fee_and_amounts = (1, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
let amount = Amount(2);
|
|
let amount = Amount(2);
|
|
|
- let fee_ppk = 1;
|
|
|
|
|
|
|
|
|
|
- let split = amount.split_with_fee(fee_ppk).unwrap();
|
|
|
|
|
|
|
+ let split = amount.split_with_fee(&fee_and_amounts).unwrap();
|
|
|
assert_eq!(split, vec![Amount(2), Amount(1)]);
|
|
assert_eq!(split, vec![Amount(2), Amount(1)]);
|
|
|
|
|
|
|
|
let amount = Amount(3);
|
|
let amount = Amount(3);
|
|
|
- let fee_ppk = 1;
|
|
|
|
|
|
|
|
|
|
- let split = amount.split_with_fee(fee_ppk).unwrap();
|
|
|
|
|
|
|
+ let split = amount.split_with_fee(&fee_and_amounts).unwrap();
|
|
|
assert_eq!(split, vec![Amount(4)]);
|
|
assert_eq!(split, vec![Amount(4)]);
|
|
|
|
|
|
|
|
let amount = Amount(3);
|
|
let amount = Amount(3);
|
|
|
- let fee_ppk = 1000;
|
|
|
|
|
|
|
+ let fee_and_amounts = (1000, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
|
|
|
|
|
- let split = amount.split_with_fee(fee_ppk).unwrap();
|
|
|
|
|
|
|
+ let split = amount.split_with_fee(&fee_and_amounts).unwrap();
|
|
|
// With fee_ppk=1000 (100%), amount 3 requires proofs totaling at least 5
|
|
// 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)
|
|
// to cover both the amount (3) and fees (~2 for 2 proofs)
|
|
|
assert_eq!(split, vec![Amount(4), Amount(1)]);
|
|
assert_eq!(split, vec![Amount(4), Amount(1)]);
|
|
@@ -463,14 +520,14 @@ mod tests {
|
|
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
|
fn test_split_with_fee_reported_issue() {
|
|
fn test_split_with_fee_reported_issue() {
|
|
|
|
|
+ let fee_and_amounts = (100, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
// Test the reported issue: mint 600, send 300 with fee_ppk=100
|
|
// Test the reported issue: mint 600, send 300 with fee_ppk=100
|
|
|
let amount = Amount(300);
|
|
let amount = Amount(300);
|
|
|
- let fee_ppk = 100;
|
|
|
|
|
|
|
|
|
|
- let split = amount.split_with_fee(fee_ppk).unwrap();
|
|
|
|
|
|
|
+ let split = amount.split_with_fee(&fee_and_amounts).unwrap();
|
|
|
|
|
|
|
|
// Calculate the total fee for the split
|
|
// Calculate the total fee for the split
|
|
|
- let total_fee_ppk = (split.len() as u64) * fee_ppk;
|
|
|
|
|
|
|
+ let total_fee_ppk = (split.len() as u64) * fee_and_amounts.fee;
|
|
|
let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
|
|
let total_fee = Amount::from(total_fee_ppk.div_ceil(1000));
|
|
|
|
|
|
|
|
// The split should cover the amount plus fees
|
|
// The split should cover the amount plus fees
|
|
@@ -502,7 +559,9 @@ mod tests {
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
for (amount, fee_ppk) in test_cases {
|
|
for (amount, fee_ppk) in test_cases {
|
|
|
- let result = amount.split_with_fee(fee_ppk);
|
|
|
|
|
|
|
+ let fee_and_amounts =
|
|
|
|
|
+ (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
|
|
+ let result = amount.split_with_fee(&fee_and_amounts);
|
|
|
assert!(
|
|
assert!(
|
|
|
result.is_ok(),
|
|
result.is_ok(),
|
|
|
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
|
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
|
@@ -550,7 +609,9 @@ mod tests {
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
for (amount, fee_ppk) in test_cases {
|
|
for (amount, fee_ppk) in test_cases {
|
|
|
- let result = amount.split_with_fee(fee_ppk);
|
|
|
|
|
|
|
+ let fee_and_amounts =
|
|
|
|
|
+ (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
|
|
+ let result = amount.split_with_fee(&fee_and_amounts);
|
|
|
assert!(
|
|
assert!(
|
|
|
result.is_ok(),
|
|
result.is_ok(),
|
|
|
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
|
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
|
@@ -578,9 +639,10 @@ mod tests {
|
|
|
// Test that the recursion doesn't go infinite
|
|
// Test that the recursion doesn't go infinite
|
|
|
// This tests the edge case where the method keeps adding Amount::ONE
|
|
// This tests the edge case where the method keeps adding Amount::ONE
|
|
|
let amount = Amount(1);
|
|
let amount = Amount(1);
|
|
|
- let fee_ppk = 10000; // Very high fee that might cause recursion
|
|
|
|
|
|
|
+ let fee_ppk = 10000;
|
|
|
|
|
+ let fee_and_amounts = (fee_ppk, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
|
|
|
|
|
- let result = amount.split_with_fee(fee_ppk);
|
|
|
|
|
|
|
+ let result = amount.split_with_fee(&fee_and_amounts);
|
|
|
assert!(
|
|
assert!(
|
|
|
result.is_ok(),
|
|
result.is_ok(),
|
|
|
"split_with_fee should handle extreme fees without infinite recursion"
|
|
"split_with_fee should handle extreme fees without infinite recursion"
|
|
@@ -589,13 +651,16 @@ mod tests {
|
|
|
|
|
|
|
|
#[test]
|
|
#[test]
|
|
|
fn test_split_values() {
|
|
fn test_split_values() {
|
|
|
|
|
+ let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
|
|
let amount = Amount(10);
|
|
let amount = Amount(10);
|
|
|
|
|
|
|
|
let target = vec![Amount(2), Amount(4), Amount(4)];
|
|
let target = vec![Amount(2), Amount(4), Amount(4)];
|
|
|
|
|
|
|
|
let split_target = SplitTarget::Values(target.clone());
|
|
let split_target = SplitTarget::Values(target.clone());
|
|
|
|
|
|
|
|
- let values = amount.split_targeted(&split_target).unwrap();
|
|
|
|
|
|
|
+ let values = amount
|
|
|
|
|
+ .split_targeted(&split_target, &fee_and_amounts)
|
|
|
|
|
+ .unwrap();
|
|
|
|
|
|
|
|
assert_eq!(target, values);
|
|
assert_eq!(target, values);
|
|
|
|
|
|
|
@@ -603,13 +668,15 @@ mod tests {
|
|
|
|
|
|
|
|
let split_target = SplitTarget::Values(vec![Amount(2), Amount(4)]);
|
|
let split_target = SplitTarget::Values(vec![Amount(2), Amount(4)]);
|
|
|
|
|
|
|
|
- let values = amount.split_targeted(&split_target).unwrap();
|
|
|
|
|
|
|
+ let values = amount
|
|
|
|
|
+ .split_targeted(&split_target, &fee_and_amounts)
|
|
|
|
|
+ .unwrap();
|
|
|
|
|
|
|
|
assert_eq!(target, values);
|
|
assert_eq!(target, values);
|
|
|
|
|
|
|
|
let split_target = SplitTarget::Values(vec![Amount(2), Amount(10)]);
|
|
let split_target = SplitTarget::Values(vec![Amount(2), Amount(10)]);
|
|
|
|
|
|
|
|
- let values = amount.split_targeted(&split_target);
|
|
|
|
|
|
|
+ let values = amount.split_targeted(&split_target, &fee_and_amounts);
|
|
|
|
|
|
|
|
assert!(values.is_err())
|
|
assert!(values.is_err())
|
|
|
}
|
|
}
|