Skip to content

Commit 4866612

Browse files
feat(dre): top up neuron request (#752)
Co-authored-by: sa-github-api <138766536+sa-github-api@users.noreply.github.com>
1 parent 03d10b3 commit 4866612

File tree

6 files changed

+172
-5
lines changed

6 files changed

+172
-5
lines changed

rs/cli/src/commands/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use get::Get;
1111
use heal::Heal;
1212
use hostos::HostOs;
1313
use ic_management_types::{MinNakamotoCoefficients, Network, NodeFeature};
14+
use neuron::Neuron;
1415
use node_metrics::NodeMetrics;
1516
use nodes::Nodes;
1617
use proposals::Proposals;
@@ -24,7 +25,7 @@ use url::Url;
2425
use version::Version;
2526
use vote::Vote;
2627

27-
use crate::auth::Neuron;
28+
use crate::auth::Neuron as AuthNeuron;
2829

2930
mod api_boundary_nodes;
3031
mod completions;
@@ -33,6 +34,7 @@ mod firewall;
3334
pub mod get;
3435
mod heal;
3536
pub mod hostos;
37+
mod neuron;
3638
mod node_metrics;
3739
mod nodes;
3840
mod proposals;
@@ -144,7 +146,7 @@ macro_rules! impl_executable_command_for_enums {
144146
}
145147
pub(crate) use impl_executable_command_for_enums;
146148

147-
impl_executable_command_for_enums! { DerToPrincipal, Heal, Subnet, Get, Propose, UpdateUnassignedNodes, Version, NodeMetrics, HostOs, Nodes, ApiBoundaryNodes, Vote, Registry, Firewall, Upgrade, Proposals, Completions, Qualify, UpdateAuthorizedSubnets }
149+
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 }
148150

