1#[doc(inline)]
4pub use super::attributes::{
5 ComputeAllocation, FreezingThreshold, MemoryAllocation, ReservedCyclesLimit, WasmMemoryLimit,
6};
7use super::{ChunkHash, LogVisibility, ManagementCanister};
8use crate::call::CallFuture;
9use crate::{
10 call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
11};
12use async_trait::async_trait;
13use candid::{utils::ArgumentEncoder, CandidType, Deserialize, Nat};
14use futures_util::{
15 future::ready,
16 stream::{self, FuturesUnordered},
17 FutureExt, Stream, StreamExt, TryStreamExt,
18};
19use ic_agent::{agent::CallResponse, export::Principal, AgentError};
20use sha2::{Digest, Sha256};
21use std::{
22 collections::BTreeSet,
23 convert::{From, TryInto},
24 future::IntoFuture,
25 pin::Pin,
26 str::FromStr,
27};
28
29#[derive(Debug, Clone, CandidType, Deserialize)]
32pub struct CanisterSettings {
33 pub controllers: Option<Vec<Principal>>,
37 pub compute_allocation: Option<Nat>,
43 pub memory_allocation: Option<Nat>,
49
50 pub freezing_threshold: Option<Nat>,
54
55 pub reserved_cycles_limit: Option<Nat>,
68
69 pub wasm_memory_limit: Option<Nat>,
78
79 pub wasm_memory_threshold: Option<Nat>,
87
88 pub log_visibility: Option<LogVisibility>,
92}
93
94#[derive(Debug)]
96pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
97 canister: &'canister Canister<'agent>,
98 effective_canister_id: Principal,
99 controllers: Option<Result<Vec<Principal>, AgentError>>,
100 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
101 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
102 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
103 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
104 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
105 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
106 log_visibility: Option<Result<LogVisibility, AgentError>>,
107 is_provisional_create: bool,
108 amount: Option<u128>,
109 specified_id: Option<Principal>,
110}
111
112impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
113 pub fn builder(canister: &'canister Canister<'agent>) -> Self {
115 Self {
116 canister,
117 effective_canister_id: Principal::management_canister(),
118 controllers: None,
119 compute_allocation: None,
120 memory_allocation: None,
121 freezing_threshold: None,
122 reserved_cycles_limit: None,
123 wasm_memory_limit: None,
124 wasm_memory_threshold: None,
125 log_visibility: None,
126 is_provisional_create: false,
127 amount: None,
128 specified_id: None,
129 }
130 }
131
132 #[allow(clippy::wrong_self_convention)]
139 pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
140 Self {
141 is_provisional_create: true,
142 amount,
143 ..self
144 }
145 }
146
147 pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
152 Self {
153 is_provisional_create: true,
154 specified_id: Some(specified_id),
155 effective_canister_id: specified_id,
156 ..self
157 }
158 }
159
160 pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
162 where
163 E: std::fmt::Display,
164 C: TryInto<Principal, Error = E>,
165 {
166 match effective_canister_id.try_into() {
167 Ok(effective_canister_id) => Self {
168 effective_canister_id,
169 ..self
170 },
171 Err(_) => self,
172 }
173 }
174
175 pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
178 where
179 E: std::fmt::Display,
180 C: TryInto<Principal, Error = E>,
181 {
182 let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
183 ca.try_into()
184 .map_err(|e| AgentError::MessageError(format!("{e}")))
185 });
186 let controllers: Option<Result<Vec<Principal>, _>> =
187 match (controller_to_add, self.controllers) {
188 (_, Some(Err(sticky))) => Some(Err(sticky)),
189 (Some(Err(e)), _) => Some(Err(e)),
190 (None, _) => None,
191 (Some(Ok(controller)), Some(Ok(controllers))) => {
192 let mut controllers = controllers;
193 controllers.push(controller);
194 Some(Ok(controllers))
195 }
196 (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
197 };
198 Self {
199 controllers,
200 ..self
201 }
202 }
203
204 pub fn with_controller<C, E>(self, controller: C) -> Self
206 where
207 E: std::fmt::Display,
208 C: TryInto<Principal, Error = E>,
209 {
210 self.with_optional_controller(Some(controller))
211 }
212
213 pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
216 where
217 E: std::fmt::Display,
218 C: TryInto<ComputeAllocation, Error = E>,
219 {
220 Self {
221 compute_allocation: compute_allocation.map(|ca| {
222 ca.try_into()
223 .map_err(|e| AgentError::MessageError(format!("{e}")))
224 }),
225 ..self
226 }
227 }
228
229 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
231 where
232 E: std::fmt::Display,
233 C: TryInto<ComputeAllocation, Error = E>,
234 {
235 self.with_optional_compute_allocation(Some(compute_allocation))
236 }
237
238 pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
241 where
242 E: std::fmt::Display,
243 C: TryInto<MemoryAllocation, Error = E>,
244 {
245 Self {
246 memory_allocation: memory_allocation.map(|ma| {
247 ma.try_into()
248 .map_err(|e| AgentError::MessageError(format!("{e}")))
249 }),
250 ..self
251 }
252 }
253
254 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
256 where
257 E: std::fmt::Display,
258 C: TryInto<MemoryAllocation, Error = E>,
259 {
260 self.with_optional_memory_allocation(Some(memory_allocation))
261 }
262
263 pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
266 where
267 E: std::fmt::Display,
268 C: TryInto<FreezingThreshold, Error = E>,
269 {
270 Self {
271 freezing_threshold: freezing_threshold.map(|ma| {
272 ma.try_into()
273 .map_err(|e| AgentError::MessageError(format!("{e}")))
274 }),
275 ..self
276 }
277 }
278
279 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
281 where
282 E: std::fmt::Display,
283 C: TryInto<FreezingThreshold, Error = E>,
284 {
285 self.with_optional_freezing_threshold(Some(freezing_threshold))
286 }
287
288 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
290 where
291 E: std::fmt::Display,
292 C: TryInto<ReservedCyclesLimit, Error = E>,
293 {
294 self.with_optional_reserved_cycles_limit(Some(limit))
295 }
296
297 pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
300 where
301 E: std::fmt::Display,
302 C: TryInto<ReservedCyclesLimit, Error = E>,
303 {
304 Self {
305 reserved_cycles_limit: limit.map(|limit| {
306 limit
307 .try_into()
308 .map_err(|e| AgentError::MessageError(format!("{e}")))
309 }),
310 ..self
311 }
312 }
313
314 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
316 where
317 E: std::fmt::Display,
318 C: TryInto<WasmMemoryLimit, Error = E>,
319 {
320 self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
321 }
322
323 pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
326 where
327 E: std::fmt::Display,
328 C: TryInto<WasmMemoryLimit, Error = E>,
329 {
330 Self {
331 wasm_memory_limit: wasm_memory_limit.map(|limit| {
332 limit
333 .try_into()
334 .map_err(|e| AgentError::MessageError(format!("{e}")))
335 }),
336 ..self
337 }
338 }
339
340 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
342 where
343 E: std::fmt::Display,
344 C: TryInto<WasmMemoryLimit, Error = E>,
345 {
346 self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
347 }
348
349 pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
352 where
353 E: std::fmt::Display,
354 C: TryInto<WasmMemoryLimit, Error = E>,
355 {
356 Self {
357 wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
358 limit
359 .try_into()
360 .map_err(|e| AgentError::MessageError(format!("{e}")))
361 }),
362 ..self
363 }
364 }
365
366 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
368 where
369 E: std::fmt::Display,
370 C: TryInto<LogVisibility, Error = E>,
371 {
372 self.with_optional_log_visibility(Some(log_visibility))
373 }
374
375 pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
378 where
379 E: std::fmt::Display,
380 C: TryInto<LogVisibility, Error = E>,
381 {
382 Self {
383 log_visibility: log_visibility.map(|visibility| {
384 visibility
385 .try_into()
386 .map_err(|e| AgentError::MessageError(format!("{e}")))
387 }),
388 ..self
389 }
390 }
391
392 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
395 let controllers = match self.controllers {
396 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
397 Some(Ok(x)) => Some(x),
398 None => None,
399 };
400 let compute_allocation = match self.compute_allocation {
401 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
402 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
403 None => None,
404 };
405 let memory_allocation = match self.memory_allocation {
406 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
407 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
408 None => None,
409 };
410 let freezing_threshold = match self.freezing_threshold {
411 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
412 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
413 None => None,
414 };
415 let reserved_cycles_limit = match self.reserved_cycles_limit {
416 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
417 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
418 None => None,
419 };
420 let wasm_memory_limit = match self.wasm_memory_limit {
421 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
422 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
423 None => None,
424 };
425 let wasm_memory_threshold = match self.wasm_memory_threshold {
426 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
427 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
428 None => None,
429 };
430 let log_visibility = match self.log_visibility {
431 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
432 Some(Ok(x)) => Some(x),
433 None => None,
434 };
435
436 #[derive(Deserialize, CandidType)]
437 struct Out {
438 canister_id: Principal,
439 }
440
441 let async_builder = if self.is_provisional_create {
442 #[derive(CandidType)]
443 struct In {
444 amount: Option<Nat>,
445 settings: CanisterSettings,
446 specified_id: Option<Principal>,
447 }
448 let in_arg = In {
449 amount: self.amount.map(Nat::from),
450 settings: CanisterSettings {
451 controllers,
452 compute_allocation,
453 memory_allocation,
454 freezing_threshold,
455 reserved_cycles_limit,
456 wasm_memory_limit,
457 wasm_memory_threshold,
458 log_visibility,
459 },
460 specified_id: self.specified_id,
461 };
462 self.canister
463 .update(MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref())
464 .with_arg(in_arg)
465 .with_effective_canister_id(self.effective_canister_id)
466 } else {
467 self.canister
468 .update(MgmtMethod::CreateCanister.as_ref())
469 .with_arg(CanisterSettings {
470 controllers,
471 compute_allocation,
472 memory_allocation,
473 freezing_threshold,
474 reserved_cycles_limit,
475 wasm_memory_limit,
476 wasm_memory_threshold,
477 log_visibility,
478 })
479 .with_effective_canister_id(self.effective_canister_id)
480 };
481
482 Ok(async_builder
483 .build()
484 .map(|result: (Out,)| (result.0.canister_id,)))
485 }
486
487 pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
489 self.build()?.call().await
490 }
491
492 pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
494 self.build()?.call_and_wait().await
495 }
496}
497
498#[cfg_attr(target_family = "wasm", async_trait(?Send))]
499#[cfg_attr(not(target_family = "wasm"), async_trait)]
500impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
501 type Value = (Principal,);
502
503 async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
504 self.build()?.call().await
505 }
506
507 async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
508 self.build()?.call_and_wait().await
509 }
510}
511
512impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
513 type IntoFuture = CallFuture<'agent, (Principal,)>;
514 type Output = Result<(Principal,), AgentError>;
515
516 fn into_future(self) -> Self::IntoFuture {
517 AsyncCall::call_and_wait(self)
518 }
519}
520
521#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash, CandidType, Copy)]
522pub enum WasmMemoryPersistence {
525 #[serde(rename = "keep")]
528 Keep,
529 #[serde(rename = "replace")]
532 Replace,
533}
534
535#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
536pub struct CanisterUpgradeOptions {
538 pub skip_pre_upgrade: Option<bool>,
540 pub wasm_memory_persistence: Option<WasmMemoryPersistence>,
542}
543
544#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
548pub enum InstallMode {
549 #[serde(rename = "install")]
551 Install,
552 #[serde(rename = "reinstall")]
554 Reinstall,
555 #[serde(rename = "upgrade")]
557 Upgrade(Option<CanisterUpgradeOptions>),
558}
559
560#[derive(Debug, Clone, CandidType, Deserialize)]
562pub struct CanisterInstall {
563 pub mode: InstallMode,
565 pub canister_id: Principal,
567 #[serde(with = "serde_bytes")]
569 pub wasm_module: Vec<u8>,
570 #[serde(with = "serde_bytes")]
572 pub arg: Vec<u8>,
573}
574
575impl FromStr for InstallMode {
576 type Err = String;
577
578 fn from_str(s: &str) -> Result<Self, Self::Err> {
579 match s {
580 "install" => Ok(InstallMode::Install),
581 "reinstall" => Ok(InstallMode::Reinstall),
582 "upgrade" => Ok(InstallMode::Upgrade(None)),
583 &_ => Err(format!("Invalid install mode: {s}")),
584 }
585 }
586}
587
588#[derive(Debug)]
590pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
591 canister: &'canister Canister<'agent>,
592 canister_id: Principal,
593 wasm: &'canister [u8],
594 arg: Argument,
595 mode: Option<InstallMode>,
596}
597
598impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
599 pub fn builder(
601 canister: &'canister Canister<'agent>,
602 canister_id: &Principal,
603 wasm: &'canister [u8],
604 ) -> Self {
605 Self {
606 canister,
607 canister_id: *canister_id,
608 wasm,
609 arg: Default::default(),
610 mode: None,
611 }
612 }
613
614 pub fn with_arg<Argument: CandidType>(
617 mut self,
618 arg: Argument,
619 ) -> InstallCodeBuilder<'agent, 'canister> {
620 self.arg.set_idl_arg(arg);
621 self
622 }
623 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
626 assert!(self.arg.0.is_none(), "argument is being set more than once");
627 self.arg = Argument::from_candid(tuple);
628 self
629 }
630 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
632 self.arg.set_raw_arg(arg);
633 self
634 }
635
636 pub fn with_mode(self, mode: InstallMode) -> Self {
638 Self {
639 mode: Some(mode),
640 ..self
641 }
642 }
643
644 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
647 Ok(self
648 .canister
649 .update(MgmtMethod::InstallCode.as_ref())
650 .with_arg(CanisterInstall {
651 mode: self.mode.unwrap_or(InstallMode::Install),
652 canister_id: self.canister_id,
653 wasm_module: self.wasm.to_owned(),
654 arg: self.arg.serialize()?,
655 })
656 .with_effective_canister_id(self.canister_id)
657 .build())
658 }
659
660 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
662 self.build()?.call().await
663 }
664
665 pub async fn call_and_wait(self) -> Result<(), AgentError> {
667 self.build()?.call_and_wait().await
668 }
669}
670
671#[cfg_attr(target_family = "wasm", async_trait(?Send))]
672#[cfg_attr(not(target_family = "wasm"), async_trait)]
673impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
674 type Value = ();
675
676 async fn call(self) -> Result<CallResponse<()>, AgentError> {
677 self.build()?.call().await
678 }
679
680 async fn call_and_wait(self) -> Result<(), AgentError> {
681 self.build()?.call_and_wait().await
682 }
683}
684
685impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
686 type IntoFuture = CallFuture<'agent, ()>;
687 type Output = Result<(), AgentError>;
688
689 fn into_future(self) -> Self::IntoFuture {
690 AsyncCall::call_and_wait(self)
691 }
692}
693
694#[derive(Debug)]
696pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
697 canister: &'canister Canister<'agent>,
698 target_canister: Principal,
699 store_canister: Option<Principal>,
700 chunk_hashes_list: Vec<ChunkHash>,
701 wasm_module_hash: Vec<u8>,
702 arg: Argument,
703 mode: InstallMode,
704}
705
706impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
707 pub fn builder(
709 canister: &'canister Canister<'agent>,
710 target_canister: Principal,
711 wasm_module_hash: &[u8],
712 ) -> Self {
713 Self {
714 canister,
715 target_canister,
716 wasm_module_hash: wasm_module_hash.to_vec(),
717 store_canister: None,
718 chunk_hashes_list: vec![],
719 arg: Argument::new(),
720 mode: InstallMode::Install,
721 }
722 }
723
724 pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
726 self.chunk_hashes_list = chunk_hashes;
727 self
728 }
729
730 pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
732 self.store_canister = Some(store_canister);
733 self
734 }
735
736 pub fn with_arg(mut self, argument: impl CandidType) -> Self {
739 self.arg.set_idl_arg(argument);
740 self
741 }
742
743 pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
746 assert!(self.arg.0.is_none(), "argument is being set more than once");
747 self.arg = Argument::from_candid(argument);
748 self
749 }
750
751 pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
753 self.arg.set_raw_arg(argument);
754 self
755 }
756
757 pub fn with_install_mode(mut self, mode: InstallMode) -> Self {
759 self.mode = mode;
760 self
761 }
762
763 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
765 #[derive(CandidType)]
766 struct In {
767 mode: InstallMode,
768 target_canister: Principal,
769 store_canister: Option<Principal>,
770 chunk_hashes_list: Vec<ChunkHash>,
771 wasm_module_hash: Vec<u8>,
772 arg: Vec<u8>,
773 sender_canister_version: Option<u64>,
774 }
775 let Self {
776 mode,
777 target_canister,
778 store_canister,
779 chunk_hashes_list,
780 wasm_module_hash,
781 arg,
782 ..
783 } = self;
784 Ok(self
785 .canister
786 .update(MgmtMethod::InstallChunkedCode.as_ref())
787 .with_arg(In {
788 mode,
789 target_canister,
790 store_canister,
791 chunk_hashes_list,
792 wasm_module_hash,
793 arg: arg.serialize()?,
794 sender_canister_version: None,
795 })
796 .with_effective_canister_id(target_canister)
797 .build())
798 }
799
800 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
802 self.build()?.call().await
803 }
804
805 pub async fn call_and_wait(self) -> Result<(), AgentError> {
807 self.build()?.call_and_wait().await
808 }
809}
810
811#[cfg_attr(target_family = "wasm", async_trait(?Send))]
812#[cfg_attr(not(target_family = "wasm"), async_trait)]
813impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
814 type Value = ();
815
816 async fn call(self) -> Result<CallResponse<()>, AgentError> {
817 self.call().await
818 }
819
820 async fn call_and_wait(self) -> Result<(), AgentError> {
821 self.call_and_wait().await
822 }
823}
824
825impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
826 type IntoFuture = CallFuture<'agent, ()>;
827 type Output = Result<(), AgentError>;
828
829 fn into_future(self) -> Self::IntoFuture {
830 AsyncCall::call_and_wait(self)
831 }
832}
833
834#[derive(Debug)]
840pub struct InstallBuilder<'agent, 'canister, 'builder> {
841 canister: &'canister ManagementCanister<'agent>,
842 canister_id: Principal,
843 wasm: &'builder [u8],
846 arg: Argument,
847 mode: InstallMode,
848}
849
850impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
851 const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
854
855 pub fn builder(
857 canister: &'canister ManagementCanister<'agent>,
858 canister_id: &Principal,
859 wasm: &'builder [u8],
860 ) -> Self {
861 Self {
862 canister,
863 canister_id: *canister_id,
864 wasm,
865 arg: Default::default(),
866 mode: InstallMode::Install,
867 }
868 }
869
870 pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
873 self.arg.set_idl_arg(arg);
874 self
875 }
876 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
879 assert!(self.arg.0.is_none(), "argument is being set more than once");
880 self.arg = Argument::from_candid(tuple);
881 self
882 }
883 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
885 self.arg.set_raw_arg(arg);
886 self
887 }
888
889 pub fn with_mode(self, mode: InstallMode) -> Self {
891 Self { mode, ..self }
892 }
893
894 pub async fn call_and_wait(self) -> Result<(), AgentError> {
897 self.call_and_wait_with_progress()
898 .await
899 .try_for_each(|_| ready(Ok(())))
900 .await
901 }
902
903 pub async fn call_and_wait_with_progress(
907 self,
908 ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
909 let stream_res = async move {
910 let arg = self.arg.serialize()?;
911 let stream: BoxStream<'_, _> =
912 if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
913 Box::pin(
914 async move {
915 self.canister
916 .install_code(&self.canister_id, self.wasm)
917 .with_raw_arg(arg)
918 .with_mode(self.mode)
919 .call_and_wait()
920 .await
921 }
922 .into_stream(),
923 )
924 } else {
925 let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
926 let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
927 let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
928 let mut to_upload_chunks = vec![];
929 for (hash, chunk) in &all_chunks {
930 if !existing_chunks.contains(hash) {
931 to_upload_chunks.push(*chunk);
932 }
933 }
934
935 let upload_chunks_stream = FuturesUnordered::new();
936 for chunk in to_upload_chunks {
937 upload_chunks_stream.push(async move {
938 let (_res,) = self
939 .canister
940 .upload_chunk(&self.canister_id, chunk)
941 .call_and_wait()
942 .await?;
943 Ok(())
944 });
945 }
946 let install_chunked_code_stream = async move {
947 let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
948 self.canister
949 .install_chunked_code(
950 &self.canister_id,
951 &Sha256::digest(self.wasm),
952 )
953 .with_chunk_hashes(results)
954 .with_raw_arg(arg)
955 .with_install_mode(self.mode)
956 .call_and_wait()
957 .await
958 }
959 .into_stream();
960 let clear_store_stream = async move {
961 self.canister
962 .clear_chunk_store(&self.canister_id)
963 .call_and_wait()
964 .await
965 }
966 .into_stream();
967
968 Box::pin(
969 upload_chunks_stream
970 .chain(install_chunked_code_stream)
971 .chain(clear_store_stream ),
972 )
973 };
974 Ok(stream)
975 }.await;
976 match stream_res {
977 Ok(stream) => stream,
978 Err(err) => Box::pin(stream::once(async { Err(err) })),
979 }
980 }
981}
982
983impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
984 for InstallBuilder<'agent, 'canister, 'builder>
985{
986 type IntoFuture = CallFuture<'builder, ()>;
987 type Output = Result<(), AgentError>;
988
989 fn into_future(self) -> Self::IntoFuture {
990 Box::pin(self.call_and_wait())
991 }
992}
993
994#[derive(Debug)]
996pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
997 canister: &'canister Canister<'agent>,
998 canister_id: Principal,
999 controllers: Option<Result<Vec<Principal>, AgentError>>,
1000 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
1001 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1002 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1003 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1004 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1005 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1006 log_visibility: Option<Result<LogVisibility, AgentError>>,
1007}
1008
1009impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
1010 pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1012 Self {
1013 canister,
1014 canister_id: *canister_id,
1015 controllers: None,
1016 compute_allocation: None,
1017 memory_allocation: None,
1018 freezing_threshold: None,
1019 reserved_cycles_limit: None,
1020 wasm_memory_limit: None,
1021 wasm_memory_threshold: None,
1022 log_visibility: None,
1023 }
1024 }
1025
1026 pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
1029 where
1030 E: std::fmt::Display,
1031 C: TryInto<Principal, Error = E>,
1032 {
1033 let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
1034 ca.try_into()
1035 .map_err(|e| AgentError::MessageError(format!("{e}")))
1036 });
1037 let controllers: Option<Result<Vec<Principal>, _>> =
1038 match (controller_to_add, self.controllers) {
1039 (_, Some(Err(sticky))) => Some(Err(sticky)),
1040 (Some(Err(e)), _) => Some(Err(e)),
1041 (None, _) => None,
1042 (Some(Ok(controller)), Some(Ok(controllers))) => {
1043 let mut controllers = controllers;
1044 controllers.push(controller);
1045 Some(Ok(controllers))
1046 }
1047 (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
1048 };
1049
1050 Self {
1051 controllers,
1052 ..self
1053 }
1054 }
1055
1056 pub fn with_controller<C, E>(self, controller: C) -> Self
1058 where
1059 E: std::fmt::Display,
1060 C: TryInto<Principal, Error = E>,
1061 {
1062 self.with_optional_controller(Some(controller))
1063 }
1064
1065 pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
1068 where
1069 E: std::fmt::Display,
1070 C: TryInto<ComputeAllocation, Error = E>,
1071 {
1072 Self {
1073 compute_allocation: compute_allocation.map(|ca| {
1074 ca.try_into()
1075 .map_err(|e| AgentError::MessageError(format!("{e}")))
1076 }),
1077 ..self
1078 }
1079 }
1080
1081 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1083 where
1084 E: std::fmt::Display,
1085 C: TryInto<ComputeAllocation, Error = E>,
1086 {
1087 self.with_optional_compute_allocation(Some(compute_allocation))
1088 }
1089
1090 pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
1093 where
1094 E: std::fmt::Display,
1095 C: TryInto<MemoryAllocation, Error = E>,
1096 {
1097 Self {
1098 memory_allocation: memory_allocation.map(|ma| {
1099 ma.try_into()
1100 .map_err(|e| AgentError::MessageError(format!("{e}")))
1101 }),
1102 ..self
1103 }
1104 }
1105
1106 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1108 where
1109 E: std::fmt::Display,
1110 C: TryInto<MemoryAllocation, Error = E>,
1111 {
1112 self.with_optional_memory_allocation(Some(memory_allocation))
1113 }
1114
1115 pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
1118 where
1119 E: std::fmt::Display,
1120 C: TryInto<FreezingThreshold, Error = E>,
1121 {
1122 Self {
1123 freezing_threshold: freezing_threshold.map(|ma| {
1124 ma.try_into()
1125 .map_err(|e| AgentError::MessageError(format!("{e}")))
1126 }),
1127 ..self
1128 }
1129 }
1130
1131 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1133 where
1134 E: std::fmt::Display,
1135 C: TryInto<FreezingThreshold, Error = E>,
1136 {
1137 self.with_optional_freezing_threshold(Some(freezing_threshold))
1138 }
1139
1140 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1142 where
1143 E: std::fmt::Display,
1144 C: TryInto<ReservedCyclesLimit, Error = E>,
1145 {
1146 self.with_optional_reserved_cycles_limit(Some(limit))
1147 }
1148
1149 pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
1152 where
1153 E: std::fmt::Display,
1154 C: TryInto<ReservedCyclesLimit, Error = E>,
1155 {
1156 Self {
1157 reserved_cycles_limit: limit.map(|ma| {
1158 ma.try_into()
1159 .map_err(|e| AgentError::MessageError(format!("{e}")))
1160 }),
1161 ..self
1162 }
1163 }
1164
1165 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1167 where
1168 E: std::fmt::Display,
1169 C: TryInto<WasmMemoryLimit, Error = E>,
1170 {
1171 self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
1172 }
1173
1174 pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
1177 where
1178 E: std::fmt::Display,
1179 C: TryInto<WasmMemoryLimit, Error = E>,
1180 {
1181 Self {
1182 wasm_memory_limit: wasm_memory_limit.map(|limit| {
1183 limit
1184 .try_into()
1185 .map_err(|e| AgentError::MessageError(format!("{e}")))
1186 }),
1187 ..self
1188 }
1189 }
1190
1191 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1193 where
1194 E: std::fmt::Display,
1195 C: TryInto<WasmMemoryLimit, Error = E>,
1196 {
1197 self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
1198 }
1199
1200 pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
1203 where
1204 E: std::fmt::Display,
1205 C: TryInto<WasmMemoryLimit, Error = E>,
1206 {
1207 Self {
1208 wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
1209 limit
1210 .try_into()
1211 .map_err(|e| AgentError::MessageError(format!("{e}")))
1212 }),
1213 ..self
1214 }
1215 }
1216
1217 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1219 where
1220 E: std::fmt::Display,
1221 C: TryInto<LogVisibility, Error = E>,
1222 {
1223 self.with_optional_log_visibility(Some(log_visibility))
1224 }
1225
1226 pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
1229 where
1230 E: std::fmt::Display,
1231 C: TryInto<LogVisibility, Error = E>,
1232 {
1233 Self {
1234 log_visibility: log_visibility.map(|limit| {
1235 limit
1236 .try_into()
1237 .map_err(|e| AgentError::MessageError(format!("{e}")))
1238 }),
1239 ..self
1240 }
1241 }
1242
1243 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1246 #[derive(CandidType)]
1247 struct In {
1248 canister_id: Principal,
1249 settings: CanisterSettings,
1250 }
1251
1252 let controllers = match self.controllers {
1253 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1254 Some(Ok(x)) => Some(x),
1255 None => None,
1256 };
1257 let compute_allocation = match self.compute_allocation {
1258 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1259 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1260 None => None,
1261 };
1262 let memory_allocation = match self.memory_allocation {
1263 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1264 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1265 None => None,
1266 };
1267 let freezing_threshold = match self.freezing_threshold {
1268 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1269 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1270 None => None,
1271 };
1272 let reserved_cycles_limit = match self.reserved_cycles_limit {
1273 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1274 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1275 None => None,
1276 };
1277 let wasm_memory_limit = match self.wasm_memory_limit {
1278 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1279 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1280 None => None,
1281 };
1282 let wasm_memory_threshold = match self.wasm_memory_threshold {
1283 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1284 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1285 None => None,
1286 };
1287 let log_visibility = match self.log_visibility {
1288 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1289 Some(Ok(x)) => Some(x),
1290 None => None,
1291 };
1292
1293 Ok(self
1294 .canister
1295 .update(MgmtMethod::UpdateSettings.as_ref())
1296 .with_arg(In {
1297 canister_id: self.canister_id,
1298 settings: CanisterSettings {
1299 controllers,
1300 compute_allocation,
1301 memory_allocation,
1302 freezing_threshold,
1303 reserved_cycles_limit,
1304 wasm_memory_limit,
1305 wasm_memory_threshold,
1306 log_visibility,
1307 },
1308 })
1309 .with_effective_canister_id(self.canister_id)
1310 .build())
1311 }
1312
1313 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1315 self.build()?.call().await
1316 }
1317
1318 pub async fn call_and_wait(self) -> Result<(), AgentError> {
1320 self.build()?.call_and_wait().await
1321 }
1322}
1323
1324#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1325#[cfg_attr(not(target_family = "wasm"), async_trait)]
1326impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> {
1327 type Value = ();
1328 async fn call(self) -> Result<CallResponse<()>, AgentError> {
1329 self.build()?.call().await
1330 }
1331
1332 async fn call_and_wait(self) -> Result<(), AgentError> {
1333 self.build()?.call_and_wait().await
1334 }
1335}
1336
1337impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> {
1338 type IntoFuture = CallFuture<'agent, ()>;
1339 type Output = Result<(), AgentError>;
1340 fn into_future(self) -> Self::IntoFuture {
1341 AsyncCall::call_and_wait(self)
1342 }
1343}
1344
1345#[cfg(not(target_family = "wasm"))]
1346type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1347#[cfg(target_family = "wasm")]
1348type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;