diff --git a/controller/src/command.rs b/controller/src/command.rs index e2fa0dd..d596fce 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -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 { - let net_change = self.get_net_change()?; + fn to_api_args(&self, slate: &Slate) -> Result { + 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" )); } diff --git a/doc/contracts.md b/doc/contracts.md index b8ee84a..f036f97 100644 --- a/doc/contracts.md +++ b/doc/contracts.md @@ -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 diff --git a/libwallet/src/contract/actions/README.md b/libwallet/src/contract/actions/README.md index 966771e..62cbd7f 100644 --- a/libwallet/src/contract/actions/README.md +++ b/libwallet/src/contract/actions/README.md @@ -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 diff --git a/libwallet/src/contract/actions/mod.rs b/libwallet/src/contract/actions/mod.rs index 9ff6102..332af86 100644 --- a/libwallet/src/contract/actions/mod.rs +++ b/libwallet/src/contract/actions/mod.rs @@ -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, Error> { +/// Net change implied for the signer of a first-step slate +pub fn initial_net_change(state: &SlateState, amount: u64) -> Result, Error> { let sign = match state { SlateState::Standard1 => 1, SlateState::Invoice1 => -1, diff --git a/libwallet/src/contract/mod.rs b/libwallet/src/contract/mod.rs index a7dbaf5..2b587dc 100644 --- a/libwallet/src/contract/mod.rs +++ b/libwallet/src/contract/mod.rs @@ -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; diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 0180d2d..949a359 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -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 diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 8355658..cf11e67 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -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, diff --git a/tests/cmd_line_contract.rs b/tests/cmd_line_contract.rs index d6a94a2..0047909 100644 --- a/tests/cmd_line_contract.rs +++ b/tests/cmd_line_contract.rs @@ -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);