1#[doc(inline)]
4pub use super::attributes::{
5 ComputeAllocation, FreezingThreshold, LogMemoryLimit, MemoryAllocation, ReservedCyclesLimit,
6 WasmMemoryLimit,
7};
8use super::{ChunkHash, LogVisibility, ManagementCanister};
9use crate::call::CallFuture;
10use crate::{
11 call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
12};
13use async_trait::async_trait;
14use candid::{decode_args, utils::ArgumentEncoder, CandidType, Deserialize, Encode, Nat};
15use futures_util::{
16 future::ready,
17 stream::{self, FuturesUnordered},
18 FutureExt, Stream, StreamExt, TryStreamExt,
19};
20use ic_agent::{
21 agent::{CallResponse, EffectiveId},
22 export::Principal,
23 AgentError,
24};
25pub use ic_management_canister_types::{
26 CanisterInstallMode, CanisterSettings, EnvironmentVariable, InstallCodeArgs, UpgradeFlags,
27 WasmMemoryPersistence,
28};
29use sha2::{Digest, Sha256};
30use std::{
31 collections::BTreeSet,
32 convert::{From, TryInto},
33 future::IntoFuture,
34 pin::Pin,
35};
36
37#[derive(Debug)]
39pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
40 canister: &'canister Canister<'agent>,
41 effective_id: EffectiveId,
44 controllers: Option<Result<Vec<Principal>, AgentError>>,
45 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
46 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
47 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
48 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
49 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
50 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
51 log_visibility: Option<Result<LogVisibility, AgentError>>,
52 log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
53 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
54 is_provisional_create: bool,
55 amount: Option<u128>,
56 specified_id: Option<Principal>,
57}
58
59impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
60 pub fn builder(canister: &'canister Canister<'agent>) -> Self {
62 Self {
63 canister,
64 effective_id: EffectiveId::Canister(Principal::management_canister()),
65 controllers: None,
66 compute_allocation: None,
67 memory_allocation: None,
68 freezing_threshold: None,
69 reserved_cycles_limit: None,
70 wasm_memory_limit: None,
71 wasm_memory_threshold: None,
72 log_visibility: None,
73 log_memory_limit: None,
74 environment_variables: None,
75 is_provisional_create: false,
76 amount: None,
77 specified_id: None,
78 }
79 }
80
81 #[allow(clippy::wrong_self_convention)]
88 pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
89 Self {
90 is_provisional_create: true,
91 amount,
92 ..self
93 }
94 }
95
96 pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
105 Self {
106 is_provisional_create: true,
107 specified_id: Some(specified_id),
108 effective_id: EffectiveId::Canister(specified_id),
109 ..self
110 }
111 }
112
113 pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
121 where
122 E: std::fmt::Display,
123 C: TryInto<Principal, Error = E>,
124 {
125 match effective_canister_id.try_into() {
126 Ok(effective_canister_id) => Self {
127 effective_id: EffectiveId::Canister(effective_canister_id),
128 ..self
129 },
130 Err(_) => self,
131 }
132 }
133
134 pub fn with_effective_subnet_id<C, E>(self, effective_subnet_id: C) -> Self
144 where
145 E: std::fmt::Display,
146 C: TryInto<Principal, Error = E>,
147 {
148 match effective_subnet_id.try_into() {
149 Ok(subnet_id) => Self {
150 effective_id: EffectiveId::Subnet(subnet_id),
151 ..self
152 },
153 Err(_) => self,
154 }
155 }
156
157 pub fn with_controller<C, E>(self, controller: C) -> Self
159 where
160 E: std::fmt::Display,
161 C: TryInto<Principal, Error = E>,
162 {
163 let controller_result = controller
164 .try_into()
165 .map_err(|e| AgentError::MessageError(format!("{e}")));
166 let controllers = match (controller_result, self.controllers) {
167 (_, Some(Err(sticky))) => Some(Err(sticky)),
168 (Err(e), _) => Some(Err(e)),
169 (Ok(controller), Some(Ok(mut controllers))) => {
170 controllers.push(controller);
171 Some(Ok(controllers))
172 }
173 (Ok(controller), None) => Some(Ok(vec![controller])),
174 };
175 Self {
176 controllers,
177 ..self
178 }
179 }
180
181 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
183 where
184 E: std::fmt::Display,
185 C: TryInto<ComputeAllocation, Error = E>,
186 {
187 Self {
188 compute_allocation: Some(
189 compute_allocation
190 .try_into()
191 .map_err(|e| AgentError::MessageError(format!("{e}"))),
192 ),
193 ..self
194 }
195 }
196
197 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
199 where
200 E: std::fmt::Display,
201 C: TryInto<MemoryAllocation, Error = E>,
202 {
203 Self {
204 memory_allocation: Some(
205 memory_allocation
206 .try_into()
207 .map_err(|e| AgentError::MessageError(format!("{e}"))),
208 ),
209 ..self
210 }
211 }
212
213 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
215 where
216 E: std::fmt::Display,
217 C: TryInto<FreezingThreshold, Error = E>,
218 {
219 Self {
220 freezing_threshold: Some(
221 freezing_threshold
222 .try_into()
223 .map_err(|e| AgentError::MessageError(format!("{e}"))),
224 ),
225 ..self
226 }
227 }
228
229 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
231 where
232 E: std::fmt::Display,
233 C: TryInto<ReservedCyclesLimit, Error = E>,
234 {
235 Self {
236 reserved_cycles_limit: Some(
237 limit
238 .try_into()
239 .map_err(|e| AgentError::MessageError(format!("{e}"))),
240 ),
241 ..self
242 }
243 }
244
245 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
247 where
248 E: std::fmt::Display,
249 C: TryInto<WasmMemoryLimit, Error = E>,
250 {
251 Self {
252 wasm_memory_limit: Some(
253 wasm_memory_limit
254 .try_into()
255 .map_err(|e| AgentError::MessageError(format!("{e}"))),
256 ),
257 ..self
258 }
259 }
260
261 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
263 where
264 E: std::fmt::Display,
265 C: TryInto<WasmMemoryLimit, Error = E>,
266 {
267 Self {
268 wasm_memory_threshold: Some(
269 wasm_memory_threshold
270 .try_into()
271 .map_err(|e| AgentError::MessageError(format!("{e}"))),
272 ),
273 ..self
274 }
275 }
276
277 pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
279 where
280 E: std::fmt::Display,
281 C: TryInto<LogMemoryLimit, Error = E>,
282 {
283 Self {
284 log_memory_limit: Some(
285 log_memory_limit
286 .try_into()
287 .map_err(|e| AgentError::MessageError(format!("{e}"))),
288 ),
289 ..self
290 }
291 }
292
293 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
295 where
296 E: std::fmt::Display,
297 C: TryInto<LogVisibility, Error = E>,
298 {
299 Self {
300 log_visibility: Some(
301 log_visibility
302 .try_into()
303 .map_err(|e| AgentError::MessageError(format!("{e}"))),
304 ),
305 ..self
306 }
307 }
308
309 pub fn with_environment_variables<E, C>(self, environment_variables: C) -> Self
311 where
312 E: std::fmt::Display,
313 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
314 {
315 Self {
316 environment_variables: Some(
317 environment_variables
318 .try_into()
319 .map_err(|e| AgentError::MessageError(format!("{e}"))),
320 ),
321 ..self
322 }
323 }
324
325 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
332 let prepared = self.prepare()?;
333 let effective_canister_id = match prepared.effective_id {
334 EffectiveId::Canister(p) => p,
335 EffectiveId::Subnet(_) => {
336 return Err(AgentError::MessageError(
337 "CreateCanisterBuilder::build() does not support subnet routing; \
338 use call()/call_and_wait() instead."
339 .into(),
340 ));
341 }
342 };
343 Ok(prepared
344 .canister
345 .update(prepared.method)
346 .with_arg_raw(prepared.arg_bytes)
347 .with_effective_canister_id(effective_canister_id)
348 .build()
349 .map(|result: (CreateCanisterOut,)| (result.0.canister_id,)))
350 }
351
352 pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
354 match self.effective_id {
355 EffectiveId::Canister(_) => self.build()?.call().await,
356 EffectiveId::Subnet(subnet_id) => {
357 let prepared = self.prepare()?;
358 let response = subnet_call(
359 prepared.canister,
360 prepared.method,
361 prepared.arg_bytes,
362 subnet_id,
363 )
364 .await?;
365 match response {
366 CallResponse::Response(bytes) => {
367 let (out,): (CreateCanisterOut,) = decode_args(&bytes)
368 .map_err(|e| AgentError::CandidError(Box::new(e)))?;
369 Ok(CallResponse::Response((out.canister_id,)))
370 }
371 CallResponse::Poll(request_id) => Ok(CallResponse::Poll(request_id)),
372 }
373 }
374 }
375 }
376
377 pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
379 match self.effective_id {
380 EffectiveId::Canister(_) => self.build()?.call_and_wait().await,
381 EffectiveId::Subnet(subnet_id) => {
382 let prepared = self.prepare()?;
383 subnet_call_and_wait(
384 prepared.canister,
385 prepared.method,
386 prepared.arg_bytes,
387 subnet_id,
388 )
389 .await
390 }
391 }
392 }
393
394 fn prepare(self) -> Result<PreparedCreate<'agent, 'canister>, 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 let log_memory_limit = match self.log_memory_limit {
436 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
437 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
438 None => None,
439 };
440 let environment_variables = match self.environment_variables {
441 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
442 Some(Ok(x)) => Some(x),
443 None => None,
444 };
445
446 let (method, arg_bytes) = if self.is_provisional_create {
447 #[derive(CandidType)]
448 struct In {
449 amount: Option<Nat>,
450 settings: CanisterSettings,
451 specified_id: Option<Principal>,
452 }
453 let in_arg = In {
454 amount: self.amount.map(Nat::from),
455 settings: CanisterSettings {
456 controllers,
457 compute_allocation,
458 memory_allocation,
459 freezing_threshold,
460 reserved_cycles_limit,
461 wasm_memory_limit,
462 wasm_memory_threshold,
463 log_visibility,
464 log_memory_limit,
465 environment_variables,
466 },
467 specified_id: self.specified_id,
468 };
469 (
470 MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref(),
471 Encode!(&in_arg).map_err(|e| AgentError::CandidError(Box::new(e)))?,
472 )
473 } else {
474 let settings = CanisterSettings {
475 controllers,
476 compute_allocation,
477 memory_allocation,
478 freezing_threshold,
479 reserved_cycles_limit,
480 wasm_memory_limit,
481 wasm_memory_threshold,
482 log_visibility,
483 log_memory_limit,
484 environment_variables,
485 };
486 (
487 MgmtMethod::CreateCanister.as_ref(),
488 Encode!(&settings).map_err(|e| AgentError::CandidError(Box::new(e)))?,
489 )
490 };
491
492 Ok(PreparedCreate {
493 canister: self.canister,
494 effective_id: self.effective_id,
495 method,
496 arg_bytes,
497 })
498 }
499}
500
501#[derive(Deserialize, CandidType)]
502struct CreateCanisterOut {
503 canister_id: Principal,
504}
505
506struct PreparedCreate<'agent, 'canister> {
507 canister: &'canister Canister<'agent>,
508 effective_id: EffectiveId,
509 method: &'static str,
510 arg_bytes: Vec<u8>,
511}
512
513async fn subnet_call_and_wait(
516 canister: &Canister<'_>,
517 method: &str,
518 arg_bytes: Vec<u8>,
519 subnet_id: Principal,
520) -> Result<(Principal,), AgentError> {
521 let agent = canister.agent;
522 let signed = agent
523 .update(&Principal::management_canister(), method)
524 .with_arg(arg_bytes)
525 .sign()?;
526 let effective_id = EffectiveId::Subnet(subnet_id);
527 let response = agent
528 .update_signed(effective_id, signed.signed_update)
529 .await?;
530 let bytes = match response {
531 CallResponse::Response(bytes) => bytes,
532 CallResponse::Poll(request_id) => {
533 let signed_status = agent.sign_request_status(effective_id, request_id)?;
534 agent
535 .wait_signed(
536 &request_id,
537 effective_id,
538 signed_status.signed_request_status,
539 )
540 .await?
541 .0
542 }
543 };
544 let (out,): (CreateCanisterOut,) =
545 decode_args(&bytes).map_err(|e| AgentError::CandidError(Box::new(e)))?;
546 Ok((out.canister_id,))
547}
548
549async fn subnet_call(
552 canister: &Canister<'_>,
553 method: &str,
554 arg_bytes: Vec<u8>,
555 subnet_id: Principal,
556) -> Result<CallResponse<Vec<u8>>, AgentError> {
557 let agent = canister.agent;
558 let signed = agent
559 .update(&Principal::management_canister(), method)
560 .with_arg(arg_bytes)
561 .sign()?;
562 agent
563 .update_signed(EffectiveId::Subnet(subnet_id), signed.signed_update)
564 .await
565}
566
567#[cfg_attr(target_family = "wasm", async_trait(?Send))]
568#[cfg_attr(not(target_family = "wasm"), async_trait)]
569impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
570 type Value = (Principal,);
571
572 async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
573 self.call().await
574 }
575
576 async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
577 self.call_and_wait().await
578 }
579}
580
581impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
582 type IntoFuture = CallFuture<'agent, (Principal,)>;
583 type Output = Result<(Principal,), AgentError>;
584
585 fn into_future(self) -> Self::IntoFuture {
586 Box::pin(self.call_and_wait())
587 }
588}
589
590#[doc(hidden)]
591#[deprecated(since = "0.42.0", note = "Please use UpgradeFlags instead")]
592pub type CanisterUpgradeOptions = UpgradeFlags;
593
594#[doc(hidden)]
595#[deprecated(since = "0.42.0", note = "Please use CanisterInstallMode instead")]
596pub type InstallMode = CanisterInstallMode;
597
598#[doc(hidden)]
599#[deprecated(since = "0.42.0", note = "Please use InstallCodeArgs instead")]
600pub type CanisterInstall = InstallCodeArgs;
601
602#[derive(Debug)]
604pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
605 canister: &'canister Canister<'agent>,
606 canister_id: Principal,
607 wasm: &'canister [u8],
608 arg: Argument,
609 mode: Option<CanisterInstallMode>,
610}
611
612impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
613 pub fn builder(
615 canister: &'canister Canister<'agent>,
616 canister_id: &Principal,
617 wasm: &'canister [u8],
618 ) -> Self {
619 Self {
620 canister,
621 canister_id: *canister_id,
622 wasm,
623 arg: Default::default(),
624 mode: None,
625 }
626 }
627
628 pub fn with_arg<Argument: CandidType>(
631 mut self,
632 arg: Argument,
633 ) -> InstallCodeBuilder<'agent, 'canister> {
634 self.arg.set_idl_arg(arg);
635 self
636 }
637 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
640 assert!(self.arg.0.is_none(), "argument is being set more than once");
641 self.arg = Argument::from_candid(tuple);
642 self
643 }
644 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
646 self.arg.set_raw_arg(arg);
647 self
648 }
649
650 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
652 Self {
653 mode: Some(mode),
654 ..self
655 }
656 }
657
658 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
661 Ok(self
662 .canister
663 .update(MgmtMethod::InstallCode.as_ref())
664 .with_arg(InstallCodeArgs {
665 mode: self.mode.unwrap_or(CanisterInstallMode::Install),
666 canister_id: self.canister_id,
667 wasm_module: self.wasm.to_owned(),
668 arg: self.arg.serialize()?,
669 sender_canister_version: None,
670 })
671 .with_effective_canister_id(self.canister_id)
672 .build())
673 }
674
675 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
677 self.build()?.call().await
678 }
679
680 pub async fn call_and_wait(self) -> Result<(), AgentError> {
682 self.build()?.call_and_wait().await
683 }
684}
685
686#[cfg_attr(target_family = "wasm", async_trait(?Send))]
687#[cfg_attr(not(target_family = "wasm"), async_trait)]
688impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
689 type Value = ();
690
691 async fn call(self) -> Result<CallResponse<()>, AgentError> {
692 self.build()?.call().await
693 }
694
695 async fn call_and_wait(self) -> Result<(), AgentError> {
696 self.build()?.call_and_wait().await
697 }
698}
699
700impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
701 type IntoFuture = CallFuture<'agent, ()>;
702 type Output = Result<(), AgentError>;
703
704 fn into_future(self) -> Self::IntoFuture {
705 AsyncCall::call_and_wait(self)
706 }
707}
708
709#[derive(Debug)]
711pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
712 canister: &'canister Canister<'agent>,
713 target_canister: Principal,
714 store_canister: Option<Principal>,
715 chunk_hashes_list: Vec<ChunkHash>,
716 wasm_module_hash: Vec<u8>,
717 arg: Argument,
718 mode: CanisterInstallMode,
719}
720
721impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
722 pub fn builder(
724 canister: &'canister Canister<'agent>,
725 target_canister: Principal,
726 wasm_module_hash: &[u8],
727 ) -> Self {
728 Self {
729 canister,
730 target_canister,
731 wasm_module_hash: wasm_module_hash.to_vec(),
732 store_canister: None,
733 chunk_hashes_list: vec![],
734 arg: Argument::new(),
735 mode: CanisterInstallMode::Install,
736 }
737 }
738
739 pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
741 self.chunk_hashes_list = chunk_hashes;
742 self
743 }
744
745 pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
747 self.store_canister = Some(store_canister);
748 self
749 }
750
751 pub fn with_arg(mut self, argument: impl CandidType) -> Self {
754 self.arg.set_idl_arg(argument);
755 self
756 }
757
758 pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
761 assert!(self.arg.0.is_none(), "argument is being set more than once");
762 self.arg = Argument::from_candid(argument);
763 self
764 }
765
766 pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
768 self.arg.set_raw_arg(argument);
769 self
770 }
771
772 pub fn with_install_mode(mut self, mode: CanisterInstallMode) -> Self {
774 self.mode = mode;
775 self
776 }
777
778 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
780 #[derive(CandidType)]
781 struct In {
782 mode: CanisterInstallMode,
783 target_canister: Principal,
784 store_canister: Option<Principal>,
785 chunk_hashes_list: Vec<ChunkHash>,
786 wasm_module_hash: Vec<u8>,
787 arg: Vec<u8>,
788 sender_canister_version: Option<u64>,
789 }
790 let Self {
791 mode,
792 target_canister,
793 store_canister,
794 chunk_hashes_list,
795 wasm_module_hash,
796 arg,
797 ..
798 } = self;
799 Ok(self
800 .canister
801 .update(MgmtMethod::InstallChunkedCode.as_ref())
802 .with_arg(In {
803 mode,
804 target_canister,
805 store_canister,
806 chunk_hashes_list,
807 wasm_module_hash,
808 arg: arg.serialize()?,
809 sender_canister_version: None,
810 })
811 .with_effective_canister_id(target_canister)
812 .build())
813 }
814
815 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
817 self.build()?.call().await
818 }
819
820 pub async fn call_and_wait(self) -> Result<(), AgentError> {
822 self.build()?.call_and_wait().await
823 }
824}
825
826#[cfg_attr(target_family = "wasm", async_trait(?Send))]
827#[cfg_attr(not(target_family = "wasm"), async_trait)]
828impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
829 type Value = ();
830
831 async fn call(self) -> Result<CallResponse<()>, AgentError> {
832 self.call().await
833 }
834
835 async fn call_and_wait(self) -> Result<(), AgentError> {
836 self.call_and_wait().await
837 }
838}
839
840impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
841 type IntoFuture = CallFuture<'agent, ()>;
842 type Output = Result<(), AgentError>;
843
844 fn into_future(self) -> Self::IntoFuture {
845 AsyncCall::call_and_wait(self)
846 }
847}
848
849#[derive(Debug)]
855pub struct InstallBuilder<'agent, 'canister, 'builder> {
856 canister: &'canister ManagementCanister<'agent>,
857 canister_id: Principal,
858 wasm: &'builder [u8],
861 arg: Argument,
862 mode: CanisterInstallMode,
863}
864
865impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
866 const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
869
870 pub fn builder(
872 canister: &'canister ManagementCanister<'agent>,
873 canister_id: &Principal,
874 wasm: &'builder [u8],
875 ) -> Self {
876 Self {
877 canister,
878 canister_id: *canister_id,
879 wasm,
880 arg: Default::default(),
881 mode: CanisterInstallMode::Install,
882 }
883 }
884
885 pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
888 self.arg.set_idl_arg(arg);
889 self
890 }
891 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
894 assert!(self.arg.0.is_none(), "argument is being set more than once");
895 self.arg = Argument::from_candid(tuple);
896 self
897 }
898 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
900 self.arg.set_raw_arg(arg);
901 self
902 }
903
904 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
906 Self { mode, ..self }
907 }
908
909 pub async fn call_and_wait(self) -> Result<(), AgentError> {
912 self.call_and_wait_with_progress()
913 .await
914 .try_for_each(|_| ready(Ok(())))
915 .await
916 }
917
918 pub async fn call_and_wait_with_progress(
922 self,
923 ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
924 let stream_res = async move {
925 let arg = self.arg.serialize()?;
926 let stream: BoxStream<'_, _> =
927 if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
928 Box::pin(
929 async move {
930 self.canister
931 .install_code(&self.canister_id, self.wasm)
932 .with_raw_arg(arg)
933 .with_mode(self.mode)
934 .call_and_wait()
935 .await
936 }
937 .into_stream(),
938 )
939 } else {
940 let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
941 let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
942 let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
943 let mut to_upload_chunks = vec![];
944 for (hash, chunk) in &all_chunks {
945 if !existing_chunks.contains(hash) {
946 to_upload_chunks.push(*chunk);
947 }
948 }
949
950 let upload_chunks_stream = FuturesUnordered::new();
951 for chunk in to_upload_chunks {
952 upload_chunks_stream.push(async move {
953 let (_res,) = self
954 .canister
955 .upload_chunk(&self.canister_id, &ic_management_canister_types::UploadChunkArgs {
956 canister_id: self.canister_id,
957 chunk: chunk.to_vec(),
958 })
959 .call_and_wait()
960 .await?;
961 Ok(())
962 });
963 }
964 let install_chunked_code_stream = async move {
965 let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
966 self.canister
967 .install_chunked_code(
968 &self.canister_id,
969 &Sha256::digest(self.wasm),
970 )
971 .with_chunk_hashes(results)
972 .with_raw_arg(arg)
973 .with_install_mode(self.mode)
974 .call_and_wait()
975 .await
976 }
977 .into_stream();
978 let clear_store_stream = async move {
979 self.canister
980 .clear_chunk_store(&self.canister_id)
981 .call_and_wait()
982 .await
983 }
984 .into_stream();
985
986 Box::pin(
987 upload_chunks_stream
988 .chain(install_chunked_code_stream)
989 .chain(clear_store_stream ),
990 )
991 };
992 Ok(stream)
993 }.await;
994 match stream_res {
995 Ok(stream) => stream,
996 Err(err) => Box::pin(stream::once(async { Err(err) })),
997 }
998 }
999}
1000
1001impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
1002 for InstallBuilder<'agent, 'canister, 'builder>
1003{
1004 type IntoFuture = CallFuture<'builder, ()>;
1005 type Output = Result<(), AgentError>;
1006
1007 fn into_future(self) -> Self::IntoFuture {
1008 Box::pin(self.call_and_wait())
1009 }
1010}
1011
1012#[derive(Debug)]
1014pub struct UpdateSettingsBuilder<'agent, 'canister: 'agent> {
1015 canister: &'canister Canister<'agent>,
1016 canister_id: Principal,
1017 controllers: Option<Result<Vec<Principal>, AgentError>>,
1018 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
1019 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1020 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1021 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1022 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1023 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1024 log_visibility: Option<Result<LogVisibility, AgentError>>,
1025 log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
1026 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
1027}
1028
1029impl<'agent, 'canister: 'agent> UpdateSettingsBuilder<'agent, 'canister> {
1030 pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1032 Self {
1033 canister,
1034 canister_id: *canister_id,
1035 controllers: None,
1036 compute_allocation: None,
1037 memory_allocation: None,
1038 freezing_threshold: None,
1039 reserved_cycles_limit: None,
1040 wasm_memory_limit: None,
1041 wasm_memory_threshold: None,
1042 log_visibility: None,
1043 log_memory_limit: None,
1044 environment_variables: None,
1045 }
1046 }
1047
1048 pub fn with_controller<C, E>(self, controller: C) -> Self
1050 where
1051 E: std::fmt::Display,
1052 C: TryInto<Principal, Error = E>,
1053 {
1054 let controller_result = controller
1055 .try_into()
1056 .map_err(|e| AgentError::MessageError(format!("{e}")));
1057 let controllers = match (controller_result, self.controllers) {
1058 (_, Some(Err(sticky))) => Some(Err(sticky)),
1059 (Err(e), _) => Some(Err(e)),
1060 (Ok(controller), Some(Ok(mut controllers))) => {
1061 controllers.push(controller);
1062 Some(Ok(controllers))
1063 }
1064 (Ok(controller), None) => Some(Ok(vec![controller])),
1065 };
1066 Self {
1067 controllers,
1068 ..self
1069 }
1070 }
1071
1072 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1074 where
1075 E: std::fmt::Display,
1076 C: TryInto<ComputeAllocation, Error = E>,
1077 {
1078 Self {
1079 compute_allocation: Some(
1080 compute_allocation
1081 .try_into()
1082 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1083 ),
1084 ..self
1085 }
1086 }
1087
1088 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1090 where
1091 E: std::fmt::Display,
1092 C: TryInto<MemoryAllocation, Error = E>,
1093 {
1094 Self {
1095 memory_allocation: Some(
1096 memory_allocation
1097 .try_into()
1098 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1099 ),
1100 ..self
1101 }
1102 }
1103
1104 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1106 where
1107 E: std::fmt::Display,
1108 C: TryInto<FreezingThreshold, Error = E>,
1109 {
1110 Self {
1111 freezing_threshold: Some(
1112 freezing_threshold
1113 .try_into()
1114 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1115 ),
1116 ..self
1117 }
1118 }
1119
1120 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1122 where
1123 E: std::fmt::Display,
1124 C: TryInto<ReservedCyclesLimit, Error = E>,
1125 {
1126 Self {
1127 reserved_cycles_limit: Some(
1128 limit
1129 .try_into()
1130 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1131 ),
1132 ..self
1133 }
1134 }
1135
1136 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1138 where
1139 E: std::fmt::Display,
1140 C: TryInto<WasmMemoryLimit, Error = E>,
1141 {
1142 Self {
1143 wasm_memory_limit: Some(
1144 wasm_memory_limit
1145 .try_into()
1146 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1147 ),
1148 ..self
1149 }
1150 }
1151
1152 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1154 where
1155 E: std::fmt::Display,
1156 C: TryInto<WasmMemoryLimit, Error = E>,
1157 {
1158 Self {
1159 wasm_memory_threshold: Some(
1160 wasm_memory_threshold
1161 .try_into()
1162 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1163 ),
1164 ..self
1165 }
1166 }
1167
1168 pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
1170 where
1171 E: std::fmt::Display,
1172 C: TryInto<LogMemoryLimit, Error = E>,
1173 {
1174 Self {
1175 log_memory_limit: Some(
1176 log_memory_limit
1177 .try_into()
1178 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1179 ),
1180 ..self
1181 }
1182 }
1183
1184 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1186 where
1187 E: std::fmt::Display,
1188 C: TryInto<LogVisibility, Error = E>,
1189 {
1190 Self {
1191 log_visibility: Some(
1192 log_visibility
1193 .try_into()
1194 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1195 ),
1196 ..self
1197 }
1198 }
1199
1200 pub fn with_environment_variables<C, E>(self, environment_variables: C) -> Self
1202 where
1203 E: std::fmt::Display,
1204 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
1205 {
1206 Self {
1207 environment_variables: Some(
1208 environment_variables
1209 .try_into()
1210 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1211 ),
1212 ..self
1213 }
1214 }
1215
1216 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1219 #[derive(CandidType)]
1220 struct In {
1221 canister_id: Principal,
1222 settings: CanisterSettings,
1223 }
1224
1225 let controllers = match self.controllers {
1226 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1227 Some(Ok(x)) => Some(x),
1228 None => None,
1229 };
1230 let compute_allocation = match self.compute_allocation {
1231 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1232 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1233 None => None,
1234 };
1235 let memory_allocation = match self.memory_allocation {
1236 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1237 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1238 None => None,
1239 };
1240 let freezing_threshold = match self.freezing_threshold {
1241 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1242 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1243 None => None,
1244 };
1245 let reserved_cycles_limit = match self.reserved_cycles_limit {
1246 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1247 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1248 None => None,
1249 };
1250 let wasm_memory_limit = match self.wasm_memory_limit {
1251 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1252 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1253 None => None,
1254 };
1255 let wasm_memory_threshold = match self.wasm_memory_threshold {
1256 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1257 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1258 None => None,
1259 };
1260 let log_visibility = match self.log_visibility {
1261 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1262 Some(Ok(x)) => Some(x),
1263 None => None,
1264 };
1265 let log_memory_limit = match self.log_memory_limit {
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 environment_variables = match self.environment_variables {
1271 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1272 Some(Ok(x)) => Some(x),
1273 None => None,
1274 };
1275
1276 Ok(self
1277 .canister
1278 .update(MgmtMethod::UpdateSettings.as_ref())
1279 .with_arg(In {
1280 canister_id: self.canister_id,
1281 settings: CanisterSettings {
1282 controllers,
1283 compute_allocation,
1284 memory_allocation,
1285 freezing_threshold,
1286 reserved_cycles_limit,
1287 wasm_memory_limit,
1288 wasm_memory_threshold,
1289 log_visibility,
1290 log_memory_limit,
1291 environment_variables,
1292 },
1293 })
1294 .with_effective_canister_id(self.canister_id)
1295 .build())
1296 }
1297
1298 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1300 self.build()?.call().await
1301 }
1302
1303 pub async fn call_and_wait(self) -> Result<(), AgentError> {
1305 self.build()?.call_and_wait().await
1306 }
1307}
1308
1309#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1310#[cfg_attr(not(target_family = "wasm"), async_trait)]
1311impl<'agent, 'canister: 'agent> AsyncCall for UpdateSettingsBuilder<'agent, 'canister> {
1312 type Value = ();
1313 async fn call(self) -> Result<CallResponse<()>, AgentError> {
1314 self.build()?.call().await
1315 }
1316
1317 async fn call_and_wait(self) -> Result<(), AgentError> {
1318 self.build()?.call_and_wait().await
1319 }
1320}
1321
1322impl<'agent, 'canister: 'agent> IntoFuture for UpdateSettingsBuilder<'agent, 'canister> {
1323 type IntoFuture = CallFuture<'agent, ()>;
1324 type Output = Result<(), AgentError>;
1325 fn into_future(self) -> Self::IntoFuture {
1326 AsyncCall::call_and_wait(self)
1327 }
1328}
1329
1330#[cfg(not(target_family = "wasm"))]
1331type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1332#[cfg(target_family = "wasm")]
1333type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;