add contract outfile

This commit is contained in:
wiesche
2026-09-05 11:26:24 +02:00
parent fb0d0493bc
commit bc6516ed81
7 changed files with 70 additions and 33 deletions
+7 -7
View File
@@ -1694,12 +1694,10 @@ pub struct ContractNewArgs {
pub ttl_blocks: Option<u64>,
/// Fee rate in nanogrin per unit of transaction weight
pub fee_rate: Option<u32>,
/// Override the output Slatepack file
pub outfile: Option<String>,
/// Select and lock outputs early
pub add_outputs: bool,
// Future features
/// Save slatepack to a specific filename
pub outfile: Option<String>,
}
impl ContractNewArgs {
@@ -1799,12 +1797,12 @@ pub struct ContractSetupArgs {
pub minimum_confirmations: Option<u64>,
/// Fee rate in nanogrin per unit of transaction weight
pub fee_rate: Option<u32>,
/// Override the output Slatepack file
pub outfile: Option<String>,
// Future features
/// Whether we should automatically sign a receive of any value
// pub auto_receive: Option<bool>,
/// Save slatepack to a specific filename
pub outfile: Option<String>,
/// Add outputs
pub add_outputs: bool, // lock outputs early
}
@@ -1937,6 +1935,8 @@ where
pub struct ContractRevokeArgs {
/// Id of a transaction we want to cancel
pub tx_id: u32,
/// Override the output Slatepack file
pub outfile: Option<String>,
}
pub fn contract_revoke<L, C, K>(
@@ -1961,7 +1961,7 @@ where
)?;
if let Some(slate) = slate_opt {
// A revoke has no counterparty, so write the replacement as plaintext.
let slate_out = prepare_slatepack(api, keychain_mask, &slate, None, None)?;
let slate_out = prepare_slatepack(api, keychain_mask, &slate, None, args.outfile)?;
println!("{}", slate_out);
} else {
println!("Contract revoked. No replacement transaction was created.");
+13 -18
View File
@@ -14,6 +14,7 @@
//! Test contract revoke, including when a different account is active than the
//! one that contributed (and locked) the inputs.
// #[macro_use]
extern crate grin_wallet_api as api;
extern crate grin_wallet_controller as wallet;
extern crate grin_wallet_impls as impls;
extern crate log;
@@ -29,7 +30,7 @@ use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallets, setup};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
/// Revoke the transaction in the requested account when two accounts share a transaction id.
fn contract_revoke_other_account_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
@@ -202,7 +203,7 @@ fn wallet_contract_revoke_other_account() -> Result<(), libwallet::Error> {
/// built yet) must be resumable: a second revoke detects the now-Unspent inputs that are
/// still tagged with the cancelled tx and still produces the self-spend, rather than
/// silently doing nothing.
fn contract_revoke_resume_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
fn contract_revoke_resume_impl(test_dir: &'static str) -> Result<(), wallet::Error> {
let (wallets, _chain, stopper, _bh) =
create_wallets(vec![vec![("default", 4)]], test_dir).unwrap();
let wallet1 = wallets[0].0.clone();
@@ -261,25 +262,19 @@ fn contract_revoke_resume_impl(test_dir: &'static str) -> Result<(), libwallet::
},
)?;
// Resume: revoke must still produce the self-spend from the now-Unspent input.
let mut revoked = None;
wallet::controller::owner_single_use(
wallet1.clone(),
// Resume through the command path and write the self-spend to the requested file.
let outfile = format!("{}/revoke.slatepack", test_dir);
let mut owner = api::Owner::new(wallet1.clone(), None, PathBuf::from(test_dir));
wallet::command::contract_revoke(
&mut owner,
mask1,
PathBuf::from(test_dir),
|api, m| {
revoked = api.contract_revoke(
m,
&ContractRevokeArgsAPI {
tx_id,
src_acct_name: None,
},
)?;
Ok(())
wallet::command::ContractRevokeArgs {
tx_id,
outfile: Some(outfile.clone()),
},
)?;
assert!(
revoked.is_some(),
Path::new(&outfile).is_file(),
"interrupted revoke should resume and produce a self-spend slate"
);
@@ -289,7 +284,7 @@ fn contract_revoke_resume_impl(test_dir: &'static str) -> Result<(), libwallet::
}
#[test]
fn wallet_contract_revoke_resume() -> Result<(), libwallet::Error> {
fn wallet_contract_revoke_resume() -> Result<(), wallet::Error> {
let test_dir = "test_output/contract_revoke_resume";
setup(test_dir);
contract_revoke_resume_impl(test_dir)?;
+2 -1
View File
@@ -13,7 +13,8 @@ API does not require manual confirmation, so a wallet using it still needs to sh
transaction and ask the user before signing.
The CLI exchanges Slatepacks. The last `sign` broadcasts the transaction unless
`--no-broadcast` is used.
`--no-broadcast` is used. `--outfile` overrides the output Slatepack file for `new`, `sign`
and `revoke`.
## Amounts and fees
-1
View File
@@ -41,7 +41,6 @@ Separating side effects until the 'save_step' part would make these functions mu
clearer to callers.
- For a payjoin, `--use-inputs any` picks an input for us; naming the commitment would be
clearer, for the API as much as the CLI.
- Support `--outfile` for contract slatepacks.
- Raise the replacement fee when `revoke` races a transaction already in the mempool.
- Merge invoice-proof retrieval with the existing payment-proof API.
- Add a Grin node API for kernel lookup by MMR index, then use the witness index instead
+15
View File
@@ -512,6 +512,11 @@ subcommands:
short: b
long: ttl_blocks
takes_value: true
- outfile:
help: If present, overrides the filename and location of the output Slatepack file.
short: u
long: outfile
takes_value: true
- sign:
about: Sign a contract
args:
@@ -559,6 +564,11 @@ subcommands:
help: Fee rate to use when first joining, in nanogrin per unit of transaction weight (defaults to accept_fee_base)
long: fee_rate
takes_value: true
- outfile:
help: If present, overrides the filename and location of the output Slatepack file.
short: u
long: outfile
takes_value: true
- no-broadcast:
help: Don't broadcast the transaction if it was finalized
long: no-broadcast
@@ -579,3 +589,8 @@ subcommands:
short: i
long: tx-id
takes_value: true
- outfile:
help: If present, overrides the filename and location of the output Slatepack file.
short: u
long: outfile
takes_value: true
+6 -5
View File
@@ -1048,6 +1048,7 @@ pub fn parse_contract_new_args(
"minimum_confirmations",
)?;
let fee_rate = parse_contract_fee_rate(args)?;
let outfile = parse_optional(args, "outfile")?;
let ttl_blocks = match args.value_of("ttl_blocks") {
Some(value) => {
let blocks = parse_u64(value, "ttl_blocks")?;
@@ -1117,8 +1118,7 @@ pub fn parse_contract_new_args(
minimum_confirmations,
fee_rate,
ttl_blocks,
// Not yet wired to CLI flags:
outfile: None,
outfile,
})
}
@@ -1149,6 +1149,7 @@ pub fn parse_contract_setup_args(
None => None,
};
let fee_rate = parse_contract_fee_rate(args)?;
let outfile = parse_optional(args, "outfile")?;
let no_payjoin = args.is_present("no-payjoin");
let use_inputs = match args.value_of("use-inputs") {
Some(v) => {
@@ -1192,8 +1193,7 @@ pub fn parse_contract_setup_args(
make_outputs: make_outputs,
minimum_confirmations,
fee_rate,
// Not yet wired to CLI flags:
outfile: None,
outfile,
})
}
@@ -1233,8 +1233,9 @@ pub fn parse_contract_revoke_args(
let tx_id = tx_id_str.parse::<u32>().map_err(|e| {
ParseError::ArgumentError(format!("Could not parse tx-id '{}'. e={}", tx_id_str, e))
})?;
let outfile = parse_optional(args, "outfile")?;
Ok(command::ContractRevokeArgs { tx_id: tx_id })
Ok(command::ContractRevokeArgs { tx_id, outfile })
}
pub fn contract_json_output(args: &ArgMatches<'_>) -> bool {
+27 -1
View File
@@ -107,6 +107,7 @@ fn contract_command_test_impl(test_dir: &str) -> Result<(), grin_wallet_controll
let expected_fee = Transaction::weight_by_iok(1, 1, 0) * fee_rate
+ (Transaction::weight_by_iok(0, 0, 1) * fee_rate).div_ceil(2);
let fee_rate = fee_rate.to_string();
let file_name = format!("{}/wallet1/contract.slatepack", test_dir);
// Wallet 1 opens a contract sending 1 grin, leaving an unencrypted slatepack because
// no --encrypt-for was given
@@ -122,11 +123,13 @@ fn contract_command_test_impl(test_dir: &str) -> Result<(), grin_wallet_controll
"1",
"--fee_rate",
&fee_rate,
"--outfile",
&file_name,
];
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
assert!(std::path::Path::new(&file_name).is_file());
// Wallet 2 views the contract before signing it
let file_name = only_slatepack(test_dir, "wallet1");
grin_wallet_controller::controller::owner_single_use(
wallet1.clone(),
mask1,
@@ -284,6 +287,8 @@ fn parses_contract_options() {
"2",
"--ttl_blocks",
"20",
"--outfile",
"new.slatepack",
])
.unwrap();
let contract = args.subcommand_matches("contract").unwrap();
@@ -293,6 +298,7 @@ fn parses_contract_options() {
assert_eq!(parsed.minimum_confirmations, 3);
assert_eq!(parsed.fee_rate, Some(2));
assert_eq!(parsed.ttl_blocks, Some(20));
assert_eq!(parsed.outfile.as_deref(), Some("new.slatepack"));
let expected = grin_wallet_libwallet::contract::types::DEFAULT_MINIMUM_CONFIRMATIONS;
let args = app
@@ -363,6 +369,7 @@ fn parses_contract_options() {
assert_eq!(parsed.fee_rate, None);
let args = app
.clone()
.get_matches_from_safe(vec![
"grin-wallet",
"contract",
@@ -371,6 +378,8 @@ fn parses_contract_options() {
"3",
"--fee_rate",
"2",
"--outfile",
"sign.slatepack",
])
.unwrap();
let contract = args.subcommand_matches("contract").unwrap();
@@ -378,6 +387,23 @@ fn parses_contract_options() {
let parsed = grin_wallet::cmd::wallet_args::parse_contract_setup_args(sign_args).unwrap();
assert_eq!(parsed.minimum_confirmations, Some(3));
assert_eq!(parsed.fee_rate, Some(2));
assert_eq!(parsed.outfile.as_deref(), Some("sign.slatepack"));
let args = app
.get_matches_from_safe(vec![
"grin-wallet",
"contract",
"revoke",
"--tx-id",
"1",
"--outfile",
"revoke.slatepack",
])
.unwrap();
let contract = args.subcommand_matches("contract").unwrap();
let revoke_args = contract.subcommand_matches("revoke").unwrap();
let parsed = grin_wallet::cmd::wallet_args::parse_contract_revoke_args(revoke_args).unwrap();
assert_eq!(parsed.outfile.as_deref(), Some("revoke.slatepack"));
}
#[test]