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