simplify contract amounts

This commit is contained in:
wiesche
2026-09-12 18:20:43 +02:00
parent 2f3f65a227
commit f1d11e38ab
3 changed files with 53 additions and 65 deletions
+51 -46
View File
@@ -1740,30 +1740,27 @@ fn set_proof_sender(
Ok(()) Ok(())
} }
impl ContractNewArgs { fn contract_net_change(receive: Option<u64>, send: Option<u64>) -> Result<Option<i64>, Error> {
fn get_net_change(&self) -> Result<i64, Error> { if receive.is_some() && send.is_some() {
if self.receive.is_some() && self.send.is_some() { return Err(Error::ArgumentError(
return Err(Error::ArgumentError( "Can't pass both --receive and --send parameters.".into(),
"Can't pass both --receive and --send parameters.".into(), ));
));
}
let to_i64 = |v: u64| {
i64::try_from(v).map_err(|_| Error::ArgumentError(format!("Amount {} is too large", v)))
};
match self.receive {
None => match self.send {
None => Err(Error::ArgumentError(
"Send or receive not specified.".into(),
)),
Some(v) => Ok(-to_i64(v)?), // negative net change on send
},
Some(v) => to_i64(v), // positive net change on receive
}
} }
let to_i64 = |v: u64| {
i64::try_from(v).map_err(|_| Error::ArgumentError(format!("Amount {} is too large", v)))
};
Ok(match (receive, send) {
(Some(value), _) => Some(to_i64(value)?),
(_, Some(value)) => Some(-to_i64(value)?),
(None, None) => None,
})
}
impl ContractNewArgs {
// Create a ContractNewArgsAPI from the ContractNewArgs // Create a ContractNewArgsAPI from the ContractNewArgs
fn to_api_args(&self) -> Result<ContractNewArgsAPI, Error> { fn to_api_args(&self) -> Result<ContractNewArgsAPI, Error> {
let net_change = self.get_net_change()?; let net_change = contract_net_change(self.receive, self.send)?
.ok_or_else(|| Error::ArgumentError("Send or receive not specified.".into()))?;
Ok(ContractNewArgsAPI { Ok(ContractNewArgsAPI {
ttl_blocks: self.ttl_blocks, ttl_blocks: self.ttl_blocks,
setup_args: ContractSetupArgsAPI { setup_args: ContractSetupArgsAPI {
@@ -1850,42 +1847,18 @@ pub struct ContractSetupArgs {
pub proof_type: Option<ProofType>, pub proof_type: Option<ProofType>,
/// Early payment proof memo /// Early payment proof memo
pub memo: Option<PaymentMemo>, pub memo: Option<PaymentMemo>,
// Future features
/// Whether we should automatically sign a receive of any value
// pub auto_receive: Option<bool>,
/// Add outputs
pub add_outputs: bool, // lock outputs early
} }
impl ContractSetupArgs { impl ContractSetupArgs {
fn get_net_change(&self) -> Result<Option<i64>, Error> {
if self.receive.is_some() && self.send.is_some() {
return Err(Error::ArgumentError(
"Can't pass both --receive and --send parameters.".into(),
));
}
let to_i64 = |v: u64| {
i64::try_from(v).map_err(|_| Error::ArgumentError(format!("Amount {} is too large", v)))
};
let net_change = match (self.receive, self.send) {
(Some(v), _) => Some(to_i64(v)?),
(_, Some(v)) => Some(-to_i64(v)?),
(None, None) => None,
};
Ok(net_change)
}
// Create a ContractSetupArgsAPI from the ContractSetupArgs // Create a ContractSetupArgsAPI from the ContractSetupArgs
fn to_api_args(&self, slate: &Slate) -> Result<ContractSetupArgsAPI, Error> { fn to_api_args(&self, slate: &Slate) -> Result<ContractSetupArgsAPI, Error> {
let net_change = match self.get_net_change()? { let net_change = match contract_net_change(self.receive, self.send)? {
Some(value) => Some(value), Some(value) => Some(value),
None => initial_net_change(&slate.state, slate.amount)?, None => initial_net_change(&slate.state, slate.amount)?,
}; };
Ok(ContractSetupArgsAPI { Ok(ContractSetupArgsAPI {
fee_rate: self.fee_rate, fee_rate: self.fee_rate,
net_change: net_change, net_change: net_change,
add_outputs: self.add_outputs,
selection_args: OutputSelectionArgs { selection_args: OutputSelectionArgs {
minimum_confirmations: self.minimum_confirmations, minimum_confirmations: self.minimum_confirmations,
use_inputs: match self.use_inputs.as_ref() { use_inputs: match self.use_inputs.as_ref() {
@@ -2182,6 +2155,32 @@ mod contract_tests {
)); ));
} }
#[test]
fn contract_amounts() {
for (receive, send, expected) in [
(None, None, None),
(Some(0), None, Some(0)),
(None, Some(0), Some(0)),
(Some(i64::MAX as u64), None, Some(i64::MAX)),
(None, Some(i64::MAX as u64), Some(-i64::MAX)),
] {
assert_eq!(contract_net_change(receive, send).unwrap(), expected);
}
let too_large = i64::MAX as u64 + 1;
for (receive, send) in [(Some(too_large), None), (None, Some(too_large))] {
assert!(matches!(
contract_net_change(receive, send),
Err(Error::ArgumentError(message))
if message == format!("Amount {} is too large", too_large)
));
}
assert!(matches!(
contract_net_change(Some(too_large), Some(1)),
Err(Error::ArgumentError(message))
if message == "Can't pass both --receive and --send parameters."
));
}
#[test] #[test]
fn contract_sign_args() { fn contract_sign_args() {
let args = ContractSetupArgs { let args = ContractSetupArgs {
@@ -2194,7 +2193,6 @@ mod contract_tests {
minimum_confirmations: None, minimum_confirmations: None,
fee_rate: Some(2), fee_rate: Some(2),
outfile: None, outfile: None,
add_outputs: false,
proof_type: Some(ProofType::Invoice), proof_type: Some(ProofType::Invoice),
memo: Some(PaymentMemo::new("payment".into()).unwrap()), memo: Some(PaymentMemo::new("payment".into()).unwrap()),
}; };
@@ -2212,6 +2210,7 @@ mod contract_tests {
set_proof_sender(&mut api_args.proof_args, Some(&sender), Some(&fallback)).unwrap(); set_proof_sender(&mut api_args.proof_args, Some(&sender), Some(&fallback)).unwrap();
assert_eq!(api_args.fee_rate, Some(2)); assert_eq!(api_args.fee_rate, Some(2));
assert_eq!(api_args.net_change, Some(1)); assert_eq!(api_args.net_change, Some(1));
assert!(!api_args.add_outputs);
assert_eq!(api_args.proof_args.proof_type, ProofType::Invoice); assert_eq!(api_args.proof_args.proof_type, ProofType::Invoice);
assert_eq!(api_args.proof_args.sender_address, Some(sender.pub_key)); assert_eq!(api_args.proof_args.sender_address, Some(sender.pub_key));
assert_eq!( assert_eq!(
@@ -2305,5 +2304,11 @@ mod contract_tests {
Err(Error::ArgumentError(message)) Err(Error::ArgumentError(message))
if message == "Can't pass both --receive and --send parameters." if message == "Can't pass both --receive and --send parameters."
)); ));
invalid.receive = None;
invalid.send = None;
assert!(matches!(
invalid.to_api_args(),
Err(Error::ArgumentError(message)) if message == "Send or receive not specified."
));
} }
} }
+2 -18
View File
@@ -452,18 +452,11 @@ where
/// Compares the setup args provided at call with those in the Context and checks whether they conflict. /// Compares the setup args provided at call with those in the Context and checks whether they conflict.
/// This is relevant to see if there's any conflict in the arguments provided at step1 with step3. /// This is relevant to see if there's any conflict in the arguments provided at step1 with step3.
/// The caller resolves net_change with get_net_change before this check
pub fn verify_setup_args_consistency( pub fn verify_setup_args_consistency(
ctx_setup_args: &ContractSetupArgsAPI, ctx_setup_args: &ContractSetupArgsAPI,
cur_setup_args: &ContractSetupArgsAPI, cur_setup_args: &ContractSetupArgsAPI,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Compare net_change
if ctx_setup_args.net_change.unwrap() != cur_setup_args.net_change.unwrap() {
return Err(Error::GenericError(format!(
"Inconsistent net change. Ctx net_change:{}, Current net_change: {}",
ctx_setup_args.net_change.unwrap(),
cur_setup_args.net_change.unwrap()
)));
}
// Compare num_participants // Compare num_participants
if ctx_setup_args.num_participants != cur_setup_args.num_participants { if ctx_setup_args.num_participants != cur_setup_args.num_participants {
return Err(Error::GenericError(format!( return Err(Error::GenericError(format!(
@@ -567,23 +560,14 @@ mod tests {
#[test] #[test]
fn fee_rate_consistency() { fn fee_rate_consistency() {
let setup = ContractSetupArgsAPI { let setup = ContractSetupArgsAPI {
net_change: Some(-1),
fee_rate: Some(2), fee_rate: Some(2),
..Default::default() ..Default::default()
}; };
assert!(verify_setup_args_consistency(&setup, &setup).is_ok()); assert!(verify_setup_args_consistency(&setup, &setup).is_ok());
assert!(verify_setup_args_consistency( assert!(verify_setup_args_consistency(&setup, &ContractSetupArgsAPI::default()).is_ok());
&setup,
&ContractSetupArgsAPI {
net_change: Some(-1),
..Default::default()
}
)
.is_ok());
let err = verify_setup_args_consistency( let err = verify_setup_args_consistency(
&setup, &setup,
&ContractSetupArgsAPI { &ContractSetupArgsAPI {
net_change: Some(-1),
fee_rate: Some(3), fee_rate: Some(3),
..Default::default() ..Default::default()
}, },
-1
View File
@@ -1183,7 +1183,6 @@ pub fn parse_contract_setup_args(
receive: receive, receive: receive,
send: send, send: send,
as_json: as_json, as_json: as_json,
add_outputs: false,
use_inputs: use_inputs, use_inputs: use_inputs,
make_outputs: make_outputs, make_outputs: make_outputs,
minimum_confirmations, minimum_confirmations,