@@ -333,91 +333,189 @@ fn sign_transaction_single_sig_standard(
333
333
. ok_or ( "TX did not finish signing -- was this a standard single signature transaction?" ) ?)
334
334
}
335
335
336
+ /// Counts how many times a specific flag appears in the argument list.
337
+ ///
338
+ /// # Arguments
339
+ ///
340
+ /// * `args` - A reference to a vector of argument strings.
341
+ /// * `flag` - The flag to count occurrences of.
342
+ ///
343
+ /// # Returns
344
+ ///
345
+ /// The number of times `flag` appears in `args`.
346
+ fn count_flag ( args : & Vec < String > , flag : & str ) -> usize {
347
+ args. iter ( ) . filter ( |& arg| arg == flag) . count ( )
348
+ }
349
+
350
+ /// Removes the first occurrence of a flag from the argument list.
351
+ ///
352
+ /// # Arguments
353
+ ///
354
+ /// * `args` - A mutable reference to a vector of argument strings.
355
+ /// * `flag` - The flag to remove.
356
+ ///
357
+ /// # Returns
358
+ ///
359
+ /// `true` if the flag was found and removed; `false` otherwise.
360
+ fn extract_flag ( args : & mut Vec < String > , flag : & str ) -> bool {
361
+ args. iter ( )
362
+ . position ( |arg| arg == flag)
363
+ . map ( |flag_index| {
364
+ args. remove ( flag_index) ;
365
+ } )
366
+ . is_some ( )
367
+ }
368
+
369
+ /// Removes a flag and its following value from the argument list.
370
+ ///
371
+ /// # Arguments
372
+ ///
373
+ /// * `args` - A mutable reference to a vector of argument strings.
374
+ /// * `flag` - The flag whose value to extract and remove.
375
+ ///
376
+ /// # Returns
377
+ ///
378
+ /// An `Option<String>` containing the value following the flag if both were found and removed;
379
+ /// returns `None` if the flag was not found or no value follows the flag.
380
+ fn extract_flag_with_value ( args : & mut Vec < String > , flag : & str ) -> Option < String > {
381
+ args. iter ( )
382
+ . position ( |arg| arg == flag)
383
+ . and_then ( |flag_index| {
384
+ if flag_index + 1 < args. len ( ) {
385
+ let value = args. remove ( flag_index + 1 ) ;
386
+ args. remove ( flag_index) ;
387
+ Some ( value)
388
+ } else {
389
+ None
390
+ }
391
+ } )
392
+ }
393
+
394
+ /// Parses anchor mode flags from the CLI arguments.
395
+ ///
396
+ /// This function checks for the presence of `--microblock-only` and `--block-only` flags
397
+ /// in the provided `args` vector, and returns the corresponding `TransactionAnchorMode`.
398
+ ///
399
+ /// The user may specify **at most one** of these flags:
400
+ /// - `--microblock-only` maps to `TransactionAnchorMode::OffChainOnly`
401
+ /// - `--block-only` maps to `TransactionAnchorMode::OnChainOnly`
402
+ ///
403
+ /// If **neither flag is provided**, the default mode `TransactionAnchorMode::Any` is returned.
404
+ ///
405
+ /// Both flags are removed from the `args` vector if present.
406
+ ///
407
+ /// # Arguments
408
+ ///
409
+ /// * `args` - A mutable reference to a vector of CLI arguments.
410
+ /// * `usage` - A usage string displayed in error messages.
411
+ ///
412
+ /// # Returns
413
+ ///
414
+ /// * `Ok(TransactionAnchorMode)` - On successful parsing or if no anchor mode is specified.
415
+ /// * `Err(CliError)` - If either flag is duplicated, or if both are present simultaneously.
416
+ ///
417
+ /// # Errors
418
+ ///
419
+ /// Returns a `CliError::Message` if:
420
+ /// - `--microblock-only` or `--block-only` appears more than once.
421
+ /// - Both flags are specified together (mutually exclusive).
422
+ ///
423
+ /// # Side Effects
424
+ ///
425
+ /// Removes `--microblock-only` or `--block-only` from the `args` vector if found.
336
426
fn parse_anchor_mode (
337
427
args : & mut Vec < String > ,
338
428
usage : & str ,
339
429
) -> Result < TransactionAnchorMode , CliError > {
340
- let num_args = args. len ( ) ;
341
- let mut offchain_only = false ;
342
- let mut onchain_only = false ;
343
- let mut idx = 0 ;
344
- for i in 0 ..num_args {
345
- if args[ i] == "--microblock-only" {
346
- if idx > 0 {
347
- return Err ( CliError :: Message ( format ! (
348
- "Multiple anchor modes detected.\n \n USAGE:\n {}" ,
349
- usage,
350
- ) ) ) ;
351
- }
430
+ const FLAG_MICROBLOCK : & str = "--microblock-only" ;
431
+ const FLAG_BLOCK : & str = "--block-only" ;
352
432
353
- offchain_only = true ;
354
- idx = i;
355
- }
356
- if args[ i] == "--block-only" {
357
- if idx > 0 {
358
- return Err ( CliError :: Message ( format ! (
359
- "Multiple anchor modes detected.\n \n USAGE:\n {}" ,
360
- usage,
361
- ) ) ) ;
362
- }
433
+ let count_micro = count_flag ( args, FLAG_MICROBLOCK ) ;
434
+ let count_block = count_flag ( args, FLAG_BLOCK ) ;
363
435
364
- onchain_only = true ;
365
- idx = i;
366
- }
367
- }
368
- if idx > 0 {
369
- args. remove ( idx) ;
436
+ if count_micro > 1 || count_block > 1 {
437
+ return Err ( CliError :: Message ( format ! (
438
+ "Duplicated anchor mode detected.\n \n USAGE:\n {}" ,
439
+ usage,
440
+ ) ) ) ;
370
441
}
371
- if onchain_only {
372
- Ok ( TransactionAnchorMode :: OnChainOnly )
373
- } else if offchain_only {
374
- Ok ( TransactionAnchorMode :: OffChainOnly )
375
- } else {
376
- Ok ( TransactionAnchorMode :: Any )
442
+
443
+ let has_microblock = extract_flag ( args, FLAG_MICROBLOCK ) ;
444
+ let has_block = extract_flag ( args, FLAG_BLOCK ) ;
445
+
446
+ match ( has_microblock, has_block) {
447
+ ( true , true ) => Err ( CliError :: Message ( format ! (
448
+ "Both anchor modes detected.\n \n USAGE:\n {}" ,
449
+ usage
450
+ ) ) ) ,
451
+ ( true , false ) => Ok ( TransactionAnchorMode :: OffChainOnly ) ,
452
+ ( false , true ) => Ok ( TransactionAnchorMode :: OnChainOnly ) ,
453
+ ( false , false ) => Ok ( TransactionAnchorMode :: Any ) ,
377
454
}
378
455
}
379
456
457
+ /// Parses the `--postcondition-mode` flag from the CLI arguments.
458
+ ///
459
+ /// This function looks for the `--postcondition-mode` flag in the provided `args` vector
460
+ /// and extracts its associated value. The flag must be specified at most once, and the value
461
+ /// must be either `"allow"` or `"deny"`. If the flag is not present, the default mode
462
+ /// `TransactionPostConditionMode::Deny` is returned.
463
+ ///
464
+ /// The flag and its value are removed from `args` if found.
465
+ ///
466
+ /// # Arguments
467
+ ///
468
+ /// * `args` - A mutable reference to a vector of CLI arguments.
469
+ /// * `usage` - A usage string that is displayed in error messages.
470
+ ///
471
+ /// # Returns
472
+ ///
473
+ /// * `Ok(TransactionPostConditionMode)` - If the flag is parsed successfully or not present (defaults to `Deny`).
474
+ /// * `Err(CliError)` - If the flag is duplicated, missing a value, or contains an invalid value.
475
+ ///
476
+ /// # Errors
477
+ ///
478
+ /// Returns a `CliError::Message` if:
479
+ /// - The flag appears more than once.
480
+ /// - The flag is present but missing a value.
481
+ /// - The flag value is not `"allow"` or `"deny"`.
482
+ ///
483
+ /// # Side Effects
484
+ ///
485
+ /// This function modifies the `args` vector by removing the parsed flag and its value if found.
380
486
fn parse_postcondition_mode (
381
487
args : & mut Vec < String > ,
382
488
usage : & str ,
383
489
) -> Result < TransactionPostConditionMode , CliError > {
384
- let mut i = 0 ;
385
- let mut value = None ;
386
- while i < args. len ( ) {
387
- if args[ i] == "--postcondition-mode" {
388
- if value. is_some ( ) {
389
- return Err ( CliError :: Message ( format ! (
390
- "Duplicated `--postcondition-mode`.\n \n USAGE:\n {}" ,
391
- usage
392
- ) ) ) ;
393
- }
394
- if i + 1 >= args. len ( ) {
395
- return Err ( CliError :: Message ( format ! (
396
- "Missing value for `--postcondition-mode`.\n \n USAGE:\n {}" ,
397
- usage
398
- ) ) ) ;
399
- }
400
- value = Some ( args. remove ( i + 1 ) ) ;
401
- args. remove ( i) ;
402
- continue ; // do not increment i since elements shifted
490
+ const FLAG_POSTCONDITION : & str = "--postcondition-mode" ;
491
+ const VALUE_ALLOW : & str = "allow" ;
492
+ const VALUE_DENY : & str = "deny" ;
493
+
494
+ match count_flag ( args, FLAG_POSTCONDITION ) {
495
+ 0 => return Ok ( TransactionPostConditionMode :: Deny ) ,
496
+ 1 => { /* continue below */ }
497
+ _ => {
498
+ return Err ( CliError :: Message ( format ! (
499
+ "Duplicated `{}`.\n \n USAGE:\n {}" ,
500
+ FLAG_POSTCONDITION , usage
501
+ ) ) ) ;
403
502
}
404
- i += 1 ;
405
503
}
406
504
407
- let mode = match value {
408
- Some ( mode_str) => match mode_str. as_ref ( ) {
409
- "allow" => TransactionPostConditionMode :: Allow ,
410
- "deny" => TransactionPostConditionMode :: Deny ,
411
- _ => {
412
- return Err ( CliError :: Message ( format ! (
413
- "Invalid value for `--postcondition-mode`.\n \n USAGE:\n {}" ,
414
- usage,
415
- ) ) )
416
- }
505
+ match extract_flag_with_value ( args, FLAG_POSTCONDITION ) {
506
+ Some ( value) => match value. as_str ( ) {
507
+ VALUE_ALLOW => Ok ( TransactionPostConditionMode :: Allow ) ,
508
+ VALUE_DENY => Ok ( TransactionPostConditionMode :: Deny ) ,
509
+ _ => Err ( CliError :: Message ( format ! (
510
+ "Invalid value for `{}`.\n \n USAGE:\n {}" ,
511
+ FLAG_POSTCONDITION , usage
512
+ ) ) ) ,
417
513
} ,
418
- None => TransactionPostConditionMode :: Deny ,
419
- } ;
420
- Ok ( mode)
514
+ None => Err ( CliError :: Message ( format ! (
515
+ "Missing value for `{}`.\n \n USAGE:\n {}" ,
516
+ FLAG_POSTCONDITION , usage
517
+ ) ) ) ,
518
+ }
421
519
}
422
520
423
521
fn handle_contract_publish (
@@ -1106,7 +1204,28 @@ mod test {
1106
1204
1107
1205
let exp_err_msg = format ! (
1108
1206
"{}\n \n USAGE:\n {}" ,
1109
- "Multiple anchor modes detected." , PUBLISH_USAGE
1207
+ "Both anchor modes detected." , PUBLISH_USAGE
1208
+ ) ;
1209
+ assert_eq ! ( exp_err_msg, result. unwrap_err( ) . to_string( ) ) ;
1210
+
1211
+ // Scenario FAIL using duplicated anchor mode
1212
+ let publish_args = [
1213
+ "publish" ,
1214
+ "043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3" ,
1215
+ "1" ,
1216
+ "0" ,
1217
+ "foo-contract" ,
1218
+ & contract_path,
1219
+ "--microblock-only" ,
1220
+ "--microblock-only" ,
1221
+ ] ;
1222
+
1223
+ let result = main_handler ( to_string_vec ( & publish_args) ) ;
1224
+ assert ! ( result. is_err( ) ) ;
1225
+
1226
+ let exp_err_msg = format ! (
1227
+ "{}\n \n USAGE:\n {}" ,
1228
+ "Duplicated anchor mode detected." , PUBLISH_USAGE
1110
1229
) ;
1111
1230
assert_eq ! ( exp_err_msg, result. unwrap_err( ) . to_string( ) ) ;
1112
1231
}
0 commit comments