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 Keep,
528 Replace,
531}
532
533#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
534pub struct CanisterUpgradeOptions {
536 pub skip_pre_upgrade: Option<bool>,
538 pub wasm_memory_persistence: Option<WasmMemoryPersistence>,
540}
541
542#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
546pub enum InstallMode {
547 #[serde(rename = "install")]
549 Install,
550 #[serde(rename = "reinstall")]
552 Reinstall,
553 #[serde(rename = "upgrade")]
555 Upgrade(Option<CanisterUpgradeOptions>),
556}
557
558#[derive(Debug, Clone, CandidType, Deserialize)]
560pub struct CanisterInstall {
561 pub mode: InstallMode,
563 pub canister_id: Principal,
565 #[serde(with = "serde_bytes")]
567 pub wasm_module: Vec<u8>,
568 #[serde(with = "serde_bytes")]
570 pub arg: Vec<u8>,
571}
572
573impl FromStr for InstallMode {
574 type Err = String;
575
576 fn from_str(s: &str) -> Result<Self, Self::Err> {
577 match s {
578 "install" => Ok(InstallMode::Install),
579 "reinstall" => Ok(InstallMode::Reinstall),
580 "upgrade" => Ok(InstallMode::Upgrade(None)),
581 &_ => Err(format!("Invalid install mode: {s}")),
582 }
583 }
584}
585
586#[derive(Debug)]
588pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
589 canister: &'canister Canister<'agent>,
590 canister_id: Principal,
591 wasm: &'canister [u8],
592 arg: Argument,
593 mode: Option<InstallMode>,
594}
595
596impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
597 pub fn builder(
599 canister: &'canister Canister<'agent>,
600 canister_id: &Principal,
601 wasm: &'canister [u8],
602 ) -> Self {
603 Self {
604 canister,
605 canister_id: *canister_id,
606 wasm,
607 arg: Default::default(),
608 mode: None,
609 }
610 }
611
612 pub fn with_arg<Argument: CandidType>(
615 mut self,
616 arg: Argument,
617 ) -> InstallCodeBuilder<'agent, 'canister> {
618 self.arg.set_idl_arg(arg);
619 self
620 }
621 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
624 assert!(self.arg.0.is_none(), "argument is being set more than once");
625 self.arg = Argument::from_candid(tuple);
626 self
627 }
628 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
630 self.arg.set_raw_arg(arg);
631 self
632 }
633
634 pub fn with_mode(self, mode: InstallMode) -> Self {
636 Self {
637 mode: Some(mode),
638 ..self
639 }
640 }
641
642 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
645 Ok(self
646 .canister
647 .update(MgmtMethod::InstallCode.as_ref())
648 .with_arg(CanisterInstall {
649 mode: self.mode.unwrap_or(InstallMode::Install),
650 canister_id: self.canister_id,
651 wasm_module: self.wasm.to_owned(),
652 arg: self.arg.serialize()?,
653 })
654 .with_effective_canister_id(self.canister_id)
655 .build())
656 }
657
658 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
660 self.build()?.call().await
661 }
662
663 pub async fn call_and_wait(self) -> Result<(), AgentError> {
665 self.build()?.call_and_wait().await
666 }
667}
668
669#[cfg_attr(target_family = "wasm", async_trait(?Send))]
670#[cfg_attr(not(target_family = "wasm"), async_trait)]
671impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
672 type Value = ();
673
674 async fn call(self) -> Result<CallResponse<()>, AgentError> {
675 self.build()?.call().await
676 }
677
678 async fn call_and_wait(self) -> Result<(), AgentError> {
679 self.build()?.call_and_wait().await
680 }
681}
682
683impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
684 type IntoFuture = CallFuture<'agent, ()>;
685 type Output = Result<(), AgentError>;
686
687 fn into_future(self) -> Self::IntoFuture {
688 AsyncCall::call_and_wait(self)
689 }
690}
691
692#[derive(Debug)]
694pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
695 canister: &'canister Canister<'agent>,
696 target_canister: Principal,
697 store_canister: Option<Principal>,
698 chunk_hashes_list: Vec<ChunkHash>,
699 wasm_module_hash: Vec<u8>,
700 arg: Argument,
701 mode: InstallMode,
702}
703
704impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
705 pub fn builder(
707 canister: &'canister Canister<'agent>,
708 target_canister: Principal,
709 wasm_module_hash: &[u8],
710 ) -> Self {
711 Self {
712 canister,
713 target_canister,
714 wasm_module_hash: wasm_module_hash.to_vec(),
715 store_canister: None,
716 chunk_hashes_list: vec![],
717 arg: Argument::new(),
718 mode: InstallMode::Install,
719 }
720 }
721
722 pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
724 self.chunk_hashes_list = chunk_hashes;
725 self
726 }
727
728 pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
730 self.store_canister = Some(store_canister);
731 self
732 }
733
734 pub fn with_arg(mut self, argument: impl CandidType) -> Self {
737 self.arg.set_idl_arg(argument);
738 self
739 }
740
741 pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
744 assert!(self.arg.0.is_none(), "argument is being set more than once");
745 self.arg = Argument::from_candid(argument);
746 self
747 }
748
749 pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
751 self.arg.set_raw_arg(argument);
752 self
753 }
754
755 pub fn with_install_mode(mut self, mode: InstallMode) -> Self {
757 self.mode = mode;
758 self
759 }
760
761 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
763 #[derive(CandidType)]
764 struct In {
765 mode: InstallMode,
766 target_canister: Principal,
767 store_canister: Option<Principal>,
768 chunk_hashes_list: Vec<ChunkHash>,
769 wasm_module_hash: Vec<u8>,
770 arg: Vec<u8>,
771 sender_canister_version: Option<u64>,
772 }
773 let Self {
774 mode,
775 target_canister,
776 store_canister,
777 chunk_hashes_list,
778 wasm_module_hash,
779 arg,
780 ..
781 } = self;
782 Ok(self
783 .canister
784 .update(MgmtMethod::InstallChunkedCode.as_ref())
785 .with_arg(In {
786 mode,
787 target_canister,
788 store_canister,
789 chunk_hashes_list,
790 wasm_module_hash,
791 arg: arg.serialize()?,
792 sender_canister_version: None,
793 })
794 .with_effective_canister_id(target_canister)
795 .build())
796 }
797
798 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
800 self.build()?.call().await
801 }
802
803 pub async fn call_and_wait(self) -> Result<(), AgentError> {
805 self.build()?.call_and_wait().await
806 }
807}
808
809#[cfg_attr(target_family = "wasm", async_trait(?Send))]
810#[cfg_attr(not(target_family = "wasm"), async_trait)]
811impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
812 type Value = ();
813
814 async fn call(self) -> Result<CallResponse<()>, AgentError> {
815 self.call().await
816 }
817
818 async fn call_and_wait(self) -> Result<(), AgentError> {
819 self.call_and_wait().await
820 }
821}
822
823impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
824 type IntoFuture = CallFuture<'agent, ()>;
825 type Output = Result<(), AgentError>;
826
827 fn into_future(self) -> Self::IntoFuture {
828 AsyncCall::call_and_wait(self)
829 }
830}
831
832#[derive(Debug)]
838pub struct InstallBuilder<'agent, 'canister, 'builder> {
839 canister: &'canister ManagementCanister<'agent>,
840 canister_id: Principal,
841 wasm: &'builder [u8],
844 arg: Argument,
845 mode: InstallMode,
846}
847
848impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
849 const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
852
853 pub fn builder(
855 canister: &'canister ManagementCanister<'agent>,
856 canister_id: &Principal,
857 wasm: &'builder [u8],
858 ) -> Self {
859 Self {
860 canister,
861 canister_id: *canister_id,
862 wasm,
863 arg: Default::default(),
864 mode: InstallMode::Install,
865 }
866 }
867
868 pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
871 self.arg.set_idl_arg(arg);
872 self
873 }
874 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
877 assert!(self.arg.0.is_none(), "argument is being set more than once");
878 self.arg = Argument::from_candid(tuple);
879 self
880 }
881 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
883 self.arg.set_raw_arg(arg);
884 self
885 }
886
887 pub fn with_mode(self, mode: InstallMode) -> Self {
889 Self { mode, ..self }
890 }
891
892 pub async fn call_and_wait(self) -> Result<(), AgentError> {
895 self.call_and_wait_with_progress()
896 .await
897 .try_for_each(|_| ready(Ok(())))
898 .await
899 }
900
901 pub async fn call_and_wait_with_progress(
905 self,
906 ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
907 let stream_res = async move {
908 let arg = self.arg.serialize()?;
909 let stream: BoxStream<'_, _> =
910 if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
911 Box::pin(
912 async move {
913 self.canister
914 .install_code(&self.canister_id, self.wasm)
915 .with_raw_arg(arg)
916 .with_mode(self.mode)
917 .call_and_wait()
918 .await
919 }
920 .into_stream(),
921 )
922 } else {
923 let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
924 let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
925 let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
926 let mut to_upload_chunks = vec![];
927 for (hash, chunk) in &all_chunks {
928 if !existing_chunks.contains(hash) {
929 to_upload_chunks.push(*chunk);
930 }
931 }
932
933 let upload_chunks_stream = FuturesUnordered::new();
934 for chunk in to_upload_chunks {
935 upload_chunks_stream.push(async move {
936 let (_res,) = self
937 .canister
938 .upload_chunk(&self.canister_id, chunk)
939 .call_and_wait()
940 .await?;
941 Ok(())
942 });
943 }
944 let install_chunked_code_stream = async move {
945 let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
946 self.canister
947 .install_chunked_code(
948 &self.canister_id,
949 &Sha256::digest(self.wasm),
950 )
951 .with_chunk_hashes(results)
952 .with_raw_arg(arg)
953 .with_install_mode(self.mode)
954 .call_and_wait()
955 .await
956 }
957 .into_stream();
958 let clear_store_stream = async move {
959 self.canister
960 .clear_chunk_store(&self.canister_id)
961 .call_and_wait()
962 .await
963 }
964 .into_stream();
965
966 Box::pin(
967 upload_chunks_stream
968 .chain(install_chunked_code_stream)
969 .chain(clear_store_stream ),
970 )
971 };
972 Ok(stream)
973 }.await;
974 match stream_res {
975 Ok(stream) => stream,
976 Err(err) => Box::pin(stream::once(async { Err(err) })),
977 }
978 }
979}
980
981impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
982 for InstallBuilder<'agent, 'canister, 'builder>
983{
984 type IntoFuture = CallFuture<'builder, ()>;
985 type Output = Result<(), AgentError>;
986
987 fn into_future(self) -> Self::IntoFuture {
988 Box::pin(self.call_and_wait())
989 }
990}
991
992#[derive(Debug)]
994pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
995 canister: &'canister Canister<'agent>,
996 canister_id: Principal,
997 controllers: Option<Result<Vec<Principal>, AgentError>>,
998 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
999 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1000 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1001 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1002 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1003 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1004 log_visibility: Option<Result<LogVisibility, AgentError>>,
1005}
1006
1007impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
1008 pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1010 Self {
1011 canister,
1012 canister_id: *canister_id,
1013 controllers: None,
1014 compute_allocation: None,
1015 memory_allocation: None,
1016 freezing_threshold: None,
1017 reserved_cycles_limit: None,
1018 wasm_memory_limit: None,
1019 wasm_memory_threshold: None,
1020 log_visibility: None,
1021 }
1022 }
1023
1024 pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
1027 where
1028 E: std::fmt::Display,
1029 C: TryInto<Principal, Error = E>,
1030 {
1031 let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
1032 ca.try_into()
1033 .map_err(|e| AgentError::MessageError(format!("{e}")))
1034 });
1035 let controllers: Option<Result<Vec<Principal>, _>> =
1036 match (controller_to_add, self.controllers) {
1037 (_, Some(Err(sticky))) => Some(Err(sticky)),
1038 (Some(Err(e)), _) => Some(Err(e)),
1039 (None, _) => None,
1040 (Some(Ok(controller)), Some(Ok(controllers))) => {
1041 let mut controllers = controllers;
1042 controllers.push(controller);
1043 Some(Ok(controllers))
1044 }
1045 (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
1046 };
1047
1048 Self {
1049 controllers,
1050 ..self
1051 }
1052 }
1053
1054 pub fn with_controller<C, E>(self, controller: C) -> Self
1056 where
1057 E: std::fmt::Display,
1058 C: TryInto<Principal, Error = E>,
1059 {
1060 self.with_optional_controller(Some(controller))
1061 }
1062
1063 pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
1066 where
1067 E: std::fmt::Display,
1068 C: TryInto<ComputeAllocation, Error = E>,
1069 {
1070 Self {
1071 compute_allocation: compute_allocation.map(|ca| {
1072 ca.try_into()
1073 .map_err(|e| AgentError::MessageError(format!("{e}")))
1074 }),
1075 ..self
1076 }
1077 }
1078
1079 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1081 where
1082 E: std::fmt::Display,
1083 C: TryInto<ComputeAllocation, Error = E>,
1084 {
1085 self.with_optional_compute_allocation(Some(compute_allocation))
1086 }
1087
1088 pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
1091 where
1092 E: std::fmt::Display,
1093 C: TryInto<MemoryAllocation, Error = E>,
1094 {
1095 Self {
1096 memory_allocation: memory_allocation.map(|ma| {
1097 ma.try_into()
1098 .map_err(|e| AgentError::MessageError(format!("{e}")))
1099 }),
1100 ..self
1101 }
1102 }
1103
1104 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1106 where
1107 E: std::fmt::Display,
1108 C: TryInto<MemoryAllocation, Error = E>,
1109 {
1110 self.with_optional_memory_allocation(Some(memory_allocation))
1111 }
1112
1113 pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
1116 where
1117 E: std::fmt::Display,
1118 C: TryInto<FreezingThreshold, Error = E>,
1119 {
1120 Self {
1121 freezing_threshold: freezing_threshold.map(|ma| {
1122 ma.try_into()
1123 .map_err(|e| AgentError::MessageError(format!("{e}")))
1124 }),
1125 ..self
1126 }
1127 }
1128
1129 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1131 where
1132 E: std::fmt::Display,
1133 C: TryInto<FreezingThreshold, Error = E>,
1134 {
1135 self.with_optional_freezing_threshold(Some(freezing_threshold))
1136 }
1137
1138 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1140 where
1141 E: std::fmt::Display,
1142 C: TryInto<ReservedCyclesLimit, Error = E>,
1143 {
1144 self.with_optional_reserved_cycles_limit(Some(limit))
1145 }
1146
1147 pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
1150 where
1151 E: std::fmt::Display,
1152 C: TryInto<ReservedCyclesLimit, Error = E>,
1153 {
1154 Self {
1155 reserved_cycles_limit: limit.map(|ma| {
1156 ma.try_into()
1157 .map_err(|e| AgentError::MessageError(format!("{e}")))
1158 }),
1159 ..self
1160 }
1161 }
1162
1163 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1165 where
1166 E: std::fmt::Display,
1167 C: TryInto<WasmMemoryLimit, Error = E>,
1168 {
1169 self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
1170 }
1171
1172 pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
1175 where
1176 E: std::fmt::Display,
1177 C: TryInto<WasmMemoryLimit, Error = E>,
1178 {
1179 Self {
1180 wasm_memory_limit: wasm_memory_limit.map(|limit| {
1181 limit
1182 .try_into()
1183 .map_err(|e| AgentError::MessageError(format!("{e}")))
1184 }),
1185 ..self
1186 }
1187 }
1188
1189 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1191 where
1192 E: std::fmt::Display,
1193 C: TryInto<WasmMemoryLimit, Error = E>,
1194 {
1195 self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
1196 }
1197
1198 pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
1201 where
1202 E: std::fmt::Display,
1203 C: TryInto<WasmMemoryLimit, Error = E>,
1204 {
1205 Self {
1206 wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
1207 limit
1208 .try_into()
1209 .map_err(|e| AgentError::MessageError(format!("{e}")))
1210 }),
1211 ..self
1212 }
1213 }
1214
1215 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1217 where
1218 E: std::fmt::Display,
1219 C: TryInto<LogVisibility, Error = E>,
1220 {
1221 self.with_optional_log_visibility(Some(log_visibility))
1222 }
1223
1224 pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
1227 where
1228 E: std::fmt::Display,
1229 C: TryInto<LogVisibility, Error = E>,
1230 {
1231 Self {
1232 log_visibility: log_visibility.map(|limit| {
1233 limit
1234 .try_into()
1235 .map_err(|e| AgentError::MessageError(format!("{e}")))
1236 }),
1237 ..self
1238 }
1239 }
1240
1241 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1244 #[derive(CandidType)]
1245 struct In {
1246 canister_id: Principal,
1247 settings: CanisterSettings,
1248 }
1249
1250 let controllers = match self.controllers {
1251 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1252 Some(Ok(x)) => Some(x),
1253 None => None,
1254 };
1255 let compute_allocation = match self.compute_allocation {
1256 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1257 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1258 None => None,
1259 };
1260 let memory_allocation = match self.memory_allocation {
1261 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1262 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1263 None => None,
1264 };
1265 let freezing_threshold = match self.freezing_threshold {
1266 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1267 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1268 None => None,
1269 };
1270 let reserved_cycles_limit = match self.reserved_cycles_limit {
1271 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1272 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1273 None => None,
1274 };
1275 let wasm_memory_limit = match self.wasm_memory_limit {
1276 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1277 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1278 None => None,
1279 };
1280 let wasm_memory_threshold = match self.wasm_memory_threshold {
1281 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1282 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1283 None => None,
1284 };
1285 let log_visibility = match self.log_visibility {
1286 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1287 Some(Ok(x)) => Some(x),
1288 None => None,
1289 };
1290
1291 Ok(self
1292 .canister
1293 .update(MgmtMethod::UpdateSettings.as_ref())
1294 .with_arg(In {
1295 canister_id: self.canister_id,
1296 settings: CanisterSettings {
1297 controllers,
1298 compute_allocation,
1299 memory_allocation,
1300 freezing_threshold,
1301 reserved_cycles_limit,
1302 wasm_memory_limit,
1303 wasm_memory_threshold,
1304 log_visibility,
1305 },
1306 })
1307 .with_effective_canister_id(self.canister_id)
1308 .build())
1309 }
1310
1311 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1313 self.build()?.call().await
1314 }
1315
1316 pub async fn call_and_wait(self) -> Result<(), AgentError> {
1318 self.build()?.call_and_wait().await
1319 }
1320}
1321
1322#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1323#[cfg_attr(not(target_family = "wasm"), async_trait)]
1324impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> {
1325 type Value = ();
1326 async fn call(self) -> Result<CallResponse<()>, AgentError> {
1327 self.build()?.call().await
1328 }
1329
1330 async fn call_and_wait(self) -> Result<(), AgentError> {
1331 self.build()?.call_and_wait().await
1332 }
1333}
1334
1335impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> {
1336 type IntoFuture = CallFuture<'agent, ()>;
1337 type Output = Result<(), AgentError>;
1338 fn into_future(self) -> Self::IntoFuture {
1339 AsyncCall::call_and_wait(self)
1340 }
1341}
1342
1343#[cfg(not(target_family = "wasm"))]
1344type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1345#[cfg(target_family = "wasm")]
1346type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;