infer amount when signing contracts

This commit is contained in:
wiesche
2026-09-08 18:03:23 +02:00
parent 8cf5069c07
commit b0722adce9
8 changed files with 43 additions and 21 deletions
+34 -12
View File
@@ -21,11 +21,11 @@ use crate::error::Error;
use crate::impls::PathToSlatepack;
use crate::keychain;
use crate::libwallet::api_impl::types::update_tx_slate_state;
use crate::libwallet::contract::can_finalize;
use crate::libwallet::contract::types::{
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs,
PaymentMemo, ProofArgs, ProofType,
};
use crate::libwallet::contract::{can_finalize, initial_net_change};
use crate::libwallet::{
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState,
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
@@ -1716,7 +1716,7 @@ fn contract_proof_args(
}
if proof_type.is_some() && !receiving {
return Err(Error::ArgumentError(
"Early payment proofs require a positive --receive amount".to_string(),
"Early payment proofs require a receiving contract".to_string(),
));
}
Ok(match proof_type {
@@ -1894,8 +1894,11 @@ impl ContractSetupArgs {
}
// Create a ContractSetupArgsAPI from the ContractSetupArgs
fn to_api_args(&self) -> Result<ContractSetupArgsAPI, Error> {
let net_change = self.get_net_change()?;
fn to_api_args(&self, slate: &Slate) -> Result<ContractSetupArgsAPI, Error> {
let net_change = match self.get_net_change()? {
Some(value) => Some(value),
None => initial_net_change(&slate.state, slate.amount)?,
};
Ok(ContractSetupArgsAPI {
fee_rate: self.fee_rate,
net_change: net_change,
@@ -1912,7 +1915,7 @@ impl ContractSetupArgs {
proof_args: contract_proof_args(
self.proof_type,
self.memo.clone(),
self.receive.unwrap_or(0) > 0,
net_change.unwrap_or(0) > 0,
)?,
..Default::default()
})
@@ -1930,7 +1933,6 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut contract_sign_args = args.to_api_args()?;
let recipient = slatepack_recipient(args.counterparty_addr.as_deref())?;
print_contract_status("Paste slatepack:", args.as_json);
let mut slatepack_msg = String::new();
@@ -1939,6 +1941,7 @@ where
.map_err(|e| libwallet::Error::GenericError(format!("Failed to read from stdin: {}", e)))?;
let (mut slate, sender, _) =
parse_slatepack_with_mode(owner_api, keychain_mask, None, Some(slatepack_msg))?;
let mut contract_sign_args = args.to_api_args(&slate)?;
// Bind the proof to the Slatepack sender; --encrypt-for is only the fallback
set_proof_sender(
&mut contract_sign_args.proof_args,
@@ -2158,7 +2161,8 @@ mod contract_tests {
proof_type: Some(ProofType::Invoice),
memo: Some(PaymentMemo::new("payment".into()).unwrap()),
};
let mut api_args = args.to_api_args().unwrap();
let slate = Slate::blank(2, false);
let mut api_args = args.to_api_args(&slate).unwrap();
let key = SigningKey::from_bytes(&[1; 32]);
let sender = SlatepackAddress {
hrp: "tgrin".to_string(),
@@ -2170,6 +2174,7 @@ mod contract_tests {
};
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.net_change, Some(1));
assert_eq!(api_args.proof_args.proof_type, ProofType::Invoice);
assert_eq!(api_args.proof_args.sender_address, Some(sender.pub_key));
assert_eq!(
@@ -2181,22 +2186,39 @@ mod contract_tests {
Some("commitment")
);
for (state, expected) in [
(SlateState::Standard1, Some(10)),
(SlateState::Invoice1, Some(-10)),
(SlateState::Standard2, None),
(SlateState::Invoice2, None),
] {
let mut inferred = args.clone();
inferred.receive = None;
inferred.proof_type = None;
inferred.memo = None;
let mut slate = Slate::blank(2, false);
slate.state = state;
slate.amount = 10;
let api_args = inferred.to_api_args(&slate).unwrap();
assert_eq!(api_args.net_change, expected);
}
let mut invalid = args;
invalid.receive = None;
assert!(matches!(
invalid.to_api_args(),
invalid.to_api_args(&slate),
Err(Error::ArgumentError(message))
if message == "Early payment proofs require a positive --receive amount"
if message == "Early payment proofs require a receiving contract"
));
invalid.receive = Some(0);
assert!(matches!(
invalid.to_api_args(),
invalid.to_api_args(&slate),
Err(Error::ArgumentError(message))
if message == "Early payment proofs require a positive --receive amount"
if message == "Early payment proofs require a receiving contract"
));
invalid.proof_type = None;
assert!(matches!(
invalid.to_api_args(),
invalid.to_api_args(&slate),
Err(Error::ArgumentError(message)) if message == "--memo requires --proof-type"
));
}
+2 -2
View File
@@ -20,8 +20,8 @@ and `revoke`.
`--send` and `--receive` are the amount being transferred, before each side pays its own
fee. The sender spends the amount plus its fee. The receiver gets the amount minus its
fee. Each side pays for the inputs and outputs it adds, plus its rounded-up share of the
kernel fee.
fee. Without either option, `contract sign` reads both from the first slate. Each side pays
for its own inputs and outputs and a rounded-up share of the kernel fee.
CLI amounts are human readable. Values passed to the API, including `make_outputs`, are in
nanogrin. Outputs requested with `make_outputs` are added together with the output needed
-1
View File
@@ -51,7 +51,6 @@ without a user confirmation step.
- Separate side effects out from the computation, as described above.
- Store transactions in LMDB so they can be committed with the wallet state.
- Revisit automatic receive signing once there is a user confirmation step.
- Infer `--receive` and `--send` from the slate state.
- Revisit `remove_other_sigdata` before allowing more than two participants.
### Side-effects
+2 -2
View File
@@ -29,8 +29,8 @@ pub use self::setup::setup;
pub use self::sign::sign;
pub use self::view::view;
// Net change expected from the other party at the first step
fn initial_net_change(state: &SlateState, amount: u64) -> Result<Option<i64>, Error> {
/// Net change implied for the signer of a first-step slate
pub fn initial_net_change(state: &SlateState, amount: u64) -> Result<Option<i64>, Error> {
let sign = match state {
SlateState::Standard1 => 1,
SlateState::Invoice1 => -1,
+1 -1
View File
@@ -22,7 +22,7 @@ mod slate;
pub mod types;
mod utils;
pub use self::actions::{new, revoke, setup, sign, view};
pub use self::actions::{initial_net_change, new, revoke, setup, sign, view};
pub use self::slate::can_finalize;
pub use self::utils::my_fee_contribution;
+2 -2
View File
@@ -518,7 +518,7 @@ subcommands:
long: outfile
takes_value: true
- proof-type:
help: Create an early payment proof of this type (requires a positive --receive amount and --encrypt-for)
help: Create an early payment proof of this type (requires a receiving contract and --encrypt-for)
long: proof-type
possible_values:
- invoice
@@ -585,7 +585,7 @@ subcommands:
long: no-broadcast
takes_value: false
- proof-type:
help: Create an early payment proof of this type (requires a positive --receive amount and a Slatepack sender or --encrypt-for)
help: Create an early payment proof of this type (requires a receiving contract and a Slatepack sender or --encrypt-for)
long: proof-type
possible_values:
- invoice
-1
View File
@@ -1208,7 +1208,6 @@ pub fn parse_contract_setup_args(
}
None => None,
};
// FUTURE: detect a reversed --receive/--send from the slate state (S1 -> receive, I1 -> send).
Ok(command::ContractSetupArgs {
counterparty_addr: counterparty_addr,
+2
View File
@@ -405,6 +405,8 @@ fn parses_contract_options() {
let contract = args.subcommand_matches("contract").unwrap();
let sign_args = contract.subcommand_matches("sign").unwrap();
let parsed = grin_wallet::cmd::wallet_args::parse_contract_setup_args(sign_args).unwrap();
assert_eq!(parsed.receive, None);
assert_eq!(parsed.send, None);
assert_eq!(parsed.minimum_confirmations, None);
assert_eq!(parsed.fee_rate, None);