diff --git a/docs/cli-reference/quill-neuron-manage.md b/docs/cli-reference/quill-neuron-manage.md index c394f818..5e22bda3 100644 --- a/docs/cli-reference/quill-neuron-manage.md +++ b/docs/cli-reference/quill-neuron-manage.md @@ -40,5 +40,5 @@ quill neuron-manage [option] | `--merge-maturity ` | Merge the percentage (between 1 and 100) of the maturity of a neuron into the current stake. | | `--remove-hot-key ` | Principal hot key to be removed. | | `--split ` | Split off the given number of ICP from a neuron. | -| `--register-vote ...` [--reject] | Vote to approve (default) or reject proposal(s). | +| `--register-vote (\|-)...` [--reject] | Vote to approve (default) or reject proposal(s). | diff --git a/src/commands/neuron_manage.rs b/src/commands/neuron_manage.rs index 8912505d..8234d73b 100644 --- a/src/commands/neuron_manage.rs +++ b/src/commands/neuron_manage.rs @@ -13,6 +13,7 @@ use ledger_canister::Tokens; pub const ONE_DAY_SECONDS: u32 = 24 * 60 * 60; pub const ONE_YEAR_SECONDS: u32 = (4 * 365 + 1) * ONE_DAY_SECONDS / 4; pub const ONE_MONTH_SECONDS: u32 = ONE_YEAR_SECONDS / 12; +pub const RANGE_LIMIT: usize = 100; #[derive(CandidType)] pub struct IncreaseDissolveDelay { @@ -202,7 +203,7 @@ pub struct ManageOpts { /// Vote on proposal(s) (approve by default). #[clap(long, multiple_values(true))] - register_vote: Option>, + register_vote: Option>, /// Reject proposal(s). #[clap(long)] @@ -407,15 +408,26 @@ pub fn exec(auth: &AuthInfo, opts: ManageOpts) -> AnyhowResult() { + proposals.push(proposal); + } else { + return Err(anyhow!("Unable to parse proposal or range.")); + } + for proposal in proposals { + let args = Encode!(&ManageNeuron { + id, + command: Some(Command::RegisterVote(RegisterVote { + vote: if opts.reject { 2 } else { 1 }, + proposal: Some(ProposalId { id: proposal }), + })), + neuron_id_or_subaccount: None, + })?; + msgs.push(args); + } } }; @@ -457,3 +469,60 @@ fn parse_neuron_id(id: String) -> AnyhowResult { .parse() .context("Failed to parse the neuron id") } + +// Get the range first..last from a string of the form X-Y +// of the form 1234-5 = 1234..1245, 1234-45 = 1234-1245, etc. where +// the string Y is a new suffix overwriting the end of X. +// Empty ranges are an error as are any points which do not parse as u64 as +// are any ranges not less than RANGE_LIMIT. +// See test_get_range() for additional examples. +fn get_range(range: &str) -> AnyhowResult<(u64, u64)> { + let pieces: Vec<&str> = range.split('-').collect(); + if pieces.len() == 2 { + if let Ok(first) = pieces[0].parse::() { + let mut last = pieces[0].to_string(); + last.replace_range( + pieces[0] + .chars() + .count() + .saturating_sub(pieces[1].chars().count()).., + pieces[1], + ); + if let Ok(last) = last.parse::() { + if last >= first && ((last - first) as usize) < RANGE_LIMIT { + return Ok((first, last)); + } + } + } + } + return Err(anyhow!( + "Proposal ranges must be of the form START-ENDSUFFIX (e.g. 123-31) and shorter than {}.", + RANGE_LIMIT + )); +} + +#[test] +fn test_get_range() { + assert!(get_range("1").is_err()); + assert!(get_range("ABCDE").is_err()); + assert!(get_range("1-A").is_err()); + assert!(get_range("2-1").is_err()); + assert!(get_range("12-10").is_err()); + assert!(get_range("12-1").is_err()); + assert!(get_range("10000-99999").is_err()); + + assert_eq!(get_range("1-1").ok(), Some((1, 1))); + assert_eq!(get_range("1-2").ok(), Some((1, 2))); + assert_eq!(get_range("1-29").ok(), Some((1, 29))); + + assert_eq!(get_range("10-10").ok(), Some((10, 10))); + assert_eq!(get_range("10-2").ok(), Some((10, 12))); + assert_eq!(get_range("10-12").ok(), Some((10, 12))); + assert_eq!(get_range("10-22").ok(), Some((10, 22))); + + assert_eq!(get_range("777-8").ok(), Some((777, 778))); + assert_eq!(get_range("777-88").ok(), Some((777, 788))); + assert_eq!(get_range("777-788").ok(), Some((777, 788))); + assert_eq!(get_range("777-783").ok(), Some((777, 783))); + assert_eq!(get_range("999-1001").ok(), Some((999, 1001))); +} diff --git a/tests/commands/neuron-manage-vote-range.sh b/tests/commands/neuron-manage-vote-range.sh new file mode 100755 index 00000000..84e10350 --- /dev/null +++ b/tests/commands/neuron-manage-vote-range.sh @@ -0,0 +1 @@ +${CARGO_TARGET_DIR:-../target}/debug/quill neuron-manage 2313380519530470538 --register-vote 349-51 --pem-file - | ${CARGO_TARGET_DIR:-../target}/debug/quill send --dry-run - diff --git a/tests/outputs/neuron-manage-vote-range.txt b/tests/outputs/neuron-manage-vote-range.txt new file mode 100644 index 00000000..e255e25d --- /dev/null +++ b/tests/outputs/neuron-manage-vote-range.txt @@ -0,0 +1,54 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + RegisterVote = record { + vote = 1 : int32; + proposal = opt record { id = 349 : nat64 }; + } + }; + neuron_id_or_subaccount = null; + }, +) +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + RegisterVote = record { + vote = 1 : int32; + proposal = opt record { id = 350 : nat64 }; + } + }; + neuron_id_or_subaccount = null; + }, +) +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + RegisterVote = record { + vote = 1 : int32; + proposal = opt record { id = 351 : nat64 }; + } + }; + neuron_id_or_subaccount = null; + }, +)