feat: try_sum for amounts

This commit is contained in:
thesimplekid
2024-09-05 17:33:09 +01:00
parent 5dca9af70f
commit 1f81b24f40
15 changed files with 114 additions and 95 deletions

View File

@@ -51,13 +51,13 @@ impl JsSwapRequest {
/// Proofs Amount
#[wasm_bindgen(js_name = proofsAmount)]
pub fn proofs_amount(&self) -> JsAmount {
self.inner.input_amount().into()
self.inner.input_amount().expect("Amount overflow").into()
}
/// Output Amount
#[wasm_bindgen(js_name = outputAmount)]
pub fn output_amount(&self) -> JsAmount {
self.inner.output_amount().into()
self.inner.output_amount().expect("Amount overflow").into()
}
}
@@ -99,6 +99,9 @@ impl JsSwapResponse {
/// Promises Amount
#[wasm_bindgen(js_name = promisesAmount)]
pub fn promises_amount(&self) -> JsAmount {
self.inner.promises_amount().into()
self.inner
.promises_amount()
.expect("Amount overflow")
.into()
}
}

View File

@@ -102,7 +102,7 @@ impl JsMintBolt11Request {
#[wasm_bindgen(js_name = totalAmount)]
pub fn total_amount(&self) -> JsAmount {
self.inner.total_amount().into()
self.inner.total_amount().expect("Amount overflow").into()
}
}

View File

