De-panic output_amounts(): fallible make-outputs parsing

OutputSelectionArgs::output_amounts() unwrap()'d the float parse of each
make-outputs value, panicking on bad input via the API path (the CLI now
validates earlier). Return Result instead; sum_output_amounts() propagates.
num_custom_outputs() now counts CSV entries without parsing, so it stays
infallible and the debug! macros that use it are unaffected.
This commit is contained in:
jwinterm
2026-06-03 13:56:32 -04:00
parent 2e3b04498c
commit 3fcf3b7816
3 changed files with 24 additions and 16 deletions
@@ -137,7 +137,7 @@ fn contract_self_spend_custom_tx_impl(test_dir: &'static str) -> Result<(), libw
assert_eq!(
commits[15].output.value,
3 * consensus::REWARD
- selection_args.sum_output_amounts()
- selection_args.sum_output_amounts()?
- my_fee_contribution(3, 6, 1, 1)?.fee()
);
Ok(())
+2 -2
View File
@@ -92,7 +92,7 @@ where
// Σmy_inputs >= Σmy_outputs + my_fee_cost
// Each party later balances the equation by adding an additional output (change output or receiver output)
let net_change = setup_args.net_change.unwrap();
let custom_outputs_amount_sum = setup_args.selection_args.sum_output_amounts();
let custom_outputs_amount_sum = setup_args.selection_args.sum_output_amounts()?;
let pay_amount = if net_change < 0 {
net_change.abs() as u64
} else {
@@ -260,7 +260,7 @@ fn build_output_amount_list(
setup_args: &ContractSetupArgsAPI,
) -> Result<Vec<u64>, Error> {
let expected_net_change = setup_args.net_change.unwrap();
let mut my_output_amounts = setup_args.selection_args.output_amounts();
let mut my_output_amounts = setup_args.selection_args.output_amounts()?;
let custom_outputs_sum = my_output_amounts.iter().sum::<u64>();
// We know that `Σmy_inputs >= Σmy_outputs + my_fee_cost` holds so we balance the equation by adding
// an additional output holding the missing amount (change output or receiver output)
+21 -13
View File
@@ -14,6 +14,7 @@
//! Types related to a contract
use crate::error::Error;
use crate::grin_core::consensus;
use crate::slate_versions::ser as dalek_ser;
use ed25519_dalek::PublicKey as DalekPublicKey;
@@ -49,25 +50,32 @@ impl OutputSelectionArgs {
}
}
/// Returns the outputs we have to create
pub fn output_amounts(&self) -> Vec<u64> {
if self.make_outputs.is_some() {
let output_amounts: Vec<u64> = self.make_outputs.as_ref().unwrap()[..]
// TODO: move consensus code outside of here. Consider turning make_outputs to Vec<u64>
pub fn output_amounts(&self) -> Result<Vec<u64>, Error> {
match self.make_outputs.as_ref() {
Some(s) => s
.split(",")
// TODO: move consensus code outside of here. Consider turning make_outputs to Vec<u64>
.map(|amt| (amt.parse::<f64>().unwrap() * consensus::GRIN_BASE as f64) as u64)
.collect();
output_amounts
} else {
vec![]
.map(|amt| {
amt.parse::<f64>()
.map(|g| (g * consensus::GRIN_BASE as f64) as u64)
.map_err(|e| {
Error::GenericError(format!("Invalid output amount '{}': {}", amt, e))
})
})
.collect(),
None => Ok(vec![]),
}
}
/// Returns the sum of our output amounts
pub fn sum_output_amounts(&self) -> u64 {
self.output_amounts().iter().sum()
pub fn sum_output_amounts(&self) -> Result<u64, Error> {
Ok(self.output_amounts()?.iter().sum())
}
/// Returns the number of custom outputs
/// Returns the number of custom outputs (count only; does not parse the amounts)
pub fn num_custom_outputs(&self) -> usize {
self.output_amounts().len()
self.make_outputs
.as_ref()
.map(|s| s.split(",").count())
.unwrap_or(0)
}
// TODO: make sure to validate this: if custom outputs are specified, it has to be a payjoin.