ic_utils/interfaces/
management_canister.rs

1//! The canister interface for the IC management canister. See the [specification][spec] for full documentation of the interface.
2//!
3//! [spec]: https://internetcomputer.org/docs/current/references/ic-interface-spec#ic-management-canister
4
5use crate::{
6    call::{AsyncCall, SyncCall},
7    Canister,
8};
9use candid::{CandidType, Deserialize, Nat};
10use ic_agent::{export::Principal, Agent};
11use std::{convert::AsRef, ops::Deref};
12use strum_macros::{AsRefStr, Display, EnumString};
13
14pub mod attributes;
15pub mod builders;
16
17#[doc(inline)]
18pub use builders::{
19    CreateCanisterBuilder, InstallBuilder, InstallChunkedCodeBuilder, InstallCodeBuilder,
20    UpdateCanisterBuilder,
21};
22
23/// The IC management canister.
24#[derive(Debug, Clone)]
25pub struct ManagementCanister<'agent>(Canister<'agent>);
26
27impl<'agent> Deref for ManagementCanister<'agent> {
28    type Target = Canister<'agent>;
29    fn deref(&self) -> &Self::Target {
30        &self.0
31    }
32}
33
34/// All the known methods of the management canister.
35#[derive(AsRefStr, Debug, EnumString, Display)]
36#[strum(serialize_all = "snake_case")]
37pub enum MgmtMethod {
38    /// See [`ManagementCanister::create_canister`].
39    CreateCanister,
40    /// See [`ManagementCanister::install_code`].
41    InstallCode,
42    /// See [`ManagementCanister::start_canister`].
43    StartCanister,
44    /// See [`ManagementCanister::stop_canister`].
45    StopCanister,
46    /// See [`ManagementCanister::canister_status`].
47    CanisterStatus,
48    /// See [`ManagementCanister::delete_canister`].
49    DeleteCanister,
50    /// See [`ManagementCanister::deposit_cycles`].
51    DepositCycles,
52    /// See [`ManagementCanister::raw_rand`].
53    RawRand,
54    /// See [`CreateCanisterBuilder::as_provisional_create_with_amount`].
55    ProvisionalCreateCanisterWithCycles,
56    /// See [`ManagementCanister::provisional_top_up_canister`].
57    ProvisionalTopUpCanister,
58    /// See [`ManagementCanister::uninstall_code`].
59    UninstallCode,
60    /// See [`ManagementCanister::update_settings`].
61    UpdateSettings,
62    /// See [`ManagementCanister::upload_chunk`].
63    UploadChunk,
64    /// See [`ManagementCanister::clear_chunk_store`].
65    ClearChunkStore,
66    /// See [`ManagementCanister::stored_chunks`].
67    StoredChunks,
68    /// See [`ManagementCanister::install_chunked_code`].
69    InstallChunkedCode,
70    /// See [`ManagementCanister::fetch_canister_logs`].
71    FetchCanisterLogs,
72    /// See [`ManagementCanister::take_canister_snapshot`].
73    TakeCanisterSnapshot,
74    /// See [`ManagementCanister::load_canister_snapshot`].
75    LoadCanisterSnapshot,
76    /// See [`ManagementCanister::list_canister_snapshots`].
77    ListCanisterSnapshots,
78    /// See [`ManagementCanister::delete_canister_snapshot`].
79    DeleteCanisterSnapshot,
80    /// There is no corresponding agent function as only canisters can call it.
81    EcdsaPublicKey,
82    /// There is no corresponding agent function as only canisters can call it.
83    SignWithEcdsa,
84    /// There is no corresponding agent function as only canisters can call it. Use [`BitcoinCanister`](super::BitcoinCanister) instead.
85    BitcoinGetBalance,
86    /// There is no corresponding agent function as only canisters can call it. Use [`BitcoinCanister`](super::BitcoinCanister) instead.
87    BitcoinGetUtxos,
88    /// There is no corresponding agent function as only canisters can call it. Use [`BitcoinCanister`](super::BitcoinCanister) instead.
89    BitcoinSendTransaction,
90    /// There is no corresponding agent function as only canisters can call it. Use [`BitcoinCanister`](super::BitcoinCanister) instead.
91    BitcoinGetCurrentFeePercentiles,
92    /// There is no corresponding agent function as only canisters can call it. Use [`BitcoinCanister`](super::BitcoinCanister) instead.
93    BitcoinGetBlockHeaders,
94    /// There is no corresponding agent function as only canisters can call it.
95    NodeMetricsHistory,
96    /// There is no corresponding agent function as only canisters can call it.
97    CanisterInfo,
98}
99
100impl<'agent> ManagementCanister<'agent> {
101    /// Create an instance of a `ManagementCanister` interface pointing to the specified Canister ID.
102    pub fn create(agent: &'agent Agent) -> Self {
103        Self(
104            Canister::builder()
105                .with_agent(agent)
106                .with_canister_id(Principal::management_canister())
107                .build()
108                .unwrap(),
109        )
110    }
111
112    /// Create a `ManagementCanister` interface from an existing canister object.
113    pub fn from_canister(canister: Canister<'agent>) -> Self {
114        Self(canister)
115    }
116}
117
118/// The complete canister status information of a canister. This includes
119/// the `CanisterStatus`, a hash of the module installed on the canister (None if nothing installed),
120/// the controller of the canister, the canister's memory size, and its balance in cycles.
121#[derive(Clone, Debug, Deserialize, CandidType)]
122pub struct StatusCallResult {
123    /// The status of the canister.
124    pub status: CanisterStatus,
125    /// The canister's settings.
126    pub settings: DefiniteCanisterSettings,
127    /// The SHA-256 hash of the canister's installed code, if any.
128    pub module_hash: Option<Vec<u8>>,
129    /// The total size, in bytes, of the memory the canister is using.
130    pub memory_size: Nat,
131    /// The canister's cycle balance.
132    pub cycles: Nat,
133    /// The canister's reserved cycles balance.
134    pub reserved_cycles: Nat,
135    /// The cycles burned by the canister in one day for its resource usage
136    /// (compute and memory allocation and memory usage).
137    pub idle_cycles_burned_per_day: Nat,
138    /// Additional information relating to query calls.
139    pub query_stats: QueryStats,
140}
141
142/// Statistics relating to query calls.
143#[derive(Clone, Debug, Deserialize, CandidType)]
144pub struct QueryStats {
145    /// The total number of query calls this canister has performed.
146    pub num_calls_total: Nat,
147    /// The total number of instructions this canister has executed during query calls.
148    pub num_instructions_total: Nat,
149    /// The total number of bytes in request payloads sent to this canister's query calls.
150    pub request_payload_bytes_total: Nat,
151    /// The total number of bytes in response payloads returned from this canister's query calls.
152    pub response_payload_bytes_total: Nat,
153}
154
155/// Log visibility for a canister.
156#[derive(Default, Clone, CandidType, Deserialize, Debug, PartialEq, Eq)]
157pub enum LogVisibility {
158    #[default]
159    #[serde(rename = "controllers")]
160    /// Canister logs are visible to controllers only.
161    Controllers,
162    #[serde(rename = "public")]
163    /// Canister logs are visible to everyone.
164    Public,
165    #[serde(rename = "allowed_viewers")]
166    /// Canister logs are visible to a set of principals.
167    AllowedViewers(Vec<Principal>),
168}
169
170/// The concrete settings of a canister.
171#[derive(Clone, Debug, Deserialize, CandidType)]
172pub struct DefiniteCanisterSettings {
173    /// The set of canister controllers. Controllers can update the canister via the management canister.
174    pub controllers: Vec<Principal>,
175    /// The allocation percentage (between 0 and 100 inclusive) for *guaranteed* compute capacity.
176    pub compute_allocation: Nat,
177    /// The allocation, in bytes (up to 256 TiB) that the canister is allowed to use for storage.
178    pub memory_allocation: Nat,
179    /// The IC will freeze a canister protectively if it will likely run out of cycles before this amount of time,
180    /// in seconds (up to `u64::MAX`), has passed.
181    pub freezing_threshold: Nat,
182    /// The upper limit of the canister's reserved cycles balance.
183    pub reserved_cycles_limit: Option<Nat>,
184    /// A soft limit on the Wasm memory usage of the canister in bytes (up to 256TiB).
185    pub wasm_memory_limit: Option<Nat>,
186    /// A threshold on the Wasm memory usage of the canister as a distance in bytes from `wasm_memory_limit`,
187    /// at which the canister's `on_low_wasm_memory` hook will be called (up to 256TiB)
188    pub wasm_memory_threshold: Option<Nat>,
189    /// The canister log visibility. Defines which principals are allowed to fetch logs.
190    pub log_visibility: LogVisibility,
191}
192
193impl std::fmt::Display for StatusCallResult {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        std::fmt::Debug::fmt(self, f)
196    }
197}
198
199/// The status of a Canister, whether it's running, in the process of stopping, or
200/// stopped.
201#[derive(Clone, Debug, Deserialize, Eq, PartialEq, CandidType)]
202pub enum CanisterStatus {
203    /// The canister is currently running.
204    #[serde(rename = "running")]
205    Running,
206    /// The canister is in the process of stopping.
207    #[serde(rename = "stopping")]
208    Stopping,
209    /// The canister is stopped.
210    #[serde(rename = "stopped")]
211    Stopped,
212}
213
214impl std::fmt::Display for CanisterStatus {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        std::fmt::Debug::fmt(self, f)
217    }
218}
219
220/// A log record of a canister.
221#[derive(Default, Clone, CandidType, Deserialize, Debug, PartialEq, Eq)]
222pub struct CanisterLogRecord {
223    /// The index of the log record.
224    pub idx: u64,
225    /// The timestamp of the log record.
226    pub timestamp_nanos: u64,
227    /// The content of the log record.
228    #[serde(with = "serde_bytes")]
229    pub content: Vec<u8>,
230}
231
232/// The result of a [`ManagementCanister::fetch_canister_logs`] call.
233#[derive(Clone, Debug, Deserialize, Eq, PartialEq, CandidType)]
234pub struct FetchCanisterLogsResponse {
235    /// The logs of the canister.
236    pub canister_log_records: Vec<CanisterLogRecord>,
237}
238
239/// Chunk hash.
240#[derive(CandidType, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
241pub struct ChunkHash {
242    /// The hash of an uploaded chunk
243    #[serde(with = "serde_bytes")]
244    pub hash: Vec<u8>,
245}
246
247/// Return type of [`ManagementCanister::stored_chunks`].
248pub type StoreChunksResult = Vec<ChunkHash>;
249
250/// Return type of [`ManagementCanister::upload_chunk`].
251pub type UploadChunkResult = ChunkHash;
252
253/// A recorded snapshot of a canister. Can be restored with [`ManagementCanister::load_canister_snapshot`].
254#[derive(Debug, Clone, CandidType, Deserialize)]
255pub struct Snapshot {
256    /// The ID of the snapshot.
257    #[serde(with = "serde_bytes")]
258    pub id: Vec<u8>,
259    /// The Unix nanosecond timestamp the snapshot was taken at.
260    pub taken_at_timestamp: u64,
261    /// The size of the snapshot in bytes.
262    pub total_size: u64,
263}
264
265impl<'agent> ManagementCanister<'agent> {
266    /// Get the status of a canister.
267    pub fn canister_status(
268        &self,
269        canister_id: &Principal,
270    ) -> impl 'agent + AsyncCall<Value = (StatusCallResult,)> {
271        #[derive(CandidType)]
272        struct In {
273            canister_id: Principal,
274        }
275
276        self.update(MgmtMethod::CanisterStatus.as_ref())
277            .with_arg(In {
278                canister_id: *canister_id,
279            })
280            .with_effective_canister_id(canister_id.to_owned())
281            .build()
282            .map(|result: (StatusCallResult,)| (result.0,))
283    }
284
285    /// Create a canister.
286    pub fn create_canister<'canister>(&'canister self) -> CreateCanisterBuilder<'agent, 'canister> {
287        CreateCanisterBuilder::builder(self)
288    }
289
290    /// This method deposits the cycles included in this call into the specified canister.
291    /// Only the controller of the canister can deposit cycles.
292    pub fn deposit_cycles(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
293        #[derive(CandidType)]
294        struct Argument {
295            canister_id: Principal,
296        }
297
298        self.update(MgmtMethod::DepositCycles.as_ref())
299            .with_arg(Argument {
300                canister_id: *canister_id,
301            })
302            .with_effective_canister_id(canister_id.to_owned())
303            .build()
304    }
305
306    /// Deletes a canister.
307    pub fn delete_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
308        #[derive(CandidType)]
309        struct Argument {
310            canister_id: Principal,
311        }
312
313        self.update(MgmtMethod::DeleteCanister.as_ref())
314            .with_arg(Argument {
315                canister_id: *canister_id,
316            })
317            .with_effective_canister_id(canister_id.to_owned())
318            .build()
319    }
320
321    /// Until developers can convert real ICP tokens to a top up an existing canister,
322    /// the system provides the `provisional_top_up_canister` method.
323    /// It adds amount cycles to the balance of canister identified by amount
324    /// (implicitly capping it at `MAX_CANISTER_BALANCE`).
325    pub fn provisional_top_up_canister(
326        &self,
327        canister_id: &Principal,
328        amount: u64,
329    ) -> impl 'agent + AsyncCall<Value = ()> {
330        #[derive(CandidType)]
331        struct Argument {
332            canister_id: Principal,
333            amount: u64,
334        }
335
336        self.update(MgmtMethod::ProvisionalTopUpCanister.as_ref())
337            .with_arg(Argument {
338                canister_id: *canister_id,
339                amount,
340            })
341            .with_effective_canister_id(canister_id.to_owned())
342            .build()
343    }
344
345    /// This method takes no input and returns 32 pseudo-random bytes to the caller.
346    /// The return value is unknown to any part of the IC at time of the submission of this call.
347    /// A new return value is generated for each call to this method.
348    pub fn raw_rand(&self) -> impl 'agent + AsyncCall<Value = (Vec<u8>,)> {
349        self.update(MgmtMethod::RawRand.as_ref())
350            .build()
351            .map(|result: (Vec<u8>,)| (result.0,))
352    }
353
354    /// Starts a canister.
355    pub fn start_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
356        #[derive(CandidType)]
357        struct Argument {
358            canister_id: Principal,
359        }
360
361        self.update(MgmtMethod::StartCanister.as_ref())
362            .with_arg(Argument {
363                canister_id: *canister_id,
364            })
365            .with_effective_canister_id(canister_id.to_owned())
366            .build()
367    }
368
369    /// Stop a canister.
370    pub fn stop_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
371        #[derive(CandidType)]
372        struct Argument {
373            canister_id: Principal,
374        }
375
376        self.update(MgmtMethod::StopCanister.as_ref())
377            .with_arg(Argument {
378                canister_id: *canister_id,
379            })
380            .with_effective_canister_id(canister_id.to_owned())
381            .build()
382    }
383
384    /// This method removes a canister’s code and state, making the canister empty again.
385    /// Only the controller of the canister can uninstall code.
386    /// Uninstalling a canister’s code will reject all calls that the canister has not yet responded to,
387    /// and drop the canister’s code and state.
388    /// Outstanding responses to the canister will not be processed, even if they arrive after code has been installed again.
389    /// The canister is now empty. In particular, any incoming or queued calls will be rejected.
390    //// A canister after uninstalling retains its cycles balance, controller, status, and allocations.
391    pub fn uninstall_code(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
392        #[derive(CandidType)]
393        struct Argument {
394            canister_id: Principal,
395        }
396
397        self.update(MgmtMethod::UninstallCode.as_ref())
398            .with_arg(Argument {
399                canister_id: *canister_id,
400            })
401            .with_effective_canister_id(canister_id.to_owned())
402            .build()
403    }
404
405    /// Install a canister, with all the arguments necessary for creating the canister.
406    pub fn install_code<'canister>(
407        &'canister self,
408        canister_id: &Principal,
409        wasm: &'canister [u8],
410    ) -> InstallCodeBuilder<'agent, 'canister> {
411        InstallCodeBuilder::builder(self, canister_id, wasm)
412    }
413
414    /// Update one or more of a canisters settings (i.e its controller, compute allocation, or memory allocation.)
415    pub fn update_settings<'canister>(
416        &'canister self,
417        canister_id: &Principal,
418    ) -> UpdateCanisterBuilder<'agent, 'canister> {
419        UpdateCanisterBuilder::builder(self, canister_id)
420    }
421
422    /// Upload a chunk of a WASM module to a canister's chunked WASM storage.
423    pub fn upload_chunk(
424        &self,
425        canister_id: &Principal,
426        chunk: &[u8],
427    ) -> impl 'agent + AsyncCall<Value = (UploadChunkResult,)> {
428        #[derive(CandidType, Deserialize)]
429        struct Argument<'a> {
430            canister_id: Principal,
431            #[serde(with = "serde_bytes")]
432            chunk: &'a [u8],
433        }
434
435        self.update(MgmtMethod::UploadChunk.as_ref())
436            .with_arg(Argument {
437                canister_id: *canister_id,
438                chunk,
439            })
440            .with_effective_canister_id(*canister_id)
441            .build()
442    }
443
444    /// Clear a canister's chunked WASM storage.
445    pub fn clear_chunk_store(
446        &self,
447        canister_id: &Principal,
448    ) -> impl 'agent + AsyncCall<Value = ()> {
449        #[derive(CandidType)]
450        struct Argument<'a> {
451            canister_id: &'a Principal,
452        }
453        self.update(MgmtMethod::ClearChunkStore.as_ref())
454            .with_arg(Argument { canister_id })
455            .with_effective_canister_id(*canister_id)
456            .build()
457    }
458
459    /// Get a list of the hashes of a canister's stored WASM chunks
460    pub fn stored_chunks(
461        &self,
462        canister_id: &Principal,
463    ) -> impl 'agent + AsyncCall<Value = (StoreChunksResult,)> {
464        #[derive(CandidType)]
465        struct Argument<'a> {
466            canister_id: &'a Principal,
467        }
468        self.update(MgmtMethod::StoredChunks.as_ref())
469            .with_arg(Argument { canister_id })
470            .with_effective_canister_id(*canister_id)
471            .build()
472    }
473
474    /// Install a canister module previously uploaded in chunks via [`upload_chunk`](Self::upload_chunk).
475    pub fn install_chunked_code<'canister>(
476        &'canister self,
477        canister_id: &Principal,
478        wasm_module_hash: &[u8],
479    ) -> InstallChunkedCodeBuilder<'agent, 'canister> {
480        InstallChunkedCodeBuilder::builder(self, *canister_id, wasm_module_hash)
481    }
482
483    /// Install a canister module, automatically selecting one-shot installation or chunked installation depending on module size.
484    ///
485    /// # Warnings
486    ///
487    /// This will clear chunked code storage if chunked installation is used. Do not use with canisters that you are manually uploading chunked code to.
488    pub fn install<'canister: 'builder, 'builder>(
489        &'canister self,
490        canister_id: &Principal,
491        wasm: &'builder [u8],
492    ) -> InstallBuilder<'agent, 'canister, 'builder> {
493        InstallBuilder::builder(self, canister_id, wasm)
494    }
495
496    /// Fetch the logs of a canister.
497    pub fn fetch_canister_logs(
498        &self,
499        canister_id: &Principal,
500    ) -> impl 'agent + SyncCall<Value = (FetchCanisterLogsResponse,)> {
501        #[derive(CandidType)]
502        struct In {
503            canister_id: Principal,
504        }
505
506        // `fetch_canister_logs` is only supported in non-replicated mode.
507        self.query(MgmtMethod::FetchCanisterLogs.as_ref())
508            .with_arg(In {
509                canister_id: *canister_id,
510            })
511            .with_effective_canister_id(*canister_id)
512            .build()
513    }
514
515    /// Creates a canister snapshot, optionally replacing an existing snapshot.
516    ///  
517    /// <div class="warning">Canisters should be stopped before running this method!</div>
518    pub fn take_canister_snapshot(
519        &self,
520        canister_id: &Principal,
521        replace_snapshot: Option<&[u8]>,
522    ) -> impl 'agent + AsyncCall<Value = (Snapshot,)> {
523        #[derive(CandidType)]
524        struct In<'a> {
525            canister_id: Principal,
526            replace_snapshot: Option<&'a [u8]>,
527        }
528        self.update(MgmtMethod::TakeCanisterSnapshot.as_ref())
529            .with_arg(In {
530                canister_id: *canister_id,
531                replace_snapshot,
532            })
533            .with_effective_canister_id(*canister_id)
534            .build()
535    }
536
537    /// Loads a canister snapshot by ID, replacing the canister's state with its state at the time the snapshot was taken.
538    ///
539    /// <div class="warning">Canisters should be stopped before running this method!</div>
540    pub fn load_canister_snapshot(
541        &self,
542        canister_id: &Principal,
543        snapshot_id: &[u8],
544    ) -> impl 'agent + AsyncCall<Value = ()> {
545        #[derive(CandidType)]
546        struct In<'a> {
547            canister_id: Principal,
548            snapshot_id: &'a [u8],
549            sender_canister_version: Option<u64>,
550        }
551        self.update(MgmtMethod::LoadCanisterSnapshot.as_ref())
552            .with_arg(In {
553                canister_id: *canister_id,
554                snapshot_id,
555                sender_canister_version: None,
556            })
557            .with_effective_canister_id(*canister_id)
558            .build()
559    }
560
561    /// List a canister's recorded snapshots.
562    pub fn list_canister_snapshots(
563        &self,
564        canister_id: &Principal,
565    ) -> impl 'agent + AsyncCall<Value = (Vec<Snapshot>,)> {
566        #[derive(CandidType)]
567        struct In {
568            canister_id: Principal,
569        }
570        self.update(MgmtMethod::ListCanisterSnapshots.as_ref())
571            .with_arg(In {
572                canister_id: *canister_id,
573            })
574            .with_effective_canister_id(*canister_id)
575            .build()
576    }
577
578    /// Deletes a recorded canister snapshot by ID.
579    pub fn delete_canister_snapshot(
580        &self,
581        canister_id: &Principal,
582        snapshot_id: &[u8],
583    ) -> impl 'agent + AsyncCall<Value = ()> {
584        #[derive(CandidType)]
585        struct In<'a> {
586            canister_id: Principal,
587            snapshot_id: &'a [u8],
588        }
589        self.update(MgmtMethod::DeleteCanisterSnapshot.as_ref())
590            .with_arg(In {
591                canister_id: *canister_id,
592                snapshot_id,
593            })
594            .build()
595    }
596}