From 8fcfa6d3d5ebd6b241c504cb9dafbbb0889610c1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 9 Aug 2025 14:06:27 -0400 Subject: [PATCH] Add dedicated error for when amounts aren't representable within a u64 Fixes the issue where _inputs_ could still overflow u64::MAX and cause a panic. --- networks/monero/wallet/src/send/mod.rs | 56 ++++++++++++++++++-------- processor/src/networks/monero.rs | 3 ++ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/networks/monero/wallet/src/send/mod.rs b/networks/monero/wallet/src/send/mod.rs index f40de06c..e0c59e20 100644 --- a/networks/monero/wallet/src/send/mod.rs +++ b/networks/monero/wallet/src/send/mod.rs @@ -177,6 +177,17 @@ pub enum SendError { /// The created transaction was too large. #[cfg_attr(feature = "std", error("too large of a transaction"))] TooLargeTransaction, + /// The transactions' amounts could not be represented within a `u64`. + #[cfg_attr( + feature = "std", + error("transaction amounts exceed u64::MAX (in {in_amount}, out {out_amount})") + )] + AmountsUnrepresentable { + /// The amount in (via inputs). + in_amount: u128, + /// The amount which would be out (between outputs and the fee). + out_amount: u128, + }, /// This transaction could not pay for itself. #[cfg_attr( feature = "std", @@ -300,23 +311,34 @@ impl SignableTransaction { } // Make sure we have enough funds - let in_amount = self.inputs.iter().map(|input| input.commitment().amount).sum::(); - let payments_amount = self - .payments - .iter() - .filter_map(|payment| match payment { - InternalPayment::Payment(_, amount) => Some(*amount), - InternalPayment::Change(_) => None, - }) - .try_fold(0, u64::checked_add); - let payments_amount = payments_amount.ok_or(SendError::TooManyOutputs)?; - let (weight, necessary_fee) = self.weight_and_necessary_fee(); - if payments_amount.checked_add(necessary_fee).is_none_or(|total_out| in_amount < total_out) { - Err(SendError::NotEnoughFunds { - inputs: in_amount, - outputs: payments_amount, - necessary_fee: Some(necessary_fee), - })?; + let weight; + { + let in_amount: u128 = + self.inputs.iter().map(|input| u128::from(input.commitment().amount)).sum(); + let payments_amount: u128 = self + .payments + .iter() + .filter_map(|payment| match payment { + InternalPayment::Payment(_, amount) => Some(u128::from(*amount)), + InternalPayment::Change(_) => None, + }) + .sum(); + let necessary_fee; + (weight, necessary_fee) = self.weight_and_necessary_fee(); + let out_amount = payments_amount + u128::from(necessary_fee); + let in_out_amount = u64::try_from(in_amount) + .and_then(|in_amount| u64::try_from(out_amount).map(|out_amount| (in_amount, out_amount))); + let Ok((in_amount, out_amount)) = in_out_amount else { + Err(SendError::AmountsUnrepresentable { in_amount, out_amount })? + }; + if in_amount < out_amount { + Err(SendError::NotEnoughFunds { + inputs: in_amount, + outputs: u64::try_from(payments_amount) + .expect("total out fit within u64 but not part of total out"), + necessary_fee: Some(necessary_fee), + })?; + } } // The limit is half the no-penalty block size diff --git a/processor/src/networks/monero.rs b/processor/src/networks/monero.rs index 4e70c002..6813a76f 100644 --- a/processor/src/networks/monero.rs +++ b/processor/src/networks/monero.rs @@ -390,6 +390,8 @@ impl Monero { MakeSignableTransactionResult::SignableTransaction(signable) } })), + // AmountsUnrepresentable is unreachable on Monero without 100% of the supply before tail + // emission or fundamental corruption Err(e) => match e { SendError::UnsupportedRctType => { panic!("trying to use an RctType unsupported by monero-wallet") @@ -398,6 +400,7 @@ impl Monero { SendError::InvalidDecoyQuantity | SendError::NoOutputs | SendError::TooManyOutputs | + SendError::AmountsUnrepresentable { .. } | SendError::NoChange | SendError::TooMuchArbitraryData | SendError::TooLargeTransaction |