Skip to content

Commit

Permalink
Add ability to top up neurons for submitting proposals (#5712)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Apr 22, 2024
1 parent f8a6449 commit 6cc9fd1
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/canisters/proposals_bot/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Add `block_level_markdown` flag to messages ([#5680](https://github.com/open-chat-labs/open-chat/pull/5680))
- Add ability to top up neurons for submitting proposals ([#5712](https://github.com/open-chat-labs/open-chat/pull/5712))

## [[2.0.1124](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1124-proposals_bot)] - 2024-03-26

Expand Down
1 change: 1 addition & 0 deletions backend/canisters/proposals_bot/api/src/updates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod appoint_admins;
pub mod c2c_submit_proposal;
pub mod import_proposals_group_into_community;
pub mod stake_neuron_for_submitting_proposals;
pub mod top_up_neuron;
18 changes: 18 additions & 0 deletions backend/canisters/proposals_bot/api/src/updates/top_up_neuron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use candid::CandidType;
use serde::{Deserialize, Serialize};
use types::CanisterId;

#[derive(CandidType, Serialize, Deserialize, Debug)]
pub struct Args {
pub governance_canister_id: CanisterId,
pub amount: u128,
}

#[derive(CandidType, Serialize, Deserialize, Debug)]
pub enum Response {
Success,
TransferError(String),
GovernanceCanisterNotSupported,
Unauthorized,
InternalError(String),
}
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ impl NervousSystem {
pub fn proposal_rejection_fee(&self) -> u64 {
self.proposal_rejection_fee
}

pub fn neuron_for_submitting_proposals(&self) -> Option<SnsNeuronId> {
self.neuron_id_for_submitting_proposals
}
}

impl From<&NervousSystem> for NervousSystemMetrics {
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/proposals_bot/impl/src/updates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod appoint_admins;
pub mod c2c_submit_proposal;
pub mod import_proposals_group_into_community;
pub mod stake_neuron_for_submitting_proposals;
pub mod top_up_neuron;
pub mod wallet_receive;
100 changes: 100 additions & 0 deletions backend/canisters/proposals_bot/impl/src/updates/top_up_neuron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use crate::{mutate_state, RuntimeState};
use candid::Principal;
use canister_tracing_macros::trace;
use ic_cdk::api::call::{CallResult, RejectionCode};
use ic_cdk_macros::update;
use icrc_ledger_types::icrc1::account::Account;
use icrc_ledger_types::icrc1::transfer::TransferArg;
use proposals_bot_canister::top_up_neuron::{Response::*, *};
use sns_governance_canister::types::manage_neuron::claim_or_refresh::By;
use sns_governance_canister::types::manage_neuron::{ClaimOrRefresh, Command};
use sns_governance_canister::types::{manage_neuron_response, Empty, ManageNeuron};
use types::{CanisterId, SnsNeuronId};
use user_index_canister_c2c_client::LookupUserError;

#[update]
#[trace]
async fn top_up_neuron(args: Args) -> Response {
let PrepareResult {
caller,
user_index_canister_id,
ledger_canister_id,
sns_neuron_id,
} = match mutate_state(|state| prepare(&args, state)) {
Ok(ok) => ok,
Err(response) => return response,
};

match user_index_canister_c2c_client::lookup_user(caller, user_index_canister_id).await {
Ok(user) if user.is_platform_operator => {}
Err(LookupUserError::InternalError(error)) => return InternalError(error),
_ => return Unauthorized,
}

top_up_neuron_impl(&args, ledger_canister_id, sns_neuron_id)
.await
.unwrap_or_else(|error| InternalError(format!("{error:?}")))
}

struct PrepareResult {
caller: Principal,
user_index_canister_id: CanisterId,
ledger_canister_id: CanisterId,
sns_neuron_id: SnsNeuronId,
}

fn prepare(args: &Args, state: &mut RuntimeState) -> Result<PrepareResult, Response> {
if let Some(ns) = state.data.nervous_systems.get(&args.governance_canister_id) {
if let Some(sns_neuron_id) = ns.neuron_for_submitting_proposals() {
return Ok(PrepareResult {
caller: state.env.caller(),
user_index_canister_id: state.data.user_index_canister_id,
ledger_canister_id: ns.ledger_canister_id(),
sns_neuron_id,
});
}
}
Err(GovernanceCanisterNotSupported)
}

async fn top_up_neuron_impl(args: &Args, ledger_canister_id: CanisterId, sns_neuron_id: SnsNeuronId) -> CallResult<Response> {
if let Err(transfer_error) = icrc_ledger_canister_c2c_client::icrc1_transfer(
ledger_canister_id,
&TransferArg {
from_subaccount: None,
to: Account {
owner: args.governance_canister_id,
subaccount: Some(sns_neuron_id),
},
fee: None,
created_at_time: None,
memo: None,
amount: args.amount.into(),
},
)
.await?
{
return Ok(TransferError(format!("{transfer_error:?}")));
}

refresh_neuron(args.governance_canister_id, sns_neuron_id).await?;

Ok(Success)
}

async fn refresh_neuron(governance_canister_id: CanisterId, sns_neuron_id: SnsNeuronId) -> CallResult<()> {
let args = ManageNeuron {
subaccount: sns_neuron_id.to_vec(),
command: Some(Command::ClaimOrRefresh(ClaimOrRefresh {
by: Some(By::NeuronId(Empty {})),
})),
};

let response = sns_governance_canister_c2c_client::manage_neuron(governance_canister_id, &args).await?;

match response.command.unwrap() {
manage_neuron_response::Command::ClaimOrRefresh(_) => Ok(()),
manage_neuron_response::Command::Error(e) => Err((RejectionCode::Unknown, format!("{e:?}"))),
_ => unreachable!(),
}
}

0 comments on commit 6cc9fd1

Please sign in to comment.