diff --git a/bindings/cdk-js/src/nuts/nut03.rs b/bindings/cdk-js/src/nuts/nut03.rs index 993dba9e..166bb72a 100644 --- a/bindings/cdk-js/src/nuts/nut03.rs +++ b/bindings/cdk-js/src/nuts/nut03.rs @@ -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() } } diff --git a/bindings/cdk-js/src/nuts/nut04.rs b/bindings/cdk-js/src/nuts/nut04.rs index 3cf7841b..6cbdb33c 100644 --- a/bindings/cdk-js/src/nuts/nut04.rs +++ b/bindings/cdk-js/src/nuts/nut04.rs @@ -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() } } diff --git a/crates/cdk-axum/src/router_handlers.rs b/crates/cdk-axum/src/router_handlers.rs index 1df73a92..83d0d1c7 100644 --- a/crates/cdk-axum/src/router_handlers.rs +++ b/crates/cdk-axum/src/router_handlers.rs @@ -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) => { diff --git a/crates/cdk-integration-tests/tests/overflow.rs b/crates/cdk-integration-tests/tests/overflow.rs index 80d6612e..0c49b449 100644 --- a/crates/cdk-integration-tests/tests/overflow.rs +++ b/crates/cdk-integration-tests/tests/overflow.rs @@ -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::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::try_sum(post_swap_proofs.iter().map(|p| p.amount)).expect("Amount Overflowed") ); println!( diff --git a/crates/cdk/src/amount.rs b/crates/cdk/src/amount.rs index e73a1621..cba702f8 100644 --- a/crates/cdk/src/amount.rs +++ b/crates/cdk/src/amount.rs @@ -54,7 +54,7 @@ impl Amount { parts.extend(amount_left.split()); } - parts_total = parts.clone().iter().copied().sum::(); + 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>(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] diff --git a/crates/cdk/src/mint/error.rs b/crates/cdk/src/mint/error.rs index f062cf79..cfb884c4 100644 --- a/crates/cdk/src/mint/error.rs +++ b/crates/cdk/src/mint/error.rs @@ -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), diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 33b9a1cf..26a24510 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -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 = @@ -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) { - true => Some(p.amount), - false => None, - }) - .sum(); + 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, + } + }))?; total_redeemed.insert(keyset.id, total_spent); } diff --git a/crates/cdk/src/nuts/nut00/mod.rs b/crates/cdk/src/nuts/nut00/mod.rs index 9ea95787..08405073 100644 --- a/crates/cdk/src/nuts/nut00/mod.rs +++ b/crates/cdk/src/nuts/nut00/mod.rs @@ -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 { + Ok(Amount::try_sum( + self.secrets.iter().map(|PreMint { amount, .. }| *amount), + )?) } /// [`BlindedMessage`]s from [`PreMintSecrets`] diff --git a/crates/cdk/src/nuts/nut00/token.rs b/crates/cdk/src/nuts/nut00/token.rs index 6552ba2a..c969196a 100644 --- a/crates/cdk/src/nuts/nut00/token.rs +++ b/crates/cdk/src/nuts/nut00/token.rs @@ -74,7 +74,7 @@ impl Token { } /// Total value of [`Token`] - pub fn value(&self) -> Amount { + pub fn value(&self) -> Result { match self { Self::TokenV3(token) => token.value(), Self::TokenV4(token) => token.value(), @@ -207,11 +207,13 @@ impl TokenV3 { } #[inline] - fn value(&self) -> Amount { - self.token - .iter() - .map(|t| t.proofs.iter().map(|p| p.amount).sum()) - .sum() + fn value(&self) -> Result { + Ok(Amount::try_sum( + self.token + .iter() + .map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount))) + .collect::, _>>()?, + )?) } #[inline] @@ -305,11 +307,13 @@ impl TokenV4 { } #[inline] - fn value(&self) -> Amount { - self.token - .iter() - .map(|t| t.proofs.iter().map(|p| p.amount).sum()) - .sum() + fn value(&self) -> Result { + Ok(Amount::try_sum( + self.token + .iter() + .map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount))) + .collect::, _>>()?, + )?) } #[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)); diff --git a/crates/cdk/src/nuts/nut03.rs b/crates/cdk/src/nuts/nut03.rs index 21a91302..6e2b5f65 100644 --- a/crates/cdk/src/nuts/nut03.rs +++ b/crates/cdk/src/nuts/nut03.rs @@ -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::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::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 { - self.signatures - .iter() - .map(|BlindSignature { amount, .. }| *amount) - .sum() + pub fn promises_amount(&self) -> Result { + Amount::try_sum( + self.signatures + .iter() + .map(|BlindSignature { amount, .. }| *amount), + ) } } diff --git a/crates/cdk/src/nuts/nut04.rs b/crates/cdk/src/nuts/nut04.rs index f0e01e9e..d4b8217a 100644 --- a/crates/cdk/src/nuts/nut04.rs +++ b/crates/cdk/src/nuts/nut04.rs @@ -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 { - self.outputs - .iter() - .map(|BlindedMessage { amount, .. }| *amount) - .sum() + pub fn total_amount(&self) -> Result { + Amount::try_sum( + self.outputs + .iter() + .map(|BlindedMessage { amount, .. }| *amount), + ) + .map_err(|_| Error::AmountOverflow) } } diff --git a/crates/cdk/src/nuts/nut05.rs b/crates/cdk/src/nuts/nut05.rs index 63041278..e1b9fac6 100644 --- a/crates/cdk/src/nuts/nut05.rs +++ b/crates/cdk/src/nuts/nut05.rs @@ -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::try_sum(self.inputs.iter().map(|proof| proof.amount)) + .map_err(|_| Error::AmountOverflow) } } diff --git a/crates/cdk/src/nuts/nut08.rs b/crates/cdk/src/nuts/nut08.rs index ce723897..3f05bed3 100644 --- a/crates/cdk/src/nuts/nut08.rs +++ b/crates/cdk/src/nuts/nut08.rs @@ -10,7 +10,7 @@ impl MeltBolt11Request { pub fn output_amount(&self) -> Option { 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 { 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()) } } diff --git a/crates/cdk/src/wallet/error.rs b/crates/cdk/src/wallet/error.rs index 4e026cf9..456597a0 100644 --- a/crates/cdk/src/wallet/error.rs +++ b/crates/cdk/src/wallet/error.rs @@ -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, diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index e6a8dfa9..b898bbe1 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -145,7 +145,7 @@ impl Wallet { None, ) .await?; - let balance = proofs.iter().map(|p| p.proof.amount).sum::(); + 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("e_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 = proofs.iter().map(|p| p.y()).collect::>()?; 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::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::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::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::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::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::try_sum(change_proofs.iter().map(|p| p.amount))? ); // Update counter for keyset @@ -1532,7 +1532,7 @@ impl Wallet { ) -> Result { // TODO: Check all proofs are same unit - if proofs.iter().map(|p| p.amount).sum::() < 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::(); + 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::(); + 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::(); + 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()