149151
pub trait ExecutableCommand {
150152
fn require_ic_admin(&self) -> IcAdminRequirement;
@@ -224,9 +226,9 @@ pub trait ExecutableCommand {
224226

225227
pub enum IcAdminRequirement {
226228
None,
227-
Anonymous, // for get commands
228-
Detect, // detect the neuron
229-
OverridableBy { network: Network, neuron: Neuron }, // eg automation which we know where is placed
229+
Anonymous, // for get commands
230+
Detect, // detect the neuron
231+
OverridableBy { network: Network, neuron: AuthNeuron }, // eg automation which we know where is placed
230232
}
231233

232234
impl ExecutableCommand for Args {

rs/cli/src/commands/neuron/balance.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use clap::Args;
2+
use ic_canisters::governance::GovernanceCanisterWrapper;
3+
4+
use crate::commands::ExecutableCommand;
5+
6+
#[derive(Args, Debug)]
7+
pub struct Balance {
8+
/// Neuron to query, by default will use the one from configured identity
9+
#[clap(long)]
10+
neuron: Option<u64>,
11+
}
12+
13+
impl ExecutableCommand for Balance {
14+
fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement {
15+
match &self.neuron {
16+
Some(_) => crate::commands::IcAdminRequirement::None,
17+
None => crate::commands::IcAdminRequirement::Detect,
18+
}
19+
}
20+
21+
fn validate(&self, _cmd: &mut clap::Command) {}
22+
23+
async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> {
24+
let governance = GovernanceCanisterWrapper::from(ctx.create_canister_client()?);
25+
let neuron_info = governance
26+
.get_neuron_info(self.neuron.unwrap_or_else(|| ctx.ic_admin().neuron.neuron_id))
27+
.await?;
28+
29+
println!("{}", neuron_info.stake_e8s / 10_u64.pow(8));
30+
Ok(())
31+
}
32+
}

rs/cli/src/commands/neuron/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use clap::Args;
2+
3+
use super::{impl_executable_command_for_enums, ExecutableCommand, IcAdminRequirement};
4+
use crate::commands::neuron::balance::Balance;
5+
use crate::commands::neuron::refresh::Refresh;
6+
use crate::commands::neuron::top_up::TopUp;
7+
8+
mod balance;
9+
mod refresh;
10+
mod top_up;
11+
12+
#[derive(Args, Debug)]
13+
pub struct Neuron {
14+
#[clap(subcommand)]
15+
pub subcommand: Subcommands,
16+
}
17+
18+
impl_executable_command_for_enums! { Balance, TopUp, Refresh }
19+
20+
impl ExecutableCommand for Neuron {
21+
fn require_ic_admin(&self) -> IcAdminRequirement {
22+
self.subcommand.require_ic_admin()
23+
}
24+
25+
fn validate(&self, cmd: &mut Command) {
26+
self.subcommand.validate(cmd)
27+
}
28+
29+
async fn execute(&self, ctx: DreContext) -> anyhow::Result<()> {
30+
self.subcommand.execute(ctx).await
31+
}
32+
}

rs/cli/src/commands/neuron/refresh.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use clap::Args;
2+
use ic_canisters::governance::GovernanceCanisterWrapper;
3+
4+
use crate::commands::ExecutableCommand;
5+
6+
#[derive(Args, Debug)]
7+
pub struct Refresh {}
8+
9+
impl ExecutableCommand for Refresh {
10+
fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement {
11+
crate::commands::IcAdminRequirement::Detect
12+
}
13+
14+
fn validate(&self, _cmd: &mut clap::Command) {}
15+
16+
async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> {
17+
let governance_canister = GovernanceCanisterWrapper::from(ctx.create_canister_client()?);
18+
19+
let resp = governance_canister.refresh_neuron(ctx.ic_admin().neuron.neuron_id).await?;
20+
println!("{:?}", resp);
21+
22+
Ok(())
23+
}
24+
}

rs/cli/src/commands/neuron/top_up.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use clap::Args;
2+
use ic_canisters::governance::GovernanceCanisterWrapper;
3+
use itertools::Itertools;
4+
5+
use crate::commands::ExecutableCommand;
6+
7+
#[derive(Args, Debug)]
8+
pub struct TopUp {}
9+
10+
impl ExecutableCommand for TopUp {
11+
fn require_ic_admin(&self) -> crate::commands::IcAdminRequirement {
12+
crate::commands::IcAdminRequirement::Detect
13+
}
14+
15+
fn validate(&self, _cmd: &mut clap::Command) {}
16+
17+
async fn execute(&self, ctx: crate::ctx::DreContext) -> anyhow::Result<()> {
18+
let governance = GovernanceCanisterWrapper::from(ctx.create_canister_client()?);
19+
let full_neuron = governance.get_full_neuron(ctx.ic_admin().neuron.neuron_id).await?;
20+
let account_hex = full_neuron.account.iter().map(|byte| format!("{:02x}", byte)).join("");
21+
22+
println!("Please request ICP in the #icp-to-go slack channel:");
23+
println!(
24+
"> 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",
25+
account_hex,
26+
ctx.ic_admin().neuron.neuron_id
27+
);
28+
println!("You can check balance by running `dre neuron balance`");
29+
30+
Ok(())
31+
}
32+
}

rs/ic-canisters/src/governance.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ use ic_agent::Agent;
44
use ic_nns_common::pb::v1::NeuronId;
55
use ic_nns_common::pb::v1::ProposalId;
66
use ic_nns_constants::GOVERNANCE_CANISTER_ID;
7+
use ic_nns_governance::pb::v1::manage_neuron::claim_or_refresh::By;
8+
use ic_nns_governance::pb::v1::manage_neuron::ClaimOrRefresh;
9+
use ic_nns_governance::pb::v1::manage_neuron::Command::ClaimOrRefresh as CoR;
710
use ic_nns_governance::pb::v1::manage_neuron::RegisterVote;
11+
use ic_nns_governance::pb::v1::GovernanceError;
812
use ic_nns_governance::pb::v1::ListProposalInfo;
913
use ic_nns_governance::pb::v1::ListProposalInfoResponse;
1014
use ic_nns_governance::pb::v1::ManageNeuron;
1115
use ic_nns_governance::pb::v1::ManageNeuronResponse;
16+
use ic_nns_governance::pb::v1::Neuron;
17+
use ic_nns_governance::pb::v1::NeuronInfo;
1218
use ic_nns_governance::pb::v1::ProposalInfo;
1319
use log::warn;
1420
use serde::{self, Serialize};
@@ -162,6 +168,17 @@ impl GovernanceCanisterWrapper {
162168
}
163169
}
164170

171+
pub async fn refresh_neuron(&self, neuron_id: u64) -> anyhow::Result<ManageNeuronResponse> {
172+
self.manage_neuron(&ManageNeuron {
173+
id: Some(NeuronId { id: neuron_id }),
174+
neuron_id_or_subaccount: None,
175+
command: Some(CoR(ClaimOrRefresh {
176+
by: Some(By::NeuronIdOrSubaccount(ic_nns_governance::pb::v1::Empty {})),
177+
})),
178+
})
179+
.await
180+
}
181+
165182
async fn manage_neuron(&self, manage_neuron: &ManageNeuron) -> anyhow::Result<ManageNeuronResponse> {
166183
match self
167184
.client
@@ -195,4 +212,32 @@ impl GovernanceCanisterWrapper {
195212
Err(e) => Err(anyhow::anyhow!("Error executing query: {}", e)),
196213
}
197214
}
215+
216+
pub async fn get_neuron_info(&self, neuron_id: u64) -> anyhow::Result<NeuronInfo> {
217+
let args = candid::encode_one(neuron_id)?;
218+
match self
219+
.client
220+
.agent
221+
.execute_query(&GOVERNANCE_CANISTER_ID, "get_neuron_info", args)
222+
.await
223+
.map_err(|e| anyhow::anyhow!(e))?
224+
{
225+
Some(response) => Ok(Decode!(response.as_slice(), Result<NeuronInfo, GovernanceError>)?.map_err(|e| anyhow::anyhow!(e))?),
226+
None => Err(anyhow::anyhow!("Didn't find neuron with id {}", neuron_id)),
227+
}
228+
}
229+
230+
pub async fn get_full_neuron(&self, neuron_id: u64) -> anyhow::Result<Neuron> {
231+
let args = candid::encode_one(neuron_id)?;
232+
match self
233+
.client
234+
.agent
235+
.execute_query(&GOVERNANCE_CANISTER_ID, "get_full_neuron", args)
236+
.await
237+
.map_err(|e| anyhow::anyhow!(e))?
238+
{
239+
Some(response) => Ok(Decode!(response.as_slice(), Result<Neuron, GovernanceError>)?.map_err(|e| anyhow::anyhow!(e))?),
240+
None => Err(anyhow::anyhow!("Didn't find neuron with id {}", neuron_id)),
241+
}
242+
}
198243
}

0 commit comments

Comments
 (0)