@@ -223,7 +223,10 @@ pub async fn post_melt_bolt11(
}
};
let inputs_amount_quote_unit = payload.proofs_amount();
let inputs_amount_quote_unit = payload.proofs_amount().map_err(|_| {
tracing::error!("Proof inputs in melt quote overflowed");
into_response(Error::AmountOverflow)
})?;
let (preimage, amount_spent_quote_unit) = match mint_quote {
Some(mint_quote) => {

View File

@@ -37,8 +37,7 @@ async fn attempt_to_swap_by_overflowing() -> Result<()> {
sleep(Duration::from_secs(2)).await;
}
let premint_secrets =
PreMintSecrets::random(keyset_id.clone(), 1.into(), &SplitTarget::default())?;
let premint_secrets = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
let mint_response = wallet_client
.post_mint(
@@ -60,12 +59,11 @@ async fn attempt_to_swap_by_overflowing() -> Result<()> {
let amount = 2_u64.pow(63);
let pre_mint_amount =
PreMintSecrets::random(keyset_id.clone(), amount.into(), &SplitTarget::default())?;
PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
let pre_mint_amount_two =
PreMintSecrets::random(keyset_id.clone(), amount.into(), &SplitTarget::default())?;
PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
let mut pre_mint =
PreMintSecrets::random(keyset_id.clone(), 1.into(), &SplitTarget::default())?;
let mut pre_mint = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
pre_mint.combine(pre_mint_amount);
pre_mint.combine(pre_mint_amount_two);
@@ -91,11 +89,11 @@ async fn attempt_to_swap_by_overflowing() -> Result<()> {
println!(
"Pre swap amount: {:?}",
pre_swap_proofs.iter().map(|p| p.amount).sum::<Amount>()
Amount::try_sum(pre_swap_proofs.iter().map(|p| p.amount)).expect("Amount overflowed")
);
println!(
"Post swap amount: {:?}",
post_swap_proofs.iter().map(|p| p.amount).sum::<Amount>()
Amount::try_sum(post_swap_proofs.iter().map(|p| p.amount)).expect("Amount Overflowed")
);
println!(

View File

@@ -54,7 +54,7 @@ impl Amount {
parts.extend(amount_left.split());
}
parts_total = parts.clone().iter().copied().sum::<Amount>();
parts_total = Amount::try_sum(parts.clone().iter().copied())?;
if parts_total.eq(self) {
break;
@@ -65,7 +65,7 @@ impl Amount {
parts
}
SplitTarget::Values(values) => {
let values_total: Amount = values.clone().into_iter().sum();
let values_total: Amount = Amount::try_sum(values.clone().into_iter())?;
match self.cmp(&values_total) {
Ordering::Equal => values.clone(),
@@ -190,15 +190,6 @@ impl std::ops::Div for Amount {
}
}
impl core::iter::Sum for Amount {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Amount::ZERO, |acc, x| {
acc.checked_add(x)
.unwrap_or_else(|| panic!("Addition overflow"))
})
}
}
/// Kinds of targeting that are supported
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum SplitTarget {
@@ -314,7 +305,7 @@ mod tests {
let amounts = vec![amount_one, amount_two];
let _total: Amount = amounts.into_iter().sum();
let _total: Amount = Amount::try_sum(amounts).unwrap();
}
#[test]

View File

@@ -20,6 +20,9 @@ pub enum Error {
/// Amount is not what is expected
#[error("Amount")]
Amount,
/// Amount overflow
#[error("Amount Overflow")]
AmountOverflow,
/// Not engough inputs provided
#[error("Inputs: `{0}`, Outputs: `{0}`, Fee: `{0}`")]
InsufficientInputs(u64, u64, u64),
@@ -89,6 +92,12 @@ pub enum Error {
/// NUT00 Error
#[error(transparent)]
NUT00(#[from] crate::nuts::nut00::Error),
/// NUT04 Error
#[error(transparent)]
NUT04(#[from] crate::nuts::nut04::Error),
/// NUT05 Error
#[error(transparent)]
NUT05(#[from] crate::nuts::nut05::Error),
/// NUT11 Error
#[error(transparent)]
NUT11(#[from] crate::nuts::nut11::Error),

View File

@@ -705,13 +705,13 @@ impl Mint {
return Err(Error::BlindedMessageAlreadySigned);
}
let proofs_total = swap_request.input_amount();
let proofs_total = swap_request.input_amount()?;
let output_total = swap_request.output_amount();
let output_total = swap_request.output_amount()?;
let fee = self.get_proofs_fee(&swap_request.inputs).await?;
if proofs_total < output_total + fee {
if proofs_total < output_total.checked_add(fee).ok_or(Error::AmountOverflow)? {
tracing::info!(
"Swap request without enough inputs: {}, outputs {}, fee {}",
proofs_total,
@@ -989,7 +989,7 @@ impl Mint {
.await?
.ok_or(Error::UnknownQuote)?;
let proofs_total = melt_request.proofs_amount();
let proofs_total = melt_request.proofs_amount()?;
let fee = self.get_proofs_fee(&melt_request.inputs).await?;
@@ -1121,7 +1121,7 @@ impl Mint {
let mut change = None;
// Check if there is change to return
if melt_request.proofs_amount() > total_spent {
if melt_request.proofs_amount()? > total_spent {
// Check if wallet provided change outputs
if let Some(outputs) = melt_request.outputs.clone() {
let blinded_messages: Vec<PublicKey> =
@@ -1141,7 +1141,7 @@ impl Mint {
return Err(Error::BlindedMessageAlreadySigned);
}
let change_target = melt_request.proofs_amount() - total_spent;
let change_target = melt_request.proofs_amount()? - total_spent;
let mut amounts = change_target.split();
let mut change_sigs = Vec::with_capacity(amounts.len());
@@ -1271,7 +1271,7 @@ impl Mint {
.get_blind_signatures_for_keyset(&keyset.id)
.await?;
let total = blinded.iter().map(|b| b.amount).sum();
let total = Amount::try_sum(blinded.iter().map(|b| b.amount))?;
total_issued.insert(keyset.id, total);
}
@@ -1289,14 +1289,13 @@ impl Mint {
for keyset in keysets {
let (proofs, state) = self.localstore.get_proofs_by_keyset_id(&keyset.id).await?;
let total_spent = proofs
.iter()
.zip(state)
.filter_map(|(p, s)| match s == Some(State::Spent) {
let total_spent =
Amount::try_sum(proofs.iter().zip(state).filter_map(|(p, s)| {
match s == Some(State::Spent) {
true => Some(p.amount),
false => None,
})
.sum();
}
}))?;
total_redeemed.insert(keyset.id, total_spent);
}

View File

@@ -609,11 +609,10 @@ impl PreMintSecrets {
}
/// Totoal amount of secrets
pub fn total_amount(&self) -> Amount {
self.secrets
.iter()
.map(|PreMint { amount, .. }| *amount)
.sum()
pub fn total_amount(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.secrets.iter().map(|PreMint { amount, .. }| *amount),
)?)
}
/// [`BlindedMessage`]s from [`PreMintSecrets`]

View File

@@ -74,7 +74,7 @@ impl Token {
}
/// Total value of [`Token`]
pub fn value(&self) -> Amount {
pub fn value(&self) -> Result<Amount, Error> {
match self {
Self::TokenV3(token) => token.value(),
Self::TokenV4(token) => token.value(),
@@ -207,11 +207,13 @@ impl TokenV3 {
}
#[inline]
fn value(&self) -> Amount {
fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.token
.iter()
.map(|t| t.proofs.iter().map(|p| p.amount).sum())
.sum()
.map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount)))
.collect::<Result<Vec<Amount>, _>>()?,
)?)
}
#[inline]
@@ -305,11 +307,13 @@ impl TokenV4 {
}
#[inline]
fn value(&self) -> Amount {
fn value(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.token
.iter()
.map(|t| t.proofs.iter().map(|p| p.amount).sum())
.sum()
.map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount)))
.collect::<Result<Vec<Amount>, _>>()?,
)?)
}
#[inline]
@@ -464,7 +468,7 @@ mod tests {
let token_str_multi_keysets = "cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA==";
let token = Token::from_str(token_str_multi_keysets).unwrap();
let amount = token.value();
let amount = token.value()?;
assert_eq!(amount, Amount::from(4));

View File

@@ -5,7 +5,7 @@
use serde::{Deserialize, Serialize};
use super::nut00::{BlindSignature, BlindedMessage, PreMintSecrets, Proofs};
use crate::Amount;
use crate::{error::Error, Amount};
/// Preswap information
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -36,13 +36,13 @@ impl SwapRequest {
}
/// Total value of proofs in [`SwapRequest`]
pub fn input_amount(&self) -> Amount {
self.inputs.iter().map(|proof| proof.amount).sum()
pub fn input_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(self.inputs.iter().map(|proof| proof.amount))
}
/// Total value of outputs in [`SwapRequest`]
pub fn output_amount(&self) -> Amount {
self.outputs.iter().map(|proof| proof.amount).sum()
pub fn output_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(self.outputs.iter().map(|proof| proof.amount))
}
}
@@ -62,10 +62,11 @@ impl SwapResponse {
}
/// Total [`Amount`] of promises
pub fn promises_amount(&self) -> Amount {
pub fn promises_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(
self.signatures
.iter()
.map(|BlindSignature { amount, .. }| *amount)
.sum()
.map(|BlindSignature { amount, .. }| *amount),
)
}
}

View File

@@ -19,6 +19,9 @@ pub enum Error {
/// Unknown Quote State
#[error("Unknown Quote State")]
UnknownState,
/// Amount overflow
#[error("Amount overflow")]
AmountOverflow,
}
/// Mint quote request [NUT-04]
@@ -179,11 +182,13 @@ pub struct MintBolt11Request {
impl MintBolt11Request {
/// Total [`Amount`] of outputs
pub fn total_amount(&self) -> Amount {
pub fn total_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(
self.outputs
.iter()
.map(|BlindedMessage { amount, .. }| *amount)
.sum()
.map(|BlindedMessage { amount, .. }| *amount),
)
.map_err(|_| Error::AmountOverflow)
}
}

View File

@@ -21,6 +21,9 @@ pub enum Error {
/// Unknown Quote State
#[error("Unknown quote state")]
UnknownState,
/// Amount overflow
#[error("Amount Overflow")]
AmountOverflow,
}
/// Melt quote request [NUT-05]
@@ -210,8 +213,9 @@ pub struct MeltBolt11Request {
impl MeltBolt11Request {
/// Total [`Amount`] of [`Proofs`]
pub fn proofs_amount(&self) -> Amount {
self.inputs.iter().map(|proof| proof.amount).sum()
pub fn proofs_amount(&self) -> Result<Amount, Error> {
Amount::try_sum(self.inputs.iter().map(|proof| proof.amount))
.map_err(|_| Error::AmountOverflow)
}
}

View File

@@ -10,7 +10,7 @@ impl MeltBolt11Request {
pub fn output_amount(&self) -> Option<Amount> {
self.outputs
.as_ref()
.map(|o| o.iter().map(|proof| proof.amount).sum())
.and_then(|o| Amount::try_sum(o.iter().map(|proof| proof.amount)).ok())
}
}
@@ -19,6 +19,6 @@ impl MeltQuoteBolt11Response {
pub fn change_amount(&self) -> Option<Amount> {
self.change
.as_ref()
.map(|c| c.iter().map(|b| b.amount).sum())
.and_then(|o| Amount::try_sum(o.iter().map(|proof| proof.amount)).ok())
}
}

View File

@@ -39,6 +39,9 @@ pub enum Error {
/// Unknown Key
#[error("Unknown key")]
UnknownKey,
/// Amount overflow
#[error("Amount Overflow")]
AmountOverflow,
/// Spending Locktime not provided
#[error("Spending condition locktime not provided")]
LocktimeNotProvided,

View File

@@ -145,7 +145,7 @@ impl Wallet {
None,
)
.await?;
let balance = proofs.iter().map(|p| p.proof.amount).sum::<Amount>();
let balance = Amount::try_sum(proofs.iter().map(|p| p.proof.amount))?;
Ok(balance)
}
@@ -457,7 +457,7 @@ impl Wallet {
.into_iter()
.partition(|p| pending_states.contains(&p.y));
let amount = pending_proofs.iter().map(|p| p.proof.amount).sum();
let amount = Amount::try_sum(pending_proofs.iter().map(|p| p.proof.amount))?;
self.localstore
.update_proofs(
@@ -678,7 +678,7 @@ impl Wallet {
&keys,
)?;
let minted_amount = proofs.iter().map(|p| p.amount).sum();
let minted_amount = Amount::try_sum(proofs.iter().map(|p| p.amount))?;
// Remove filled quote from store
self.localstore.remove_mint_quote(&quote_info.id).await?;
@@ -754,7 +754,7 @@ impl Wallet {
let mut values = Vec::new();
for amount in amounts_needed_refill {
let values_sum: Amount = values.clone().into_iter().sum();
let values_sum = Amount::try_sum(values.clone().into_iter())?;
if values_sum + amount <= change_amount {
values.push(amount);
}
@@ -776,7 +776,7 @@ impl Wallet {
let active_keyset_id = self.get_active_mint_keyset().await?.id;
// Desired amount is either amount passed or value of all proof
let proofs_total: Amount = proofs.iter().map(|p| p.amount).sum();
let proofs_total = Amount::try_sum(proofs.iter().map(|p| p.amount))?;
let ys: Vec<PublicKey> = proofs.iter().map(|p| p.y()).collect::<Result<_, _>>()?;
self.localstore.set_pending_proofs(ys).await?;
@@ -953,7 +953,7 @@ impl Wallet {
for proof in all_proofs {
let proofs_to_send_amount =
proofs_to_send.iter().map(|p| p.amount).sum::<Amount>();
Amount::try_sum(proofs_to_send.iter().map(|p| p.amount))?;
if proof.amount + proofs_to_send_amount <= amount + pre_swap.fee {
proofs_to_send.push(proof);
} else {
@@ -965,7 +965,7 @@ impl Wallet {
}
};
let send_amount: Amount = proofs_to_send.iter().map(|p| p.amount).sum();
let send_amount = Amount::try_sum(proofs_to_send.iter().map(|p| p.amount))?;
if send_amount.ne(&(amount + pre_swap.fee)) {
tracing::warn!(
@@ -1158,7 +1158,7 @@ impl Wallet {
// Handle exact matches offline
(SendKind::OfflineExact, Ok(selected_proofs), _) => {
let selected_proofs_amount =
selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let amount_to_send = match include_fees {
true => amount + self.get_proofs_fee(&selected_proofs).await?,
@@ -1175,7 +1175,7 @@ impl Wallet {
// Handle exact matches
(SendKind::OnlineExact, Ok(selected_proofs), _) => {
let selected_proofs_amount =
selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let amount_to_send = match include_fees {
true => amount + self.get_proofs_fee(&selected_proofs).await?,
@@ -1196,7 +1196,7 @@ impl Wallet {
// Handle offline tolerance
(SendKind::OfflineTolerance(tolerance), Ok(selected_proofs), _) => {
let selected_proofs_amount =
selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let amount_to_send = match include_fees {
true => amount + self.get_proofs_fee(&selected_proofs).await?,
@@ -1222,7 +1222,7 @@ impl Wallet {
// Handle online tolerance with successful selection
(SendKind::OnlineTolerance(tolerance), Ok(selected_proofs), _) => {
let selected_proofs_amount =
selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let amount_to_send = match include_fees {
true => amount + self.get_proofs_fee(&selected_proofs).await?,
false => amount,
@@ -1435,7 +1435,7 @@ impl Wallet {
Some(change_proofs) => {
tracing::debug!(
"Change amount returned from melt: {}",
change_proofs.iter().map(|p| p.amount).sum::<Amount>()
Amount::try_sum(change_proofs.iter().map(|p| p.amount))?
);
// Update counter for keyset
@@ -1532,7 +1532,7 @@ impl Wallet {
) -> Result<Proofs, Error> {
// TODO: Check all proofs are same unit
if proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
if Amount::try_sum(proofs.iter().map(|p| p.amount))? < amount {
return Err(Error::InsufficientFunds);
}
@@ -1571,8 +1571,8 @@ impl Wallet {
break;
}
remaining_amount =
amount + fees - selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
remaining_amount = amount.checked_add(fees).ok_or(Error::AmountOverflow)?
- Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
(proofs_larger, proofs_smaller) = proofs_smaller
.into_iter()
.skip(1)
@@ -1608,7 +1608,7 @@ impl Wallet {
for inactive_proof in inactive_proofs {
selected_proofs.push(inactive_proof);
let selected_total = selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
let selected_total = Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let fees = self.get_proofs_fee(&selected_proofs).await?;
if selected_total >= amount + fees {
@@ -1620,7 +1620,7 @@ impl Wallet {
for active_proof in active_proofs {
selected_proofs.push(active_proof);
let selected_total = selected_proofs.iter().map(|p| p.amount).sum::<Amount>();
let selected_total = Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?;
let fees = self.get_proofs_fee(&selected_proofs).await?;
if selected_total >= amount + fees {
@@ -1767,7 +1767,7 @@ impl Wallet {
let mut total_amount = Amount::ZERO;
for (mint, proofs) in received_proofs {
total_amount += proofs.iter().map(|p| p.amount).sum();
total_amount += Amount::try_sum(proofs.iter().map(|p| p.amount))?;
let proofs = proofs
.into_iter()
.map(|proof| ProofInfo::new(proof, mint.clone(), State::Unspent, self.unit))
@@ -1927,7 +1927,7 @@ impl Wallet {
.cloned()
.collect();
restored_value += unspent_proofs.iter().map(|p| p.amount).sum();
restored_value += Amount::try_sum(unspent_proofs.iter().map(|p| p.amount))?;
let unspent_proofs = unspent_proofs
.into_iter()