canic_core/interface/ic/
mod.rs

1//! IC Interfaces
2//! Thin wrappers around the management canister and network-specific helpers.
3
4pub mod call;
5pub mod canister;
6pub mod cycles;
7pub mod helper;
8pub mod icp;
9pub mod network;
10pub mod sns;
11
12pub use helper::*;
13
14use crate::{
15    Error,
16    cdk::mgmt::{
17        self, CanisterInstallMode, CanisterStatusArgs, CanisterStatusResult, DeleteCanisterArgs,
18        DepositCyclesArgs, InstallCodeArgs, UninstallCodeArgs, WasmModule,
19    },
20    env::nns::NNS_REGISTRY_CANISTER,
21    interface::prelude::*,
22    log,
23    log::Topic,
24    model::metrics::{MetricKind, MetricsState},
25    spec::nns::{GetSubnetForCanisterRequest, GetSubnetForCanisterResponse},
26};
27use candid::{CandidType, Principal, decode_one, encode_args, utils::ArgumentEncoder};
28
29//
30// ────────────────────────────── CANISTER STATUS ──────────────────────────────
31//
32
33/// Query the management canister for a canister's status.
34pub async fn canister_status(canister_pid: Principal) -> Result<CanisterStatusResult, Error> {
35    let args = CanisterStatusArgs {
36        canister_id: canister_pid,
37    };
38
39    let status = mgmt::canister_status(&args).await.map_err(Error::from)?;
40    MetricsState::increment(MetricKind::CanisterStatus);
41
42    Ok(status)
43}
44
45//
46// ──────────────────────────────── CYCLES API ─────────────────────────────────
47//
48
49/// Returns the local canister's cycle balance (cheap).
50#[must_use]
51pub fn canister_cycle_balance() -> Cycles {
52    crate::cdk::api::canister_cycle_balance().into()
53}
54
55/// Deposits cycles into a canister.
56pub async fn deposit_cycles(canister_pid: Principal, cycles: u128) -> Result<(), Error> {
57    let args = DepositCyclesArgs {
58        canister_id: canister_pid,
59    };
60    mgmt::deposit_cycles(&args, cycles)
61        .await
62        .map_err(Error::from)?;
63
64    MetricsState::increment(MetricKind::DepositCycles);
65
66    Ok(())
67}
68
69/// Gets a canister's cycle balance (expensive: calls mgmt canister).
70pub async fn get_cycles(canister_pid: Principal) -> Result<Cycles, Error> {
71    let status = canister_status(canister_pid).await?;
72
73    Ok(status.cycles.into())
74}
75
76//
77// ────────────────────────────── TOPOLOGY LOOKUPS ─────────────────────────────
78//
79
80/// Queries the NNS registry for the subnet that this canister belongs to.
81pub async fn get_current_subnet_pid() -> Result<Option<Principal>, Error> {
82    let request = GetSubnetForCanisterRequest::new(canister_self());
83
84    let subnet_id_opt = Call::unbounded_wait(*NNS_REGISTRY_CANISTER, "get_subnet_for_canister")
85        .with_arg(request)
86        .await?
87        .candid::<GetSubnetForCanisterResponse>()?
88        .map_err(Error::CallFailed)?
89        .subnet_id;
90
91    if let Some(subnet_id) = subnet_id_opt {
92        log!(Topic::Topology, Info, "get_current_subnet_pid: {subnet_id}");
93    } else {
94        log!(Topic::Topology, Warn, "get_current_subnet_pid: not found");
95    }
96
97    Ok(subnet_id_opt)
98}
99
100//
101// ────────────────────────────── INSTALL / UNINSTALL ──────────────────────────
102//
103
104/// Installs or upgrades a canister with the given wasm + args.
105pub async fn install_code<T: ArgumentEncoder>(
106    mode: CanisterInstallMode,
107    canister_pid: Principal,
108    wasm: &[u8],
109    args: T,
110) -> Result<(), Error> {
111    let arg = encode_args(args)?;
112    let install_args = InstallCodeArgs {
113        mode,
114        canister_id: canister_pid,
115        wasm_module: WasmModule::from(wasm),
116        arg,
117    };
118
119    mgmt::install_code(&install_args)
120        .await
121        .map_err(Error::from)?;
122
123    let metric_kind = match mode {
124        CanisterInstallMode::Install => MetricKind::InstallCode,
125        CanisterInstallMode::Reinstall => MetricKind::ReinstallCode,
126        CanisterInstallMode::Upgrade(_) => MetricKind::UpgradeCode,
127    };
128    MetricsState::increment(metric_kind);
129
130    Ok(())
131}
132
133/// Uninstalls code from a canister.
134pub async fn uninstall_code(canister_pid: Principal) -> Result<(), Error> {
135    let args = UninstallCodeArgs {
136        canister_id: canister_pid,
137    };
138
139    mgmt::uninstall_code(&args).await.map_err(Error::from)?;
140    MetricsState::increment(MetricKind::UninstallCode);
141
142    Ok(())
143}
144
145/// Deletes a canister (code + controllers) via the management canister.
146pub async fn delete_canister(canister_pid: Principal) -> Result<(), Error> {
147    let args = DeleteCanisterArgs {
148        canister_id: canister_pid,
149    };
150
151    mgmt::delete_canister(&args).await.map_err(Error::from)?;
152    MetricsState::increment(MetricKind::DeleteCanister);
153
154    Ok(())
155}
156
157//
158// ──────────────────────────────── GENERIC HELPERS ────────────────────────────
159//
160
161/// Calls a method on a canister and candid-decodes the response into `T`.
162pub async fn call_and_decode<T: CandidType + for<'de> candid::Deserialize<'de>>(
163    pid: Principal,
164    method: &str,
165    arg: impl CandidType,
166) -> Result<T, Error> {
167    let response = Call::unbounded_wait(pid, method)
168        .with_arg(arg)
169        .await
170        .map_err(Error::from)?;
171
172    decode_one(&response).map_err(Error::from)
173}