mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-23 05:56:02 +01:00
Include supported amounts instead of assuming the power of 2 (#1055)
* Include supported amounts instead of assuming the power of 2 The mint's signatory defines the amounts and the wallet, and the mint, when paying, should use them instead of assuming the supported amounts are 2^(0..32), which is not part of the spec. * Introduce FeeAndAmount struct
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
//! Is any unit and will be treated as the unit of the wallet
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -11,6 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::nuts::CurrencyUnit;
|
||||
use crate::Id;
|
||||
|
||||
/// Amount Error
|
||||
#[derive(Debug, Error)]
|
||||
@@ -41,6 +43,40 @@ pub enum Error {
|
||||
#[serde(transparent)]
|
||||
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 {
|
||||
type Err = Error;
|
||||
|
||||
@@ -60,31 +96,38 @@ impl Amount {
|
||||
pub const ONE: Amount = Amount(1);
|
||||
|
||||
/// 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()
|
||||
.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
|
||||
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 {
|
||||
SplitTarget::None => self.split(),
|
||||
SplitTarget::None => self.split(fee_and_amounts),
|
||||
SplitTarget::Value(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 = Vec::new();
|
||||
|
||||
// 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) {
|
||||
for part in parts_of_value.iter().copied() {
|
||||
@@ -92,7 +135,7 @@ impl Amount {
|
||||
parts.push(part);
|
||||
} else {
|
||||
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())?;
|
||||
@@ -115,7 +158,7 @@ impl Amount {
|
||||
}
|
||||
Ordering::Greater => {
|
||||
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();
|
||||
|
||||
values.append(&mut extra_amount);
|
||||
@@ -130,17 +173,18 @@ 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 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)
|
||||
.ok_or(Error::AmountOverflow)?;
|
||||
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();
|
||||
let split = new_amount.split(fee_and_amounts);
|
||||
let split_fee_ppk = (split.len() as u64)
|
||||
.checked_mul(fee_ppk)
|
||||
.checked_mul(fee_and_amounts.fee)
|
||||
.ok_or(Error::AmountOverflow)?;
|
||||
let split_fee = Amount::from(split_fee_ppk.div_ceil(1000));
|
||||
|
||||
@@ -151,7 +195,7 @@ impl Amount {
|
||||
}
|
||||
self.checked_add(Amount::ONE)
|
||||
.ok_or(Error::AmountOverflow)?
|
||||
.split_with_fee(fee_ppk)
|
||||
.split_with_fee(fee_and_amounts)
|
||||
}
|
||||
|
||||
/// Checked addition for Amount. Returns None if overflow occurs.
|
||||
@@ -192,6 +236,11 @@ impl Amount {
|
||||
) -> Result<Amount, Error> {
|
||||
to_unit(self.0, current_unit, target_unit)
|
||||
}
|
||||
///
|
||||
/// Convert to u64
|
||||
pub fn to_u64(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Convert to i64
|
||||
pub fn to_i64(self) -> Option<i64> {
|
||||
@@ -376,34 +425,43 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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!(
|
||||
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)]
|
||||
);
|
||||
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]
|
||||
.iter()
|
||||
.map(|a| Amount::from(*a))
|
||||
.collect();
|
||||
assert_eq!(Amount::from(255).split(), amounts);
|
||||
assert_eq!(Amount::from(255).split(&fee_and_amounts), amounts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 split = amount
|
||||
.split_targeted(&SplitTarget::Value(Amount(32)))
|
||||
.split_targeted(&SplitTarget::Value(Amount(32)), &fee_and_amounts)
|
||||
.unwrap();
|
||||
assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
|
||||
|
||||
let amount = Amount(150);
|
||||
|
||||
let split = amount
|
||||
.split_targeted(&SplitTarget::Value(Amount::from(50)))
|
||||
.split_targeted(&SplitTarget::Value(Amount::from(50)), &fee_and_amounts)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vec![
|
||||
@@ -423,7 +481,7 @@ mod tests {
|
||||
let amount = Amount::from(63);
|
||||
|
||||
let split = amount
|
||||
.split_targeted(&SplitTarget::Value(Amount::from(32)))
|
||||
.split_targeted(&SplitTarget::Value(Amount::from(32)), &fee_and_amounts)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vec![
|
||||
@@ -440,22 +498,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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 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)]);
|
||||
|
||||
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)]);
|
||||
|
||||
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
|
||||
// to cover both the amount (3) and fees (~2 for 2 proofs)
|
||||
assert_eq!(split, vec![Amount(4), Amount(1)]);
|
||||
@@ -463,14 +520,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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
|
||||
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
|
||||
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));
|
||||
|
||||
// The split should cover the amount plus fees
|
||||
@@ -502,7 +559,9 @@ mod tests {
|
||||
];
|
||||
|
||||
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!(
|
||||
result.is_ok(),
|
||||
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
||||
@@ -550,7 +609,9 @@ mod tests {
|
||||
];
|
||||
|
||||
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!(
|
||||
result.is_ok(),
|
||||
"split_with_fee failed for amount {} with fee_ppk {}: {:?}",
|
||||
@@ -578,9 +639,10 @@ mod tests {
|
||||
// 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 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!(
|
||||
result.is_ok(),
|
||||
"split_with_fee should handle extreme fees without infinite recursion"
|
||||
@@ -589,13 +651,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_split_values() {
|
||||
let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
||||
let amount = Amount(10);
|
||||
|
||||
let target = vec![Amount(2), Amount(4), Amount(4)];
|
||||
|
||||
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);
|
||||
|
||||
@@ -603,13 +668,15 @@ mod tests {
|
||||
|
||||
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);
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ use super::nut10;
|
||||
#[cfg(feature = "wallet")]
|
||||
use super::nut11::SpendingConditions;
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::amount::FeeAndAmounts;
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::amount::SplitTarget;
|
||||
#[cfg(feature = "wallet")]
|
||||
use crate::dhke::blind_message;
|
||||
@@ -746,8 +748,9 @@ impl PreMintSecrets {
|
||||
keyset_id: Id,
|
||||
amount: Amount,
|
||||
amount_split_target: &SplitTarget,
|
||||
fee_and_amounts: &FeeAndAmounts,
|
||||
) -> Result<Self, Error> {
|
||||
let amount_split = amount.split_targeted(amount_split_target)?;
|
||||
let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
|
||||
|
||||
let mut output = Vec::with_capacity(amount_split.len());
|
||||
|
||||
@@ -830,8 +833,9 @@ impl PreMintSecrets {
|
||||
amount: Amount,
|
||||
amount_split_target: &SplitTarget,
|
||||
conditions: &SpendingConditions,
|
||||
fee_and_amounts: &FeeAndAmounts,
|
||||
) -> Result<Self, Error> {
|
||||
let amount_split = amount.split_targeted(amount_split_target)?;
|
||||
let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
|
||||
|
||||
let mut output = Vec::with_capacity(amount_split.len());
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use tracing::instrument;
|
||||
use super::nut00::{BlindedMessage, PreMint, PreMintSecrets};
|
||||
use super::nut01::SecretKey;
|
||||
use super::nut02::Id;
|
||||
use crate::amount::SplitTarget;
|
||||
use crate::amount::{FeeAndAmounts, SplitTarget};
|
||||
use crate::dhke::blind_message;
|
||||
use crate::secret::Secret;
|
||||
use crate::util::hex;
|
||||
@@ -127,12 +127,13 @@ impl PreMintSecrets {
|
||||
seed: &[u8; 64],
|
||||
amount: Amount,
|
||||
amount_split_target: &SplitTarget,
|
||||
fee_and_amounts: &FeeAndAmounts,
|
||||
) -> Result<Self, Error> {
|
||||
let mut pre_mint_secrets = PreMintSecrets::new(keyset_id);
|
||||
|
||||
let mut counter = counter;
|
||||
|
||||
for amount in amount.split_targeted(amount_split_target)? {
|
||||
for amount in amount.split_targeted(amount_split_target, fee_and_amounts)? {
|
||||
let secret = Secret::from_seed(seed, keyset_id, counter)?;
|
||||
let blinding_factor = SecretKey::from_seed(seed, keyset_id, counter)?;
|
||||
|
||||
@@ -486,10 +487,12 @@ mod tests {
|
||||
.unwrap();
|
||||
let amount = Amount::from(1000u64);
|
||||
let split_target = SplitTarget::default();
|
||||
let fee_and_amounts = (0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into();
|
||||
|
||||
// Test PreMintSecrets generation with v2 keyset
|
||||
let pre_mint_secrets =
|
||||
PreMintSecrets::from_seed(keyset_id, 0, &seed, amount, &split_target).unwrap();
|
||||
PreMintSecrets::from_seed(keyset_id, 0, &seed, amount, &split_target, &fee_and_amounts)
|
||||
.unwrap();
|
||||
|
||||
// Verify all secrets in the pre_mint use the new v2 derivation
|
||||
for (i, pre_mint) in pre_mint_secrets.secrets.iter().enumerate() {
|
||||
|
||||
@@ -34,8 +34,7 @@ pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
|
||||
pub fn validate_kvstore_string(s: &str) -> Result<(), Error> {
|
||||
if s.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN {
|
||||
return Err(Error::KVStoreInvalidKey(format!(
|
||||
"{} exceeds maximum length of key characters",
|
||||
KVSTORE_NAMESPACE_KEY_MAX_LEN
|
||||
"{KVSTORE_NAMESPACE_KEY_MAX_LEN} exceeds maximum length of key characters"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -72,11 +71,10 @@ pub fn validate_kvstore_params(
|
||||
}
|
||||
|
||||
// Check for potential collisions between keys and namespaces in the same namespace
|
||||
let namespace_key = format!("{}/{}", primary_namespace, secondary_namespace);
|
||||
let namespace_key = format!("{primary_namespace}/{secondary_namespace}");
|
||||
if key == primary_namespace || key == secondary_namespace || key == namespace_key {
|
||||
return Err(Error::KVStoreInvalidKey(format!(
|
||||
"Key '{}' conflicts with namespace names",
|
||||
key
|
||||
"Key '{key}' conflicts with namespace names"
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -372,8 +372,11 @@ impl Wallet {
|
||||
pub async fn get_keyset_fees_by_id(&self, keyset_id: String) -> Result<u64, FfiError> {
|
||||
let id = cdk::nuts::Id::from_str(&keyset_id)
|
||||
.map_err(|e| FfiError::Generic { msg: e.to_string() })?;
|
||||
let fees = self.inner.get_keyset_fees_by_id(id).await?;
|
||||
Ok(fees)
|
||||
Ok(self
|
||||
.inner
|
||||
.get_keyset_fees_and_amounts_by_id(id)
|
||||
.await?
|
||||
.fee())
|
||||
}
|
||||
|
||||
/// Reclaim unspent proofs (mark them as unspent in the database)
|
||||
@@ -397,8 +400,8 @@ impl Wallet {
|
||||
) -> Result<Amount, FfiError> {
|
||||
let id = cdk::nuts::Id::from_str(&keyset_id)
|
||||
.map_err(|e| FfiError::Generic { msg: e.to_string() })?;
|
||||
let fee_ppk = self.inner.get_keyset_fees_by_id(id).await?;
|
||||
let total_fee = (proof_count as u64 * fee_ppk) / 1000; // fee is per thousand
|
||||
let fee_and_amounts = self.inner.get_keyset_fees_and_amounts_by_id(id).await?;
|
||||
let total_fee = (proof_count as u64 * fee_and_amounts.fee()) / 1000; // fee is per thousand
|
||||
Ok(Amount::new(total_fee))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,14 @@ async fn test_regtest_bolt12_mint_extra() -> Result<()> {
|
||||
assert_eq!(state.amount_paid, (pay_amount_msats / 1_000).into());
|
||||
assert_eq!(state.amount_issued, Amount::ZERO);
|
||||
|
||||
let pre_mint = PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None)?;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
500.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)?;
|
||||
|
||||
let quote_info = wallet
|
||||
.localstore
|
||||
|
||||
@@ -392,9 +392,15 @@ async fn test_fake_melt_change_in_quote() {
|
||||
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
|
||||
|
||||
let keyset = wallet.fetch_active_keyset().await.unwrap();
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
keyset.id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let client = HttpClient::new(MINT_URL.parse().unwrap(), None);
|
||||
|
||||
@@ -469,9 +475,15 @@ async fn test_fake_mint_without_witness() {
|
||||
let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let request = MintRequest {
|
||||
quote: mint_quote.id,
|
||||
@@ -513,9 +525,15 @@ async fn test_fake_mint_with_wrong_witness() {
|
||||
let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut request = MintRequest {
|
||||
quote: mint_quote.id,
|
||||
@@ -561,9 +579,15 @@ async fn test_fake_mint_inflated() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
500.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let quote_info = wallet
|
||||
.localstore
|
||||
@@ -623,8 +647,15 @@ async fn test_fake_mint_multiple_units() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
50.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wallet_usd = Wallet::new(
|
||||
MINT_URL,
|
||||
@@ -637,8 +668,13 @@ async fn test_fake_mint_multiple_units() {
|
||||
|
||||
let active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
|
||||
|
||||
let usd_pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None).unwrap();
|
||||
let usd_pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
50.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let quote_info = wallet
|
||||
.localstore
|
||||
@@ -727,6 +763,7 @@ async fn test_fake_mint_multiple_unit_swap() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
{
|
||||
let inputs: Proofs = vec![
|
||||
@@ -738,6 +775,7 @@ async fn test_fake_mint_multiple_unit_swap() {
|
||||
active_keyset_id,
|
||||
inputs.total_amount().unwrap(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -764,13 +802,23 @@ async fn test_fake_mint_multiple_unit_swap() {
|
||||
let inputs: Proofs = proofs.into_iter().take(2).collect();
|
||||
|
||||
let total_inputs = inputs.total_amount().unwrap();
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let half = total_inputs / 2.into();
|
||||
let usd_pre_mint =
|
||||
PreMintSecrets::random(usd_active_keyset_id, half, &SplitTarget::None).unwrap();
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, total_inputs - half, &SplitTarget::None)
|
||||
.unwrap();
|
||||
let usd_pre_mint = PreMintSecrets::random(
|
||||
usd_active_keyset_id,
|
||||
half,
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
total_inputs - half,
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut usd_outputs = usd_pre_mint.blinded_messages();
|
||||
let mut sat_outputs = pre_mint.blinded_messages();
|
||||
@@ -870,6 +918,7 @@ async fn test_fake_mint_multiple_unit_melt() {
|
||||
}
|
||||
|
||||
{
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
|
||||
|
||||
let input_amount: u64 = inputs.total_amount().unwrap().into();
|
||||
@@ -882,10 +931,16 @@ async fn test_fake_mint_multiple_unit_melt() {
|
||||
usd_active_keyset_id,
|
||||
inputs.total_amount().unwrap() + 100.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
|
||||
|
||||
let mut usd_outputs = usd_pre_mint.blinded_messages();
|
||||
let mut sat_outputs = pre_mint.blinded_messages();
|
||||
@@ -944,6 +999,7 @@ async fn test_fake_mint_input_output_mismatch() {
|
||||
)
|
||||
.expect("failed to create new usd wallet");
|
||||
let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let inputs = proofs;
|
||||
|
||||
@@ -951,6 +1007,7 @@ async fn test_fake_mint_input_output_mismatch() {
|
||||
usd_active_keyset_id,
|
||||
inputs.total_amount().unwrap(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -985,6 +1042,7 @@ async fn test_fake_mint_swap_inflated() {
|
||||
let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
|
||||
|
||||
let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let proofs = proof_streams
|
||||
.next()
|
||||
@@ -993,8 +1051,13 @@ async fn test_fake_mint_swap_inflated() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
101.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
|
||||
|
||||
@@ -1037,9 +1100,15 @@ async fn test_fake_mint_swap_spend_after_fail() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
|
||||
|
||||
@@ -1048,8 +1117,13 @@ async fn test_fake_mint_swap_spend_after_fail() {
|
||||
|
||||
assert!(response.is_ok());
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
101.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
|
||||
|
||||
@@ -1064,8 +1138,13 @@ async fn test_fake_mint_swap_spend_after_fail() {
|
||||
Ok(_) => panic!("Should not have allowed swap with unbalanced"),
|
||||
}
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
|
||||
|
||||
@@ -1108,9 +1187,15 @@ async fn test_fake_mint_melt_spend_after_fail() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
|
||||
|
||||
@@ -1119,8 +1204,13 @@ async fn test_fake_mint_melt_spend_after_fail() {
|
||||
|
||||
assert!(response.is_ok());
|
||||
|
||||
let pre_mint =
|
||||
PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None).unwrap();
|
||||
let pre_mint = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
101.into(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
|
||||
|
||||
@@ -1180,6 +1270,7 @@ async fn test_fake_mint_duplicate_proofs_swap() {
|
||||
.expect("no error");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let inputs = vec![proofs[0].clone(), proofs[0].clone()];
|
||||
|
||||
@@ -1187,6 +1278,7 @@ async fn test_fake_mint_duplicate_proofs_swap() {
|
||||
active_keyset_id,
|
||||
inputs.total_amount().unwrap(),
|
||||
&SplitTarget::None,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -376,9 +376,15 @@ async fn test_fake_melt_change_in_quote() {
|
||||
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
|
||||
|
||||
let keyset = wallet.fetch_active_keyset().await.unwrap();
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
keyset.id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
|
||||
|
||||
|
||||
@@ -243,11 +243,13 @@ async fn test_mint_double_spend() {
|
||||
|
||||
let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
|
||||
let keyset_id = keys.id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
proofs.total_amount().unwrap(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -260,6 +262,7 @@ async fn test_mint_double_spend() {
|
||||
keyset_id,
|
||||
proofs.total_amount().unwrap(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -300,14 +303,30 @@ async fn test_attempt_to_swap_by_overflowing() {
|
||||
|
||||
let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
|
||||
let keyset_id = keys.id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_mint_amount =
|
||||
PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default()).unwrap();
|
||||
let pre_mint_amount_two =
|
||||
PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default()).unwrap();
|
||||
let pre_mint_amount = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
amount.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
let pre_mint_amount_two = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
amount.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut pre_mint =
|
||||
PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default()).unwrap();
|
||||
let mut pre_mint = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
1.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
pre_mint.combine(pre_mint_amount);
|
||||
pre_mint.combine(pre_mint_amount_two);
|
||||
@@ -320,6 +339,7 @@ async fn test_attempt_to_swap_by_overflowing() {
|
||||
cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
|
||||
cdk::Error::AmountOverflow => (),
|
||||
cdk::Error::AmountError(_) => (),
|
||||
cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
||||
_ => {
|
||||
panic!("Wrong error returned in swap overflow {:?}", err);
|
||||
}
|
||||
@@ -353,9 +373,16 @@ async fn test_swap_unbalanced() {
|
||||
|
||||
let keyset_id = get_keyset_id(&mint_bob).await;
|
||||
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
// Try to swap for less than the input amount (95 < 100)
|
||||
let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())
|
||||
.expect("Failed to create preswap");
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
95.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.expect("Failed to create preswap");
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -368,8 +395,13 @@ async fn test_swap_unbalanced() {
|
||||
}
|
||||
|
||||
// Try to swap for more than the input amount (101 > 100)
|
||||
let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())
|
||||
.expect("Failed to create preswap");
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
101.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.expect("Failed to create preswap");
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -407,12 +439,14 @@ pub async fn test_p2pk_swap() {
|
||||
let secret = SecretKey::generate();
|
||||
|
||||
let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let pre_swap = PreMintSecrets::with_conditions(
|
||||
keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&spending_conditions,
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -430,7 +464,13 @@ pub async fn test_p2pk_swap() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let pre_swap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
|
||||
|
||||
@@ -536,8 +576,15 @@ async fn test_swap_overpay_underpay_fee() {
|
||||
|
||||
let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
|
||||
let keyset_id = Id::v1_from_keys(&keys);
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
9998.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -553,7 +600,13 @@ async fn test_swap_overpay_underpay_fee() {
|
||||
},
|
||||
}
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
1000.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -602,10 +655,17 @@ async fn test_mint_enforce_fee() {
|
||||
|
||||
let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
|
||||
let keyset_id = keys.id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
let five_proofs: Vec<_> = proofs.drain(..5).collect();
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
5.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -621,7 +681,13 @@ async fn test_mint_enforce_fee() {
|
||||
},
|
||||
}
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
4.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -631,7 +697,13 @@ async fn test_mint_enforce_fee() {
|
||||
|
||||
let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
1000.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -647,7 +719,13 @@ async fn test_mint_enforce_fee() {
|
||||
},
|
||||
}
|
||||
|
||||
let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default()).unwrap();
|
||||
let preswap = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
999.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
|
||||
|
||||
@@ -721,18 +799,34 @@ async fn test_concurrent_double_spend_swap() {
|
||||
.expect("Could not get proofs");
|
||||
|
||||
let keyset_id = get_keyset_id(&mint_bob).await;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
|
||||
// Create 3 identical swap requests with the same proofs
|
||||
let preswap1 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
|
||||
.expect("Failed to create preswap");
|
||||
let preswap1 = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.expect("Failed to create preswap");
|
||||
let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
|
||||
|
||||
let preswap2 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
|
||||
.expect("Failed to create preswap");
|
||||
let preswap2 = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.expect("Failed to create preswap");
|
||||
let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
|
||||
|
||||
let preswap3 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
|
||||
.expect("Failed to create preswap");
|
||||
let preswap3 = PreMintSecrets::random(
|
||||
keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.expect("Failed to create preswap");
|
||||
let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
|
||||
|
||||
// Spawn 3 concurrent tasks to process the swap requests
|
||||
|
||||
@@ -315,9 +315,15 @@ async fn test_cached_mint() {
|
||||
.expect("payment");
|
||||
|
||||
let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
|
||||
let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
|
||||
let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
100.into(),
|
||||
&SplitTarget::default().to_owned(),
|
||||
&fee_and_amounts,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut request = MintRequest {
|
||||
quote: quote.id,
|
||||
|
||||
@@ -60,6 +60,7 @@ impl TryInto<crate::signatory::SignatoryKeySet> for KeySet {
|
||||
.map(|(amount, pk)| PublicKey::from_slice(&pk).map(|pk| (amount.into(), pk)))
|
||||
.collect::<Result<BTreeMap<Amount, _>, _>>()?,
|
||||
),
|
||||
amounts: self.amounts,
|
||||
final_expiry: self.final_expiry,
|
||||
})
|
||||
}
|
||||
@@ -80,6 +81,7 @@ impl From<crate::signatory::SignatoryKeySet> for KeySet {
|
||||
.collect(),
|
||||
}),
|
||||
final_expiry: keyset.final_expiry,
|
||||
amounts: keyset.amounts,
|
||||
version: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -361,6 +363,7 @@ impl From<cdk_common::KeySetInfo> for KeySet {
|
||||
input_fee_ppk: value.input_fee_ppk,
|
||||
keys: Default::default(),
|
||||
final_expiry: value.final_expiry,
|
||||
amounts: vec![],
|
||||
version: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ message KeySet {
|
||||
Keys keys = 5;
|
||||
optional uint64 final_expiry = 6;
|
||||
uint64 version = 7;
|
||||
repeated uint64 amounts = 8;
|
||||
}
|
||||
|
||||
message Keys {
|
||||
|
||||
@@ -71,6 +71,8 @@ pub struct SignatoryKeySet {
|
||||
pub active: bool,
|
||||
/// The list of public keys
|
||||
pub keys: Keys,
|
||||
/// Amounts supported by the keyset
|
||||
pub amounts: Vec<u64>,
|
||||
/// Information about the fee per public key
|
||||
pub input_fee_ppk: u64,
|
||||
/// Final expiry of the keyset (unix timestamp in the future)
|
||||
@@ -110,7 +112,7 @@ impl From<SignatoryKeySet> for MintKeySetInfo {
|
||||
derivation_path: Default::default(),
|
||||
derivation_path_index: Default::default(),
|
||||
max_order: 0,
|
||||
amounts: vec![],
|
||||
amounts: val.amounts,
|
||||
final_expiry: val.final_expiry,
|
||||
valid_from: 0,
|
||||
}
|
||||
@@ -124,6 +126,7 @@ impl From<&(MintKeySetInfo, MintKeySet)> for SignatoryKeySet {
|
||||
unit: key.unit.clone(),
|
||||
active: info.active,
|
||||
input_fee_ppk: info.input_fee_ppk,
|
||||
amounts: info.amounts.clone(),
|
||||
keys: key.keys.clone().into(),
|
||||
final_expiry: key.final_expiry,
|
||||
}
|
||||
|
||||
@@ -930,7 +930,24 @@ impl Mint {
|
||||
|
||||
let change_target = inputs_amount - total_spent - inputs_fee;
|
||||
|
||||
let mut amounts = change_target.split();
|
||||
let fee_and_amounts = self
|
||||
.keysets
|
||||
.load()
|
||||
.iter()
|
||||
.filter_map(|keyset| {
|
||||
if keyset.active && Some(keyset.id) == outputs.first().map(|x| x.keyset_id)
|
||||
{
|
||||
Some((keyset.input_fee_ppk, keyset.amounts.clone()).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.unwrap_or_else(|| {
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into()
|
||||
});
|
||||
|
||||
let mut amounts = change_target.split(&fee_and_amounts);
|
||||
|
||||
if outputs.len().lt(&amounts.len()) {
|
||||
tracing::debug!(
|
||||
|
||||
@@ -393,10 +393,33 @@ impl AuthWallet {
|
||||
}
|
||||
}
|
||||
|
||||
let active_keyset_id = self.fetch_active_keyset().await?.id;
|
||||
let keysets = self
|
||||
.load_mint_keysets()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| (x.id, x))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let premint_secrets =
|
||||
PreMintSecrets::random(active_keyset_id, amount, &SplitTarget::Value(1.into()))?;
|
||||
let active_keyset_id = self.fetch_active_keyset().await?.id;
|
||||
let fee_and_amounts = (
|
||||
keysets
|
||||
.get(&active_keyset_id)
|
||||
.map(|x| x.input_fee_ppk)
|
||||
.unwrap_or_default(),
|
||||
self.load_keyset_keys(active_keyset_id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(amount, _)| amount.to_u64())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into();
|
||||
|
||||
let premint_secrets = PreMintSecrets::random(
|
||||
active_keyset_id,
|
||||
amount,
|
||||
&SplitTarget::Value(1.into()),
|
||||
&fee_and_amounts,
|
||||
)?;
|
||||
|
||||
let request = MintAuthRequest {
|
||||
outputs: premint_secrets.blinded_messages(),
|
||||
|
||||
@@ -222,6 +222,9 @@ impl Wallet {
|
||||
}
|
||||
|
||||
let active_keyset_id = self.fetch_active_keyset().await?.id;
|
||||
let fee_and_amounts = self
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
|
||||
let premint_secrets = match &spending_conditions {
|
||||
Some(spending_conditions) => PreMintSecrets::with_conditions(
|
||||
@@ -229,10 +232,12 @@ impl Wallet {
|
||||
amount_mintable,
|
||||
&amount_split_target,
|
||||
spending_conditions,
|
||||
&fee_and_amounts,
|
||||
)?,
|
||||
None => {
|
||||
// Calculate how many secrets we'll need
|
||||
let amount_split = amount_mintable.split_targeted(&amount_split_target)?;
|
||||
let amount_split =
|
||||
amount_mintable.split_targeted(&amount_split_target, &fee_and_amounts)?;
|
||||
let num_secrets = amount_split.len() as u32;
|
||||
|
||||
tracing::debug!(
|
||||
@@ -255,6 +260,7 @@ impl Wallet {
|
||||
&self.seed,
|
||||
amount_mintable,
|
||||
&amount_split_target,
|
||||
&fee_and_amounts,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,6 +100,9 @@ impl Wallet {
|
||||
};
|
||||
|
||||
let active_keyset_id = self.fetch_active_keyset().await?.id;
|
||||
let fee_and_amounts = self
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
|
||||
let amount = match amount {
|
||||
Some(amount) => amount,
|
||||
@@ -123,10 +126,11 @@ impl Wallet {
|
||||
amount,
|
||||
&amount_split_target,
|
||||
spending_conditions,
|
||||
&fee_and_amounts,
|
||||
)?,
|
||||
None => {
|
||||
// Calculate how many secrets we'll need without generating them
|
||||
let amount_split = amount.split_targeted(&amount_split_target)?;
|
||||
let amount_split = amount.split_targeted(&amount_split_target, &fee_and_amounts)?;
|
||||
let num_secrets = amount_split.len() as u32;
|
||||
|
||||
tracing::debug!(
|
||||
@@ -149,6 +153,7 @@ impl Wallet {
|
||||
&self.seed,
|
||||
amount,
|
||||
&amount_split_target,
|
||||
&fee_and_amounts,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cdk_common::amount::{FeeAndAmounts, KeysetFeeAndAmounts};
|
||||
use cdk_common::nut02::{KeySetInfos, KeySetInfosMethods};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -139,12 +140,12 @@ impl Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get keyset fees for mint from local database only - offline operation
|
||||
/// Get keyset fees and amounts for mint from local database only - offline operation
|
||||
///
|
||||
/// Returns a HashMap of keyset IDs to their input fee rates (per-proof-per-thousand)
|
||||
/// from cached keysets in the local database. This is an offline operation that does
|
||||
/// not contact the mint. If no keysets are found locally, returns an error.
|
||||
pub async fn get_keyset_fees(&self) -> Result<HashMap<Id, u64>, Error> {
|
||||
pub async fn get_keyset_fees_and_amounts(&self) -> Result<KeysetFeeAndAmounts, Error> {
|
||||
let keysets = self
|
||||
.localstore
|
||||
.get_mint_keysets(self.mint_url.clone())
|
||||
@@ -153,19 +154,33 @@ impl Wallet {
|
||||
|
||||
let mut fees = HashMap::new();
|
||||
for keyset in keysets {
|
||||
fees.insert(keyset.id, keyset.input_fee_ppk);
|
||||
fees.insert(
|
||||
keyset.id,
|
||||
(
|
||||
keyset.input_fee_ppk,
|
||||
self.load_keyset_keys(keyset.id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(amount, _)| amount.to_u64())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(fees)
|
||||
}
|
||||
|
||||
/// Get keyset fees for mint by keyset id from local database only - offline operation
|
||||
/// Get keyset fees and amounts for mint by keyset id from local database only - offline operation
|
||||
///
|
||||
/// Returns the input fee rate (per-proof-per-thousand) for a specific keyset ID from
|
||||
/// cached keysets in the local database. This is an offline operation that does not
|
||||
/// contact the mint. If the keyset is not found locally, returns an error.
|
||||
pub async fn get_keyset_fees_by_id(&self, keyset_id: Id) -> Result<u64, Error> {
|
||||
self.get_keyset_fees()
|
||||
pub async fn get_keyset_fees_and_amounts_by_id(
|
||||
&self,
|
||||
keyset_id: Id,
|
||||
) -> Result<FeeAndAmounts, Error> {
|
||||
self.get_keyset_fees_and_amounts()
|
||||
.await?
|
||||
.get(&keyset_id)
|
||||
.cloned()
|
||||
|
||||
@@ -341,7 +341,7 @@ impl Wallet {
|
||||
.into_iter()
|
||||
.map(|k| k.id)
|
||||
.collect();
|
||||
let keyset_fees = self.get_keyset_fees().await?;
|
||||
let keyset_fees = self.get_keyset_fees_and_amounts().await?;
|
||||
let (mut input_proofs, mut exchange) = Wallet::select_exact_proofs(
|
||||
inputs_needed_amount,
|
||||
available_proofs,
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cdk_common::amount::FeeAndAmounts;
|
||||
use cdk_common::database::{self, WalletDatabase};
|
||||
use cdk_common::subscription::Params;
|
||||
use getrandom::getrandom;
|
||||
@@ -326,34 +327,36 @@ impl Wallet {
|
||||
|
||||
/// Get amounts needed to refill proof state
|
||||
#[instrument(skip(self))]
|
||||
pub async fn amounts_needed_for_state_target(&self) -> Result<Vec<Amount>, Error> {
|
||||
pub async fn amounts_needed_for_state_target(
|
||||
&self,
|
||||
fee_and_amounts: &FeeAndAmounts,
|
||||
) -> Result<Vec<Amount>, Error> {
|
||||
let unspent_proofs = self.get_unspent_proofs().await?;
|
||||
|
||||
let amounts_count: HashMap<usize, usize> =
|
||||
let amounts_count: HashMap<u64, u64> =
|
||||
unspent_proofs
|
||||
.iter()
|
||||
.fold(HashMap::new(), |mut acc, proof| {
|
||||
let amount = proof.amount;
|
||||
let counter = acc.entry(u64::from(amount) as usize).or_insert(0);
|
||||
let counter = acc.entry(u64::from(amount)).or_insert(0);
|
||||
*counter += 1;
|
||||
acc
|
||||
});
|
||||
|
||||
let all_possible_amounts: Vec<usize> = (0..32).map(|i| 2usize.pow(i as u32)).collect();
|
||||
let needed_amounts =
|
||||
fee_and_amounts
|
||||
.amounts()
|
||||
.iter()
|
||||
.fold(Vec::new(), |mut acc, amount| {
|
||||
let count_needed = (self.target_proof_count as u64)
|
||||
.saturating_sub(*amounts_count.get(amount).unwrap_or(&0));
|
||||
|
||||
let needed_amounts = all_possible_amounts
|
||||
.iter()
|
||||
.fold(Vec::new(), |mut acc, amount| {
|
||||
let count_needed: usize = self
|
||||
.target_proof_count
|
||||
.saturating_sub(*amounts_count.get(amount).unwrap_or(&0));
|
||||
for _i in 0..count_needed {
|
||||
acc.push(Amount::from(*amount));
|
||||
}
|
||||
|
||||
for _i in 0..count_needed {
|
||||
acc.push(Amount::from(*amount as u64));
|
||||
}
|
||||
|
||||
acc
|
||||
});
|
||||
acc
|
||||
});
|
||||
Ok(needed_amounts)
|
||||
}
|
||||
|
||||
@@ -362,8 +365,11 @@ impl Wallet {
|
||||
async fn determine_split_target_values(
|
||||
&self,
|
||||
change_amount: Amount,
|
||||
fee_and_amounts: &FeeAndAmounts,
|
||||
) -> Result<SplitTarget, Error> {
|
||||
let mut amounts_needed_refill = self.amounts_needed_for_state_target().await?;
|
||||
let mut amounts_needed_refill = self
|
||||
.amounts_needed_for_state_target(fee_and_amounts)
|
||||
.await?;
|
||||
|
||||
amounts_needed_refill.sort();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use cdk_common::amount::KeysetFeeAndAmounts;
|
||||
use cdk_common::wallet::TransactionId;
|
||||
use cdk_common::Id;
|
||||
use tracing::instrument;
|
||||
@@ -188,11 +189,16 @@ impl Wallet {
|
||||
amount: Amount,
|
||||
proofs: Proofs,
|
||||
active_keyset_ids: &Vec<Id>,
|
||||
keyset_fees: &HashMap<Id, u64>,
|
||||
fees_and_keyset_amounts: &KeysetFeeAndAmounts,
|
||||
include_fees: bool,
|
||||
) -> Result<(Proofs, Option<(Proof, Amount)>), Error> {
|
||||
let mut input_proofs =
|
||||
Self::select_proofs(amount, proofs, active_keyset_ids, keyset_fees, include_fees)?;
|
||||
let mut input_proofs = Self::select_proofs(
|
||||
amount,
|
||||
proofs,
|
||||
active_keyset_ids,
|
||||
fees_and_keyset_amounts,
|
||||
include_fees,
|
||||
)?;
|
||||
let mut exchange = None;
|
||||
|
||||
// How much amounts do we have selected in our proof sets?
|
||||
@@ -211,9 +217,9 @@ impl Wallet {
|
||||
input_proofs.sort_by(|a, b| a.amount.cmp(&b.amount));
|
||||
|
||||
if let Some(proof_to_exchange) = input_proofs.pop() {
|
||||
let fee_ppk = keyset_fees
|
||||
let fee_ppk = fees_and_keyset_amounts
|
||||
.get(&proof_to_exchange.keyset_id)
|
||||
.cloned()
|
||||
.map(|fee_and_amounts| fee_and_amounts.fee())
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
@@ -239,7 +245,7 @@ impl Wallet {
|
||||
amount: Amount,
|
||||
proofs: Proofs,
|
||||
active_keyset_ids: &Vec<Id>,
|
||||
keyset_fees: &HashMap<Id, u64>,
|
||||
fees_and_keyset_amounts: &KeysetFeeAndAmounts,
|
||||
include_fees: bool,
|
||||
) -> Result<Proofs, Error> {
|
||||
tracing::debug!(
|
||||
@@ -256,9 +262,6 @@ impl Wallet {
|
||||
let mut proofs = proofs;
|
||||
proofs.sort_by(|a, b| a.cmp(b).reverse());
|
||||
|
||||
// Split the amount into optimal amounts
|
||||
let optimal_amounts = amount.split();
|
||||
|
||||
// Track selected proofs and remaining amounts (include all inactive proofs first)
|
||||
let mut selected_proofs: HashSet<Proof> = proofs
|
||||
.iter()
|
||||
@@ -295,10 +298,13 @@ impl Wallet {
|
||||
};
|
||||
|
||||
// Select proofs with the optimal amounts
|
||||
for optimal_amount in optimal_amounts {
|
||||
if !select_proof(&proofs, optimal_amount, true) {
|
||||
// Add the remaining amount to the remaining amounts because proof with the optimal amount was not found
|
||||
remaining_amounts.push(optimal_amount);
|
||||
for (_, fee_and_amounts) in fees_and_keyset_amounts.iter() {
|
||||
// Split the amount into optimal amounts
|
||||
for optimal_amount in amount.split(fee_and_amounts) {
|
||||
if !select_proof(&proofs, optimal_amount, true) {
|
||||
// Add the remaining amount to the remaining amounts because proof with the optimal amount was not found
|
||||
remaining_amounts.push(optimal_amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +317,7 @@ impl Wallet {
|
||||
proofs,
|
||||
selected_proofs.into_iter().collect(),
|
||||
active_keyset_ids,
|
||||
keyset_fees,
|
||||
fees_and_keyset_amounts,
|
||||
);
|
||||
} else {
|
||||
return Ok(selected_proofs.into_iter().collect());
|
||||
@@ -373,7 +379,7 @@ impl Wallet {
|
||||
proofs,
|
||||
selected_proofs,
|
||||
active_keyset_ids,
|
||||
keyset_fees,
|
||||
fees_and_keyset_amounts,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -429,11 +435,17 @@ impl Wallet {
|
||||
proofs: Proofs,
|
||||
mut selected_proofs: Proofs,
|
||||
active_keyset_ids: &Vec<Id>,
|
||||
keyset_fees: &HashMap<Id, u64>,
|
||||
fees_and_keyset_amounts: &KeysetFeeAndAmounts,
|
||||
) -> Result<Proofs, Error> {
|
||||
tracing::debug!("Including fees");
|
||||
let fee =
|
||||
calculate_fee(&selected_proofs.count_by_keyset(), keyset_fees).unwrap_or_default();
|
||||
let fee = calculate_fee(
|
||||
&selected_proofs.count_by_keyset(),
|
||||
&fees_and_keyset_amounts
|
||||
.iter()
|
||||
.map(|(key, values)| (*key, values.fee()))
|
||||
.collect(),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
let net_amount = selected_proofs.total_amount()? - fee;
|
||||
tracing::debug!(
|
||||
"Net amount={}, fee={}, total amount={}",
|
||||
@@ -503,17 +515,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_empty() {
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = vec![];
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(0.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
0.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(selected_proofs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_insufficient() {
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = vec![proof(1), proof(2), proof(4)];
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(8.into(), proofs, &vec![id()], &HashMap::new(), false);
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
8.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
);
|
||||
assert!(selected_proofs.is_err());
|
||||
}
|
||||
|
||||
@@ -528,8 +563,22 @@ mod tests {
|
||||
proof(32),
|
||||
proof(64),
|
||||
];
|
||||
let mut selected_proofs =
|
||||
Wallet::select_proofs(77.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
|
||||
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
|
||||
let mut selected_proofs = Wallet::select_proofs(
|
||||
77.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
selected_proofs.sort();
|
||||
assert_eq!(selected_proofs.len(), 4);
|
||||
assert_eq!(selected_proofs[0].amount, 1.into());
|
||||
@@ -540,9 +589,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_over() {
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = vec![proof(1), proof(2), proof(4), proof(8), proof(32), proof(64)];
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(31.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
31.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(selected_proofs.len(), 1);
|
||||
assert_eq!(selected_proofs[0].amount, 32.into());
|
||||
}
|
||||
@@ -550,8 +611,21 @@ mod tests {
|
||||
#[test]
|
||||
fn test_select_proofs_smaller_over() {
|
||||
let proofs = vec![proof(8), proof(16), proof(32)];
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(23.into(), proofs, &vec![id()], &HashMap::new(), false).unwrap();
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
23.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(selected_proofs.len(), 2);
|
||||
assert_eq!(selected_proofs[0].amount, 16.into());
|
||||
assert_eq!(selected_proofs[1].amount, 8.into());
|
||||
@@ -559,10 +633,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_many_ones() {
|
||||
let active_id = id();
|
||||
let mut fee_and_keyset_amounts = HashMap::new();
|
||||
fee_and_keyset_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = (0..1024).map(|_| proof(1)).collect::<Vec<_>>();
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(1024.into(), proofs, &vec![id()], &HashMap::new(), false)
|
||||
.unwrap();
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
1024.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&fee_and_keyset_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(selected_proofs.len(), 1024);
|
||||
selected_proofs
|
||||
.iter()
|
||||
@@ -571,10 +656,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_proof_change() {
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = vec![proof(64), proof(4), proof(32)];
|
||||
let (selected_proofs, exchange) =
|
||||
Wallet::select_exact_proofs(97.into(), proofs, &vec![id()], &HashMap::new(), false)
|
||||
.unwrap();
|
||||
let (selected_proofs, exchange) = Wallet::select_exact_proofs(
|
||||
97.into(),
|
||||
proofs,
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(exchange.is_some());
|
||||
let (proof_to_exchange, amount) = exchange.unwrap();
|
||||
|
||||
@@ -585,14 +681,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_select_proofs_huge_proofs() {
|
||||
let active_id = id();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(
|
||||
active_id,
|
||||
(0, (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>()).into(),
|
||||
);
|
||||
let proofs = (0..32)
|
||||
.flat_map(|i| (0..5).map(|_| proof(1 << i)).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>();
|
||||
let mut selected_proofs = Wallet::select_proofs(
|
||||
((1u64 << 32) - 1).into(),
|
||||
proofs,
|
||||
&vec![id()],
|
||||
&HashMap::new(),
|
||||
&vec![active_id],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -608,10 +710,16 @@ mod tests {
|
||||
#[test]
|
||||
fn test_select_proofs_with_fees() {
|
||||
let proofs = vec![proof(64), proof(4), proof(32)];
|
||||
let mut keyset_fees = HashMap::new();
|
||||
keyset_fees.insert(id(), 100);
|
||||
let selected_proofs =
|
||||
Wallet::select_proofs(10.into(), proofs, &vec![id()], &keyset_fees, false).unwrap();
|
||||
let mut keyset_fee_and_amounts = HashMap::new();
|
||||
keyset_fee_and_amounts.insert(id(), (100, (0..32).map(|x| 2u64.pow(x)).collect()).into());
|
||||
let selected_proofs = Wallet::select_proofs(
|
||||
10.into(),
|
||||
proofs,
|
||||
&vec![id()],
|
||||
&keyset_fee_and_amounts,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(selected_proofs.len(), 1);
|
||||
assert_eq!(selected_proofs[0].amount, 32.into());
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
// Get keyset fees from localstore
|
||||
let keyset_fees = self.get_keyset_fees().await?;
|
||||
let keyset_fees = self.get_keyset_fees_and_amounts().await?;
|
||||
|
||||
// Get available proofs matching conditions
|
||||
let mut available_proofs = self
|
||||
@@ -129,11 +129,13 @@ impl Wallet {
|
||||
force_swap: bool,
|
||||
) -> Result<PreparedSend, Error> {
|
||||
// Split amount with fee if necessary
|
||||
let active_keyset_id = self.get_active_keyset().await?.id;
|
||||
let fee_and_amounts = self
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
let (send_amounts, send_fee) = if opts.include_fee {
|
||||
let active_keyset_id = self.get_active_keyset().await?.id;
|
||||
let keyset_fee_ppk = self.get_keyset_fees_by_id(active_keyset_id).await?;
|
||||
tracing::debug!("Keyset fee per proof: {:?}", keyset_fee_ppk);
|
||||
let send_split = amount.split_with_fee(keyset_fee_ppk)?;
|
||||
tracing::debug!("Keyset fee per proof: {:?}", fee_and_amounts.fee());
|
||||
let send_split = amount.split_with_fee(&fee_and_amounts)?;
|
||||
let send_fee = self
|
||||
.get_proofs_fee_by_count(
|
||||
vec![(active_keyset_id, send_split.len() as u64)]
|
||||
@@ -143,7 +145,7 @@ impl Wallet {
|
||||
.await?;
|
||||
(send_split, send_fee)
|
||||
} else {
|
||||
let send_split = amount.split();
|
||||
let send_split = amount.split(&fee_and_amounts);
|
||||
let send_fee = Amount::ZERO;
|
||||
(send_split, send_fee)
|
||||
};
|
||||
@@ -265,7 +267,10 @@ impl PreparedSend {
|
||||
tracing::debug!("Active keyset ID: {:?}", active_keyset_id);
|
||||
|
||||
// Get keyset fees
|
||||
let keyset_fee_ppk = self.wallet.get_keyset_fees_by_id(active_keyset_id).await?;
|
||||
let keyset_fee_ppk = self
|
||||
.wallet
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
tracing::debug!("Keyset fees: {:?}", keyset_fee_ppk);
|
||||
|
||||
// Calculate total send amount
|
||||
|
||||
@@ -40,6 +40,9 @@ impl Wallet {
|
||||
let swap_response = self.client.post_swap(pre_swap.swap_request).await?;
|
||||
|
||||
let active_keyset_id = pre_swap.pre_mint_secrets.keyset_id;
|
||||
let fee_and_amounts = self
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
|
||||
let active_keys = self
|
||||
.localstore
|
||||
@@ -74,7 +77,8 @@ impl Wallet {
|
||||
|
||||
let mut proofs_to_send = Proofs::new();
|
||||
let mut proofs_to_keep = Proofs::new();
|
||||
let mut amount_split = amount.split_targeted(&amount_split_target)?;
|
||||
let mut amount_split =
|
||||
amount.split_targeted(&amount_split_target, &fee_and_amounts)?;
|
||||
|
||||
for proof in all_proofs {
|
||||
if let Some(idx) = amount_split.iter().position(|&a| a == proof.amount)
|
||||
@@ -172,7 +176,7 @@ impl Wallet {
|
||||
.map(|k| k.id)
|
||||
.collect();
|
||||
|
||||
let keyset_fees = self.get_keyset_fees().await?;
|
||||
let keyset_fees = self.get_keyset_fees_and_amounts().await?;
|
||||
let proofs = Wallet::select_proofs(
|
||||
amount,
|
||||
available_proofs,
|
||||
@@ -224,11 +228,15 @@ impl Wallet {
|
||||
.checked_sub(total_to_subtract)
|
||||
.ok_or(Error::InsufficientFunds)?;
|
||||
|
||||
let fee_and_amounts = self
|
||||
.get_keyset_fees_and_amounts_by_id(active_keyset_id)
|
||||
.await?;
|
||||
|
||||
let (send_amount, change_amount) = match include_fees {
|
||||
true => {
|
||||
let split_count = amount
|
||||
.unwrap_or(Amount::ZERO)
|
||||
.split_targeted(&SplitTarget::default())
|
||||
.split_targeted(&SplitTarget::default(), &fee_and_amounts)
|
||||
.unwrap()
|
||||
.len();
|
||||
|
||||
@@ -251,7 +259,10 @@ impl Wallet {
|
||||
// If a non None split target is passed use that
|
||||
// else use state refill
|
||||
let change_split_target = match amount_split_target {
|
||||
SplitTarget::None => self.determine_split_target_values(change_amount).await?,
|
||||
SplitTarget::None => {
|
||||
self.determine_split_target_values(change_amount, &fee_and_amounts)
|
||||
.await?
|
||||
}
|
||||
s => s,
|
||||
};
|
||||
|
||||
@@ -261,15 +272,19 @@ impl Wallet {
|
||||
let total_secrets_needed = match spending_conditions {
|
||||
Some(_) => {
|
||||
// For spending conditions, we only need to count change secrets
|
||||
change_amount.split_targeted(&change_split_target)?.len() as u32
|
||||
change_amount
|
||||
.split_targeted(&change_split_target, &fee_and_amounts)?
|
||||
.len() as u32
|
||||
}
|
||||
None => {
|
||||
// For no spending conditions, count both send and change secrets
|
||||
let send_count = send_amount
|
||||
.unwrap_or(Amount::ZERO)
|
||||
.split_targeted(&SplitTarget::default())?
|
||||
.split_targeted(&SplitTarget::default(), &fee_and_amounts)?
|
||||
.len() as u32;
|
||||
let change_count = change_amount
|
||||
.split_targeted(&change_split_target, &fee_and_amounts)?
|
||||
.len() as u32;
|
||||
let change_count = change_amount.split_targeted(&change_split_target)?.len() as u32;
|
||||
send_count + change_count
|
||||
}
|
||||
};
|
||||
@@ -302,6 +317,7 @@ impl Wallet {
|
||||
&self.seed,
|
||||
change_amount,
|
||||
&change_split_target,
|
||||
&fee_and_amounts,
|
||||
)?;
|
||||
|
||||
derived_secret_count = change_premint_secrets.len();
|
||||
@@ -312,6 +328,7 @@ impl Wallet {
|
||||
send_amount.unwrap_or(Amount::ZERO),
|
||||
&SplitTarget::default(),
|
||||
&conditions,
|
||||
&fee_and_amounts,
|
||||
)?,
|
||||
change_premint_secrets,
|
||||
)
|
||||
@@ -323,6 +340,7 @@ impl Wallet {
|
||||
&self.seed,
|
||||
send_amount.unwrap_or(Amount::ZERO),
|
||||
&SplitTarget::default(),
|
||||
&fee_and_amounts,
|
||||
)?;
|
||||
|
||||
count += premint_secrets.len() as u32;
|
||||
@@ -333,6 +351,7 @@ impl Wallet {
|
||||
&self.seed,
|
||||
change_amount,
|
||||
&change_split_target,
|
||||
&fee_and_amounts,
|
||||
)?;
|
||||
|
||||
derived_secret_count = change_premint_secrets.len() + premint_secrets.len();
|
||||
|
||||
Reference in New Issue
Block a user