Contracts: validate net_change instead of unwrapping

contract `new` and the foreign `contract_new` API unwrapped the optional
net_change field, so a request omitting it panicked the wallet. Return an
error instead.

- new::compute and foreign::contract_new: net_change.ok_or_else(..).
- test: contract_new with no net change errors rather than panicking.

Addresses PR #754 review (new.rs, foreign.rs).
This commit is contained in:
jwinterm
2026-06-04 19:34:21 -04:00
parent 571b1f968a
commit 15cbbc96e8
3 changed files with 41 additions and 2 deletions
+35
View File
@@ -295,3 +295,38 @@ fn wallet_contract_unknown_account() -> Result<(), libwallet::Error> {
clean_output_dir(test_dir);
Ok(())
}
/// A contract with no net change (neither --send nor --receive) must error
/// rather than panic on an unwrap of the optional field.
fn contract_missing_net_change_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
let (wallets, _chain, stopper, _bh) =
create_wallets(vec![vec![("default", 1)]], test_dir).unwrap();
let wallet1 = wallets[0].0.clone();
let mask1 = wallets[0].1.as_ref();
let res = wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let args = &ContractNewArgsAPI {
setup_args: ContractSetupArgsAPI {
net_change: None,
..Default::default()
},
..Default::default()
};
api.contract_new(m, args)?;
Ok(())
});
assert!(res.is_err(), "expected an error when net_change is missing");
stopper.store(false, Ordering::Relaxed);
thread::sleep(Duration::from_millis(200));
Ok(())
}
#[test]
fn wallet_contract_missing_net_change() -> Result<(), libwallet::Error> {
let test_dir = "test_output/contract_missing_net_change";
setup(test_dir);
contract_missing_net_change_impl(test_dir)?;
clean_output_dir(test_dir);
Ok(())
}
+3 -1
View File
@@ -251,7 +251,9 @@ where
C: NodeClient + 'a,
K: Keychain + 'a,
{
let net_change = args.setup_args.net_change.unwrap();
let net_change = args.setup_args.net_change.ok_or_else(|| {
Error::GenericError("Contract requires a net change (--send or --receive)".to_string())
})?;
if net_change <= 0 {
return Err(Error::GenericError(
"Can't create a non-receiving contract from a foreign API.".to_string(),
+3 -1
View File
@@ -61,7 +61,9 @@ where
C: NodeClient + 'a,
K: Keychain + 'a,
{
let net_change = setup_args.net_change.unwrap();
let net_change = setup_args.net_change.ok_or_else(|| {
Error::GenericError("Contract requires a net change (--send or --receive)".to_string())
})?;
debug!("contract::new => net_change passed: {}", net_change);
// Initialize a new contract (if net_change is positive, I'm the receiver meaning this is invoice flow)