From b5f87774184d79fb9a78b81afe7590f2f65c46f3 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 19 Aug 2024 10:51:22 +0200 Subject: [PATCH 1/5] adding balance command --- rs/cli/src/commands/mod.rs | 12 +++++----- rs/cli/src/commands/neuron/balance.rs | 32 +++++++++++++++++++++++++++ rs/cli/src/commands/neuron/mod.rs | 29 ++++++++++++++++++++++++ rs/cli/src/commands/neuron/top_up.rs | 1 + rs/ic-canisters/src/governance.rs | 16 ++++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 rs/cli/src/commands/neuron/balance.rs create mode 100644 rs/cli/src/commands/neuron/mod.rs create mode 100644 rs/cli/src/commands/neuron/top_up.rs diff --git a/rs/cli/src/commands/mod.rs b/rs/cli/src/commands/mod.rs index 3540e3620..9f00e865d 100644 --- a/rs/cli/src/commands/mod.rs +++ b/rs/cli/src/commands/mod.rs @@ -11,6 +11,7 @@ use get::Get; use heal::Heal; use hostos::HostOs; use ic_management_types::{MinNakamotoCoefficients, Network, NodeFeature}; +use neuron::Neuron; use node_metrics::NodeMetrics; use nodes::Nodes; use proposals::Proposals; @@ -24,7 +25,7 @@ use url::Url; use version::Version; use vote::Vote; -use crate::auth::Neuron; +use crate::auth::Neuron as AuthNeuron; mod api_boundary_nodes; mod completions; @@ -33,6 +34,7 @@ mod firewall; pub mod get; mod heal; pub mod hostos; +mod neuron; mod node_metrics; mod nodes; mod proposals; @@ -144,7 +146,7 @@ macro_rules! impl_executable_command_for_enums { } pub(crate) use impl_executable_command_for_enums; -impl_executable_command_for_enums! { DerToPrincipal, Heal, Subnet, Get, Propose, UpdateUnassignedNodes, Version, NodeMetrics, HostOs, Nodes, ApiBoundaryNodes, Vote, Registry, Firewall, Upgrade, Proposals, Completions, Qualify, UpdateAuthorizedSubnets } +impl_executable_command_for_enums! { DerToPrincipal, Heal, Subnet, Get, Propose, UpdateUnassignedNodes, Version, NodeMetrics, HostOs, Nodes, ApiBoundaryNodes, Vote, Registry, Firewall, Upgrade, Proposals, Completions, Qualify, UpdateAuthorizedSubnets, Neuron } pub trait ExecutableCommand { fn require_ic_admin(&self) -> IcAdminRequirement; @@ -224,9 +226,9 @@ pub trait ExecutableCommand { pub enum IcAdminRequirement { None, - Anonymous, // for get commands - Detect, // detect the neuron - OverridableBy { network: Network, neuron: Neuron }, // eg automation which we know where is placed + Anonymous, // for get commands + Detect, // detect the neuron + OverridableBy { network: Network, neuron: AuthNeuron }, // eg automation which we know where is placed } impl ExecutableCommand for Args { diff --git a/rs/cli/src/commands/neuron/balance.rs b/rs/cli/src/commands/neuron/balance.rs new file mode 100644 index 000000000..d7ffea14e --- /dev/null +++ b/rs/cli/src/commands/neuron/balance.rs @@ -0,0 +1,32 @@ +use clap::Args; +use ic_canisters::governance::GovernanceCanisterWrapper; + +use crate::commands::ExecutableCommand; + +#[derive(Args, Debug)] +pub struct Balance { + /// Neuron to query, by default will use the one from configured identity + #[clap(long)] + neuron: Option, +} + +impl ExecutableCommand for Balance { + fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement { + match &self.neuron { + Some(_) => crate::commands::IcAdminRequirement::None, + None => crate::commands::IcAdminRequirement::Detect, + } + } + + fn validate(&self, _cmd: &mut clap::Command) {} + + async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> { + let governance = GovernanceCanisterWrapper::from(ctx.create_canister_client()?); + let neuron_info = governance + .get_neuron_info(self.neuron.unwrap_or_else(|| ctx.ic_admin().neuron.neuron_id)) + .await?; + + println!("{}", neuron_info.stake_e8s / 10_u64.pow(8)); + Ok(()) + } +} diff --git a/rs/cli/src/commands/neuron/mod.rs b/rs/cli/src/commands/neuron/mod.rs new file mode 100644 index 000000000..5ed7fe773 --- /dev/null +++ b/rs/cli/src/commands/neuron/mod.rs @@ -0,0 +1,29 @@ +use clap::Args; + +use super::{impl_executable_command_for_enums, ExecutableCommand, IcAdminRequirement}; +use crate::commands::neuron::balance::Balance; + +mod balance; +mod top_up; + +#[derive(Args, Debug)] +pub struct Neuron { + #[clap(subcommand)] + pub subcommand: Subcommands, +} + +impl_executable_command_for_enums! { Balance } + +impl ExecutableCommand for Neuron { + fn require_ic_admin(&self) -> IcAdminRequirement { + self.subcommand.require_ic_admin() + } + + fn validate(&self, cmd: &mut Command) { + self.subcommand.validate(cmd) + } + + async fn execute(&self, ctx: DreContext) -> anyhow::Result<()> { + self.subcommand.execute(ctx).await + } +} diff --git a/rs/cli/src/commands/neuron/top_up.rs b/rs/cli/src/commands/neuron/top_up.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/rs/cli/src/commands/neuron/top_up.rs @@ -0,0 +1 @@ + diff --git a/rs/ic-canisters/src/governance.rs b/rs/ic-canisters/src/governance.rs index f5fc4ea8a..357242f1a 100644 --- a/rs/ic-canisters/src/governance.rs +++ b/rs/ic-canisters/src/governance.rs @@ -5,10 +5,12 @@ use ic_nns_common::pb::v1::NeuronId; use ic_nns_common::pb::v1::ProposalId; use ic_nns_constants::GOVERNANCE_CANISTER_ID; use ic_nns_governance::pb::v1::manage_neuron::RegisterVote; +use ic_nns_governance::pb::v1::GovernanceError; use ic_nns_governance::pb::v1::ListProposalInfo; use ic_nns_governance::pb::v1::ListProposalInfoResponse; use ic_nns_governance::pb::v1::ManageNeuron; use ic_nns_governance::pb::v1::ManageNeuronResponse; +use ic_nns_governance::pb::v1::NeuronInfo; use ic_nns_governance::pb::v1::ProposalInfo; use log::warn; use serde::{self, Serialize}; @@ -195,4 +197,18 @@ impl GovernanceCanisterWrapper { Err(e) => Err(anyhow::anyhow!("Error executing query: {}", e)), } } + + pub async fn get_neuron_info(&self, neuron_id: u64) -> anyhow::Result { + let args = candid::encode_one(&neuron_id)?; + match self + .client + .agent + .execute_query(&GOVERNANCE_CANISTER_ID, "get_neuron_info", args) + .await + .map_err(|e| anyhow::anyhow!(e))? + { + Some(response) => Ok(Decode!(response.as_slice(), Result)?.map_err(|e| anyhow::anyhow!(e))?), + None => Err(anyhow::anyhow!("Didn't find neuron with id {}", neuron_id)), + } + } } From 124dff6af2db9bf29fe5bf7b7ade07544b6e418d Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 19 Aug 2024 11:36:30 +0200 Subject: [PATCH 2/5] getting account hex --- rs/cli/src/commands/neuron/mod.rs | 3 ++- rs/cli/src/commands/neuron/top_up.rs | 22 ++++++++++++++++++++++ rs/ic-canisters/src/governance.rs | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rs/cli/src/commands/neuron/mod.rs b/rs/cli/src/commands/neuron/mod.rs index 5ed7fe773..3efbce06d 100644 --- a/rs/cli/src/commands/neuron/mod.rs +++ b/rs/cli/src/commands/neuron/mod.rs @@ -2,6 +2,7 @@ use clap::Args; use super::{impl_executable_command_for_enums, ExecutableCommand, IcAdminRequirement}; use crate::commands::neuron::balance::Balance; +use crate::commands::neuron::top_up::TopUp; mod balance; mod top_up; @@ -12,7 +13,7 @@ pub struct Neuron { pub subcommand: Subcommands, } -impl_executable_command_for_enums! { Balance } +impl_executable_command_for_enums! { Balance, TopUp } impl ExecutableCommand for Neuron { fn require_ic_admin(&self) -> IcAdminRequirement { diff --git a/rs/cli/src/commands/neuron/top_up.rs b/rs/cli/src/commands/neuron/top_up.rs index 8b1378917..cb9c9e5c2 100644 --- a/rs/cli/src/commands/neuron/top_up.rs +++ b/rs/cli/src/commands/neuron/top_up.rs @@ -1 +1,23 @@ +use clap::Args; +use ic_canisters::governance::GovernanceCanisterWrapper; +use crate::commands::ExecutableCommand; + +#[derive(Args, Debug)] +pub struct TopUp {} + +impl ExecutableCommand for TopUp { + fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement { + crate::commands::IcAdminRequirement::Detect + } + + fn validate(&self, _cmd: &mut clap::Command) {} + + async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> { + let governance = GovernanceCanisterWrapper::from(ctx.create_canister_client()?); + let full_neuron = governance.get_full_neuron(ctx.ic_admin().neuron.neuron_id).await?; + let account_hex = full_neuron.account.iter().map(|byte| format!("{:02x}", byte)).collect::(); + + Ok(()) + } +} diff --git a/rs/ic-canisters/src/governance.rs b/rs/ic-canisters/src/governance.rs index 357242f1a..8a696285b 100644 --- a/rs/ic-canisters/src/governance.rs +++ b/rs/ic-canisters/src/governance.rs @@ -10,6 +10,7 @@ use ic_nns_governance::pb::v1::ListProposalInfo; use ic_nns_governance::pb::v1::ListProposalInfoResponse; use ic_nns_governance::pb::v1::ManageNeuron; use ic_nns_governance::pb::v1::ManageNeuronResponse; +use ic_nns_governance::pb::v1::Neuron; use ic_nns_governance::pb::v1::NeuronInfo; use ic_nns_governance::pb::v1::ProposalInfo; use log::warn; @@ -211,4 +212,18 @@ impl GovernanceCanisterWrapper { None => Err(anyhow::anyhow!("Didn't find neuron with id {}", neuron_id)), } } + + pub async fn get_full_neuron(&self, neuron_id: u64) -> anyhow::Result { + let args = candid::encode_one(&neuron_id)?; + match self + .client + .agent + .execute_query(&GOVERNANCE_CANISTER_ID, "get_full_neuron", args) + .await + .map_err(|e| anyhow::anyhow!(e))? + { + Some(response) => Ok(Decode!(response.as_slice(), Result)?.map_err(|e| anyhow::anyhow!(e))?), + None => Err(anyhow::anyhow!("Didn't find neuron with id {}", neuron_id)), + } + } } From 10698815e0b947b18ad9a4b8acfb9ac120846ef8 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 19 Aug 2024 11:39:57 +0200 Subject: [PATCH 3/5] implementing topup request command --- rs/cli/src/commands/neuron/top_up.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rs/cli/src/commands/neuron/top_up.rs b/rs/cli/src/commands/neuron/top_up.rs index cb9c9e5c2..0b1ef3f15 100644 --- a/rs/cli/src/commands/neuron/top_up.rs +++ b/rs/cli/src/commands/neuron/top_up.rs @@ -18,6 +18,14 @@ impl ExecutableCommand for TopUp { let full_neuron = governance.get_full_neuron(ctx.ic_admin().neuron.neuron_id).await?; let account_hex = full_neuron.account.iter().map(|byte| format!("{:02x}", byte)).collect::(); + println!("Please request ICP in the #icp-to-go slack channel:"); + println!( + "> Hi! Can I please get XX ICPs on the account address `{}` for neuron ID {} in order to be able to submit more NNS proposals. Thank you\n", + account_hex, + ctx.ic_admin().neuron.neuron_id + ); + println!("You can check balance by running `dre neuron balance`"); + Ok(()) } } From 1d20e6955fb69601b48469a789cac395892403c0 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 19 Aug 2024 13:03:11 +0200 Subject: [PATCH 4/5] implementing refresh --- rs/cli/src/commands/neuron/mod.rs | 4 +++- rs/cli/src/commands/neuron/refresh.rs | 24 ++++++++++++++++++++++++ rs/ic-canisters/src/governance.rs | 14 ++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 rs/cli/src/commands/neuron/refresh.rs diff --git a/rs/cli/src/commands/neuron/mod.rs b/rs/cli/src/commands/neuron/mod.rs index 3efbce06d..2f72da54f 100644 --- a/rs/cli/src/commands/neuron/mod.rs +++ b/rs/cli/src/commands/neuron/mod.rs @@ -2,9 +2,11 @@ use clap::Args; use super::{impl_executable_command_for_enums, ExecutableCommand, IcAdminRequirement}; use crate::commands::neuron::balance::Balance; +use crate::commands::neuron::refresh::Refresh; use crate::commands::neuron::top_up::TopUp; mod balance; +mod refresh; mod top_up; #[derive(Args, Debug)] @@ -13,7 +15,7 @@ pub struct Neuron { pub subcommand: Subcommands, } -impl_executable_command_for_enums! { Balance, TopUp } +impl_executable_command_for_enums! { Balance, TopUp, Refresh } impl ExecutableCommand for Neuron { fn require_ic_admin(&self) -> IcAdminRequirement { diff --git a/rs/cli/src/commands/neuron/refresh.rs b/rs/cli/src/commands/neuron/refresh.rs new file mode 100644 index 000000000..ddb8aa037 --- /dev/null +++ b/rs/cli/src/commands/neuron/refresh.rs @@ -0,0 +1,24 @@ +use clap::Args; +use ic_canisters::governance::GovernanceCanisterWrapper; + +use crate::commands::ExecutableCommand; + +#[derive(Args, Debug)] +pub struct Refresh {} + +impl ExecutableCommand for Refresh { + fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement { + crate::commands::IcAdminRequirement::Detect + } + + fn validate(&self, _cmd: &mut clap::Command) {} + + async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> { + let governance_canister = GovernanceCanisterWrapper::from(ctx.create_canister_client()?); + + let resp = governance_canister.refresh_neuron(ctx.ic_admin().neuron.neuron_id).await?; + println!("{:?}", resp); + + Ok(()) + } +} diff --git a/rs/ic-canisters/src/governance.rs b/rs/ic-canisters/src/governance.rs index 8a696285b..8db6b7b4c 100644 --- a/rs/ic-canisters/src/governance.rs +++ b/rs/ic-canisters/src/governance.rs @@ -4,6 +4,9 @@ use ic_agent::Agent; use ic_nns_common::pb::v1::NeuronId; use ic_nns_common::pb::v1::ProposalId; use ic_nns_constants::GOVERNANCE_CANISTER_ID; +use ic_nns_governance::pb::v1::manage_neuron::claim_or_refresh::By; +use ic_nns_governance::pb::v1::manage_neuron::ClaimOrRefresh; +use ic_nns_governance::pb::v1::manage_neuron::Command::ClaimOrRefresh as CoR; use ic_nns_governance::pb::v1::manage_neuron::RegisterVote; use ic_nns_governance::pb::v1::GovernanceError; use ic_nns_governance::pb::v1::ListProposalInfo; @@ -165,6 +168,17 @@ impl GovernanceCanisterWrapper { } } + pub async fn refresh_neuron(&self, neuron_id: u64) -> anyhow::Result { + self.manage_neuron(&ManageNeuron { + id: Some(NeuronId { id: neuron_id }), + neuron_id_or_subaccount: None, + command: Some(CoR(ClaimOrRefresh { + by: Some(By::NeuronIdOrSubaccount(ic_nns_governance::pb::v1::Empty {})), + })), + }) + .await + } + async fn manage_neuron(&self, manage_neuron: &ManageNeuron) -> anyhow::Result { match self .client From 5de65fc55946b4719e7c631c13d4293996bfb860 Mon Sep 17 00:00:00 2001 From: nikolamilosa Date: Mon, 19 Aug 2024 13:09:25 +0200 Subject: [PATCH 5/5] adding refresh --- rs/cli/src/commands/neuron/top_up.rs | 3 ++- rs/ic-canisters/src/governance.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rs/cli/src/commands/neuron/top_up.rs b/rs/cli/src/commands/neuron/top_up.rs index 0b1ef3f15..2f448e39f 100644 --- a/rs/cli/src/commands/neuron/top_up.rs +++ b/rs/cli/src/commands/neuron/top_up.rs @@ -1,5 +1,6 @@ use clap::Args; use ic_canisters::governance::GovernanceCanisterWrapper; +use itertools::Itertools; use crate::commands::ExecutableCommand; @@ -16,7 +17,7 @@ impl ExecutableCommand for TopUp { async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> { let governance = GovernanceCanisterWrapper::from(ctx.create_canister_client()?); let full_neuron = governance.get_full_neuron(ctx.ic_admin().neuron.neuron_id).await?; - let account_hex = full_neuron.account.iter().map(|byte| format!("{:02x}", byte)).collect::(); + let account_hex = full_neuron.account.iter().map(|byte| format!("{:02x}", byte)).join(""); println!("Please request ICP in the #icp-to-go slack channel:"); println!( diff --git a/rs/ic-canisters/src/governance.rs b/rs/ic-canisters/src/governance.rs index 8db6b7b4c..aab225476 100644 --- a/rs/ic-canisters/src/governance.rs +++ b/rs/ic-canisters/src/governance.rs @@ -214,7 +214,7 @@ impl GovernanceCanisterWrapper { } pub async fn get_neuron_info(&self, neuron_id: u64) -> anyhow::Result { - let args = candid::encode_one(&neuron_id)?; + let args = candid::encode_one(neuron_id)?; match self .client .agent @@ -228,7 +228,7 @@ impl GovernanceCanisterWrapper { } pub async fn get_full_neuron(&self, neuron_id: u64) -> anyhow::Result { - let args = candid::encode_one(&neuron_id)?; + let args = candid::encode_one(neuron_id)?; match self .client .agent