1use crate::{
6 call::{AsyncCall, SyncCall},
7 Canister,
8};
9use candid::{utils::ArgumentDecoder, Decode, Encode};
10use ic_agent::{agent::EffectiveId, export::Principal, Agent, AgentError};
11use ic_management_canister_types::{
12 CanisterIdRecord, DeleteCanisterSnapshotArgs, LoadCanisterSnapshotArgs,
13 ProvisionalTopUpCanisterArgs, ReadCanisterSnapshotDataArgs, ReadCanisterSnapshotMetadataArgs,
14 TakeCanisterSnapshotArgs, UploadCanisterSnapshotDataArgs, UploadCanisterSnapshotMetadataArgs,
15 UploadChunkArgs,
16};
17pub use ic_management_canister_types::{
19 CanisterIdRange, CanisterLogFilter, CanisterLogRecord, CanisterMetadataArgs,
20 CanisterMetadataResult, CanisterMetricsArgs, CanisterMetricsResult, CanisterStatusResult,
21 CanisterStatusType, CanisterTimer, ChunkHash, CyclesConsumed, DefiniteCanisterSettings,
22 FetchCanisterLogsArgs, FetchCanisterLogsResult, ListCanistersResult, LogVisibility,
23 MemoryMetrics, OnLowWasmMemoryHookStatus, QueryStats, ReadCanisterSnapshotDataResult,
24 ReadCanisterSnapshotMetadataResult, RenameCanisterRecord, RenameToRecord, Snapshot,
25 SnapshotDataKind, SnapshotDataOffset, SnapshotMetadataGlobal, SnapshotSource,
26 StoredChunksResult, UploadCanisterSnapshotMetadataResult, UploadChunkResult,
27};
28use std::{convert::AsRef, marker::PhantomData, ops::Deref};
29use strum_macros::{AsRefStr, Display, EnumString, IntoStaticStr};
30
31pub mod attributes;
32pub mod builders;
33
34#[doc(inline)]
35pub use builders::{
36 CreateCanisterBuilder, InstallBuilder, InstallChunkedCodeBuilder, InstallCodeBuilder,
37 UpdateSettingsBuilder,
38};
39
40#[derive(Debug, Clone)]
42pub struct ManagementCanister<'agent>(Canister<'agent>);
43
44impl<'agent> Deref for ManagementCanister<'agent> {
45 type Target = Canister<'agent>;
46 fn deref(&self) -> &Self::Target {
47 &self.0
48 }
49}
50
51#[derive(AsRefStr, Debug, EnumString, Display, IntoStaticStr)]
53#[strum(serialize_all = "snake_case")]
54pub enum MgmtMethod {
55 CreateCanister,
57 InstallCode,
59 StartCanister,
61 StopCanister,
63 CanisterStatus,
65 DeleteCanister,
67 DepositCycles,
69 RawRand,
71 ProvisionalCreateCanisterWithCycles,
73 ProvisionalTopUpCanister,
75 UninstallCode,
77 UpdateSettings,
79 UploadChunk,
81 ClearChunkStore,
83 StoredChunks,
85 InstallChunkedCode,
87 FetchCanisterLogs,
89 TakeCanisterSnapshot,
91 LoadCanisterSnapshot,
93 ListCanisterSnapshots,
95 DeleteCanisterSnapshot,
97 ReadCanisterSnapshotMetadata,
99 ReadCanisterSnapshotData,
101 UploadCanisterSnapshotMetadata,
103 UploadCanisterSnapshotData,
105 EcdsaPublicKey,
107 SignWithEcdsa,
109 BitcoinGetBalance,
111 BitcoinGetUtxos,
113 BitcoinSendTransaction,
115 BitcoinGetCurrentFeePercentiles,
117 BitcoinGetBlockHeaders,
119 NodeMetricsHistory,
121 CanisterInfo,
123 CanisterMetadata,
125 CanisterMetrics,
127 ListCanisters,
129}
130
131impl<'agent> ManagementCanister<'agent> {
132 pub fn create(agent: &'agent Agent) -> Self {
134 Self(
135 Canister::builder()
136 .with_agent(agent)
137 .with_canister_id(Principal::management_canister())
138 .build()
139 .unwrap(),
140 )
141 }
142
143 pub fn from_canister(canister: Canister<'agent>) -> Self {
145 Self(canister)
146 }
147}
148
149#[doc(hidden)]
150#[deprecated(since = "0.42.0", note = "Please use CanisterStatusResult instead")]
151pub type StatusCallResult = CanisterStatusResult;
152
153#[doc(hidden)]
154#[deprecated(since = "0.42.0", note = "Please use CanisterStatusType instead")]
155pub type CanisterStatus = CanisterStatusType;
156
157#[doc(hidden)]
158#[deprecated(since = "0.42.0", note = "Please use FetchCanisterLogsResult instead")]
159pub type FetchCanisterLogsResponse = FetchCanisterLogsResult;
160
161#[doc(hidden)]
162#[deprecated(since = "0.42.0", note = "Please use StoredChunksResult instead")]
163pub type StoreChunksResult = StoredChunksResult;
164
165#[doc(hidden)]
166#[deprecated(
167 since = "0.42.0",
168 note = "Please use ReadCanisterSnapshotMetadataResult instead"
169)]
170pub type SnapshotMetadata = ReadCanisterSnapshotMetadataResult;
171
172#[doc(hidden)]
173#[deprecated(
174 since = "0.42.0",
175 note = "Please use ReadCanisterSnapshotDataResult instead"
176)]
177pub type SnapshotDataResult = ReadCanisterSnapshotDataResult;
178
179#[doc(hidden)]
180#[deprecated(
181 since = "0.42.0",
182 note = "Please use UploadCanisterSnapshotMetadataResult instead"
183)]
184pub type CanisterSnapshotId = UploadCanisterSnapshotMetadataResult;
185
186impl<'agent> ManagementCanister<'agent> {
187 pub fn canister_status(
193 &self,
194 canister_id: &Principal,
195 ) -> QueryOrUpdateCall<'agent, '_, (CanisterStatusResult,)> {
196 QueryOrUpdateCall::new(&self.0, MgmtMethod::CanisterStatus.into(), *canister_id)
197 }
198
199 pub fn create_canister<'canister>(&'canister self) -> CreateCanisterBuilder<'agent, 'canister> {
201 CreateCanisterBuilder::builder(self)
202 }
203
204 pub fn deposit_cycles(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
207 self.update(MgmtMethod::DepositCycles.as_ref())
208 .with_arg(CanisterIdRecord {
209 canister_id: *canister_id,
210 })
211 .with_effective_canister_id(canister_id.to_owned())
212 .build()
213 }
214
215 pub fn delete_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
217 self.update(MgmtMethod::DeleteCanister.as_ref())
218 .with_arg(CanisterIdRecord {
219 canister_id: *canister_id,
220 })
221 .with_effective_canister_id(canister_id.to_owned())
222 .build()
223 }
224
225 pub fn provisional_top_up_canister(
230 &self,
231 canister_id: &Principal,
232 top_up_args: &ProvisionalTopUpCanisterArgs,
233 ) -> impl 'agent + AsyncCall<Value = ()> {
234 self.update(MgmtMethod::ProvisionalTopUpCanister.as_ref())
235 .with_arg(top_up_args)
236 .with_effective_canister_id(canister_id.to_owned())
237 .build()
238 }
239
240 pub fn raw_rand(&self) -> impl 'agent + AsyncCall<Value = (Vec<u8>,)> {
244 self.update(MgmtMethod::RawRand.as_ref())
245 .build()
246 .map(|result: (Vec<u8>,)| (result.0,))
247 }
248
249 pub fn start_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
251 self.update(MgmtMethod::StartCanister.as_ref())
252 .with_arg(CanisterIdRecord {
253 canister_id: *canister_id,
254 })
255 .with_effective_canister_id(canister_id.to_owned())
256 .build()
257 }
258
259 pub fn stop_canister(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
261 self.update(MgmtMethod::StopCanister.as_ref())
262 .with_arg(CanisterIdRecord {
263 canister_id: *canister_id,
264 })
265 .with_effective_canister_id(canister_id.to_owned())
266 .build()
267 }
268
269 pub fn uninstall_code(&self, canister_id: &Principal) -> impl 'agent + AsyncCall<Value = ()> {
277 self.update(MgmtMethod::UninstallCode.as_ref())
278 .with_arg(CanisterIdRecord {
279 canister_id: *canister_id,
280 })
281 .with_effective_canister_id(canister_id.to_owned())
282 .build()
283 }
284
285 pub fn install_code<'canister>(
287 &'canister self,
288 canister_id: &Principal,
289 wasm: &'canister [u8],
290 ) -> InstallCodeBuilder<'agent, 'canister> {
291 InstallCodeBuilder::builder(self, canister_id, wasm)
292 }
293
294 pub fn update_settings<'canister>(
296 &'canister self,
297 canister_id: &Principal,
298 ) -> UpdateSettingsBuilder<'agent, 'canister> {
299 UpdateSettingsBuilder::builder(self, canister_id)
300 }
301
302 pub fn upload_chunk(
304 &self,
305 canister_id: &Principal,
306 upload_chunk_args: &UploadChunkArgs,
307 ) -> impl 'agent + AsyncCall<Value = (UploadChunkResult,)> {
308 self.update(MgmtMethod::UploadChunk.as_ref())
309 .with_arg(upload_chunk_args)
310 .with_effective_canister_id(*canister_id)
311 .build()
312 }
313
314 pub fn clear_chunk_store(
316 &self,
317 canister_id: &Principal,
318 ) -> impl 'agent + AsyncCall<Value = ()> {
319 self.update(MgmtMethod::ClearChunkStore.as_ref())
320 .with_arg(CanisterIdRecord {
321 canister_id: *canister_id,
322 })
323 .with_effective_canister_id(*canister_id)
324 .build()
325 }
326
327 pub fn stored_chunks(
329 &self,
330 canister_id: &Principal,
331 ) -> impl 'agent + AsyncCall<Value = (StoredChunksResult,)> {
332 self.update(MgmtMethod::StoredChunks.as_ref())
333 .with_arg(CanisterIdRecord {
334 canister_id: *canister_id,
335 })
336 .with_effective_canister_id(*canister_id)
337 .build()
338 }
339
340 pub fn install_chunked_code<'canister>(
342 &'canister self,
343 canister_id: &Principal,
344 wasm_module_hash: &[u8],
345 ) -> InstallChunkedCodeBuilder<'agent, 'canister> {
346 InstallChunkedCodeBuilder::builder(self, *canister_id, wasm_module_hash)
347 }
348
349 pub fn install<'canister: 'builder, 'builder>(
355 &'canister self,
356 canister_id: &Principal,
357 wasm: &'builder [u8],
358 ) -> InstallBuilder<'agent, 'canister, 'builder> {
359 InstallBuilder::builder(self, canister_id, wasm)
360 }
361
362 pub fn fetch_canister_logs(
364 &self,
365 args: &FetchCanisterLogsArgs,
366 ) -> impl 'agent + SyncCall<Value = (FetchCanisterLogsResult,)> {
367 self.query(MgmtMethod::FetchCanisterLogs.as_ref())
368 .with_arg(args)
369 .with_effective_canister_id(args.canister_id)
370 .build()
371 }
372
373 pub fn take_canister_snapshot(
377 &self,
378 take_args: &TakeCanisterSnapshotArgs,
379 ) -> impl 'agent + AsyncCall<Value = (Snapshot,)> {
380 let args = TakeCanisterSnapshotArgs {
381 sender_canister_version: None,
382 ..take_args.clone()
383 };
384 self.update(MgmtMethod::TakeCanisterSnapshot.as_ref())
385 .with_arg(args)
386 .with_effective_canister_id(take_args.canister_id)
387 .build()
388 }
389
390 pub fn load_canister_snapshot(
394 &self,
395 load_args: &LoadCanisterSnapshotArgs,
396 ) -> impl 'agent + AsyncCall<Value = ()> {
397 let args = LoadCanisterSnapshotArgs {
398 sender_canister_version: None,
399 ..load_args.clone()
400 };
401 self.update(MgmtMethod::LoadCanisterSnapshot.as_ref())
402 .with_arg(args)
403 .with_effective_canister_id(load_args.canister_id)
404 .build()
405 }
406
407 pub fn list_canister_snapshots(
409 &self,
410 canister_id: &Principal,
411 ) -> impl 'agent + AsyncCall<Value = (Vec<Snapshot>,)> {
412 self.update(MgmtMethod::ListCanisterSnapshots.as_ref())
413 .with_arg(CanisterIdRecord {
414 canister_id: *canister_id,
415 })
416 .with_effective_canister_id(*canister_id)
417 .build()
418 }
419
420 pub fn delete_canister_snapshot(
422 &self,
423 delete_args: &DeleteCanisterSnapshotArgs,
424 ) -> impl 'agent + AsyncCall<Value = ()> {
425 self.update(MgmtMethod::DeleteCanisterSnapshot.as_ref())
426 .with_arg(delete_args)
427 .with_effective_canister_id(delete_args.canister_id)
428 .build()
429 }
430
431 pub fn read_canister_snapshot_metadata(
433 &self,
434 metadata_args: &ReadCanisterSnapshotMetadataArgs,
435 ) -> impl 'agent + AsyncCall<Value = (ReadCanisterSnapshotMetadataResult,)> {
436 self.update(MgmtMethod::ReadCanisterSnapshotMetadata.as_ref())
437 .with_arg(metadata_args)
438 .with_effective_canister_id(metadata_args.canister_id)
439 .build()
440 }
441
442 pub fn read_canister_snapshot_data(
444 &self,
445 data_args: &ReadCanisterSnapshotDataArgs,
446 ) -> impl 'agent + AsyncCall<Value = (ReadCanisterSnapshotDataResult,)> {
447 self.update(MgmtMethod::ReadCanisterSnapshotData.as_ref())
448 .with_arg(data_args)
449 .with_effective_canister_id(data_args.canister_id)
450 .build()
451 }
452
453 pub fn upload_canister_snapshot_metadata(
455 &self,
456 metadata_args: &UploadCanisterSnapshotMetadataArgs,
457 ) -> impl 'agent + AsyncCall<Value = (UploadCanisterSnapshotMetadataResult,)> {
458 self.update(MgmtMethod::UploadCanisterSnapshotMetadata.as_ref())
459 .with_arg(metadata_args)
460 .with_effective_canister_id(metadata_args.canister_id)
461 .build()
462 }
463
464 pub fn upload_canister_snapshot_data(
466 &self,
467 data_args: &UploadCanisterSnapshotDataArgs,
468 ) -> impl 'agent + AsyncCall<Value = ()> {
469 self.update(MgmtMethod::UploadCanisterSnapshotData.as_ref())
470 .with_arg(data_args)
471 .with_effective_canister_id(data_args.canister_id)
472 .build()
473 }
474
475 pub fn canister_metadata(
477 &self,
478 args: &CanisterMetadataArgs,
479 ) -> impl 'agent + SyncCall<Value = (CanisterMetadataResult,)> {
480 self.query(MgmtMethod::CanisterMetadata.as_ref())
481 .with_arg(args)
482 .with_effective_canister_id(args.canister_id)
483 .build()
484 }
485
486 pub fn canister_metrics(
494 &self,
495 canister_id: &Principal,
496 ) -> QueryOrUpdateCall<'agent, '_, (CanisterMetricsResult,)> {
497 QueryOrUpdateCall::new(&self.0, MgmtMethod::CanisterMetrics.into(), *canister_id)
498 }
499
500 pub async fn list_canisters(
509 &self,
510 subnet_id: Principal,
511 ) -> Result<ListCanistersResult, AgentError> {
512 let agent = self.0.agent;
513 let signed = agent
514 .query(
515 &Principal::management_canister(),
516 MgmtMethod::ListCanisters.as_ref(),
517 )
518 .with_arg(Encode!().map_err(|e| AgentError::CandidError(Box::new(e)))?)
519 .sign()?;
520 let bytes = agent
521 .query_signed(EffectiveId::Subnet(subnet_id), signed.signed_query)
522 .await?;
523 Decode!(&bytes, ListCanistersResult).map_err(|e| AgentError::CandidError(Box::new(e)))
524 }
525}
526
527#[derive(Debug)]
540pub struct QueryOrUpdateCall<'agent, 'canister, Out> {
541 canister: &'canister Canister<'agent>,
542 method_name: &'static str,
543 canister_id: Principal,
544 as_update: bool,
545 _out: PhantomData<Out>,
546}
547
548impl<'agent: 'canister, 'canister, Out> QueryOrUpdateCall<'agent, 'canister, Out>
549where
550 Out: for<'de> ArgumentDecoder<'de> + Send + Sync,
551{
552 fn new(
553 canister: &'canister Canister<'agent>,
554 method_name: &'static str,
555 canister_id: Principal,
556 ) -> Self {
557 Self {
558 canister,
559 method_name,
560 canister_id,
561 as_update: false,
562 _out: PhantomData,
563 }
564 }
565
566 pub fn as_update(mut self) -> Self {
568 self.as_update = true;
569 self
570 }
571
572 pub async fn call(self) -> Result<Out, AgentError> {
576 let arg = CanisterIdRecord {
577 canister_id: self.canister_id,
578 };
579 if self.as_update {
580 self.canister
581 .update(self.method_name)
582 .with_arg(arg)
583 .with_effective_canister_id(self.canister_id)
584 .build()
585 .call_and_wait()
586 .await
587 } else {
588 self.canister
589 .query(self.method_name)
590 .with_arg(arg)
591 .with_effective_canister_id(self.canister_id)
592 .build()
593 .call()
594 .await
595 }
596 }
597}