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,
27 SnapshotVisibility, UpgradeFlags, WasmMemoryPersistence,
28};
29use sha2::{Digest, Sha256};
30use std::{
31 collections::BTreeSet,
32 convert::{From, TryInto},
33 future::IntoFuture,
34 pin::Pin,
35};
36
37fn reject_mixed_settings(has_individual_settings: bool) -> Result<(), AgentError> {
42 if has_individual_settings {
43 return Err(AgentError::MessageError(
44 "`with_canister_settings` cannot be combined with the individual settings \
45 setters (`with_controller`, `with_compute_allocation`, etc.)"
46 .to_string(),
47 ));
48 }
49 Ok(())
50}
51
52#[derive(Debug)]
54pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
55 canister: &'canister Canister<'agent>,
56 effective_id: EffectiveId,
59 controllers: Option<Result<Vec<Principal>, AgentError>>,
60 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
61 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
62 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
63 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
64 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
65 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
66 log_visibility: Option<Result<LogVisibility, AgentError>>,
67 log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
68 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
69 snapshot_visibility: Option<Result<SnapshotVisibility, AgentError>>,
70 settings: Option<CanisterSettings>,
71 is_provisional_create: bool,
72 amount: Option<u128>,
73 specified_id: Option<Principal>,
74}
75
76impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
77 pub fn builder(canister: &'canister Canister<'agent>) -> Self {
79 Self {
80 canister,
81 effective_id: EffectiveId::Canister(Principal::management_canister()),
82 controllers: None,
83 compute_allocation: None,
84 memory_allocation: None,
85 freezing_threshold: None,
86 reserved_cycles_limit: None,
87 wasm_memory_limit: None,
88 wasm_memory_threshold: None,
89 log_visibility: None,
90 log_memory_limit: None,
91 environment_variables: None,
92 snapshot_visibility: None,
93 settings: None,
94 is_provisional_create: false,
95 amount: None,
96 specified_id: None,
97 }
98 }
99
100 #[allow(clippy::wrong_self_convention)]
107 pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
108 Self {
109 is_provisional_create: true,
110 amount,
111 ..self
112 }
113 }
114
115 pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
124 Self {
125 is_provisional_create: true,
126 specified_id: Some(specified_id),
127 effective_id: EffectiveId::Canister(specified_id),
128 ..self
129 }
130 }
131
132 pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
140 where
141 E: std::fmt::Display,
142 C: TryInto<Principal, Error = E>,
143 {
144 match effective_canister_id.try_into() {
145 Ok(effective_canister_id) => Self {
146 effective_id: EffectiveId::Canister(effective_canister_id),
147 ..self
148 },
149 Err(_) => self,
150 }
151 }
152
153 pub fn with_effective_subnet_id<C, E>(self, effective_subnet_id: C) -> Self
163 where
164 E: std::fmt::Display,
165 C: TryInto<Principal, Error = E>,
166 {
167 match effective_subnet_id.try_into() {
168 Ok(subnet_id) => Self {
169 effective_id: EffectiveId::Subnet(subnet_id),
170 ..self
171 },
172 Err(_) => self,
173 }
174 }
175
176 pub fn with_controller<C, E>(self, controller: C) -> Self
178 where
179 E: std::fmt::Display,
180 C: TryInto<Principal, Error = E>,
181 {
182 let controller_result = controller
183 .try_into()
184 .map_err(|e| AgentError::MessageError(format!("{e}")));
185 let controllers = match (controller_result, self.controllers) {
186 (_, Some(Err(sticky))) => Some(Err(sticky)),
187 (Err(e), _) => Some(Err(e)),
188 (Ok(controller), Some(Ok(mut controllers))) => {
189 controllers.push(controller);
190 Some(Ok(controllers))
191 }
192 (Ok(controller), None) => Some(Ok(vec![controller])),
193 };
194 Self {
195 controllers,
196 ..self
197 }
198 }
199
200 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
202 where
203 E: std::fmt::Display,
204 C: TryInto<ComputeAllocation, Error = E>,
205 {
206 Self {
207 compute_allocation: Some(
208 compute_allocation
209 .try_into()
210 .map_err(|e| AgentError::MessageError(format!("{e}"))),
211 ),
212 ..self
213 }
214 }
215
216 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
218 where
219 E: std::fmt::Display,
220 C: TryInto<MemoryAllocation, Error = E>,
221 {
222 Self {
223 memory_allocation: Some(
224 memory_allocation
225 .try_into()
226 .map_err(|e| AgentError::MessageError(format!("{e}"))),
227 ),
228 ..self
229 }
230 }
231
232 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
234 where
235 E: std::fmt::Display,
236 C: TryInto<FreezingThreshold, Error = E>,
237 {
238 Self {
239 freezing_threshold: Some(
240 freezing_threshold
241 .try_into()
242 .map_err(|e| AgentError::MessageError(format!("{e}"))),
243 ),
244 ..self
245 }
246 }
247
248 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
250 where
251 E: std::fmt::Display,
252 C: TryInto<ReservedCyclesLimit, Error = E>,
253 {
254 Self {
255 reserved_cycles_limit: Some(
256 limit
257 .try_into()
258 .map_err(|e| AgentError::MessageError(format!("{e}"))),
259 ),
260 ..self
261 }
262 }
263
264 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
266 where
267 E: std::fmt::Display,
268 C: TryInto<WasmMemoryLimit, Error = E>,
269 {
270 Self {
271 wasm_memory_limit: Some(
272 wasm_memory_limit
273 .try_into()
274 .map_err(|e| AgentError::MessageError(format!("{e}"))),
275 ),
276 ..self
277 }
278 }
279
280 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
282 where
283 E: std::fmt::Display,
284 C: TryInto<WasmMemoryLimit, Error = E>,
285 {
286 Self {
287 wasm_memory_threshold: Some(
288 wasm_memory_threshold
289 .try_into()
290 .map_err(|e| AgentError::MessageError(format!("{e}"))),
291 ),
292 ..self
293 }
294 }
295
296 pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
298 where
299 E: std::fmt::Display,
300 C: TryInto<LogMemoryLimit, Error = E>,
301 {
302 Self {
303 log_memory_limit: Some(
304 log_memory_limit
305 .try_into()
306 .map_err(|e| AgentError::MessageError(format!("{e}"))),
307 ),
308 ..self
309 }
310 }
311
312 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
314 where
315 E: std::fmt::Display,
316 C: TryInto<LogVisibility, Error = E>,
317 {
318 Self {
319 log_visibility: Some(
320 log_visibility
321 .try_into()
322 .map_err(|e| AgentError::MessageError(format!("{e}"))),
323 ),
324 ..self
325 }
326 }
327
328 pub fn with_environment_variables<E, C>(self, environment_variables: C) -> Self
330 where
331 E: std::fmt::Display,
332 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
333 {
334 Self {
335 environment_variables: Some(
336 environment_variables
337 .try_into()
338 .map_err(|e| AgentError::MessageError(format!("{e}"))),
339 ),
340 ..self
341 }
342 }
343
344 pub fn with_snapshot_visibility<C, E>(self, snapshot_visibility: C) -> Self
346 where
347 E: std::fmt::Display,
348 C: TryInto<SnapshotVisibility, Error = E>,
349 {
350 Self {
351 snapshot_visibility: Some(
352 snapshot_visibility
353 .try_into()
354 .map_err(|e| AgentError::MessageError(format!("{e}"))),
355 ),
356 ..self
357 }
358 }
359
360 pub fn with_canister_settings(self, settings: CanisterSettings) -> Self {
370 Self {
371 settings: Some(settings),
372 ..self
373 }
374 }
375
376 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
383 let prepared = self.prepare()?;
384 let effective_canister_id = match prepared.effective_id {
385 EffectiveId::Canister(p) => p,
386 EffectiveId::Subnet(_) => {
387 return Err(AgentError::MessageError(
388 "CreateCanisterBuilder::build() does not support subnet routing; \
389 use call()/call_and_wait() instead."
390 .into(),
391 ));
392 }
393 };
394 Ok(prepared
395 .canister
396 .update(prepared.method)
397 .with_arg_raw(prepared.arg_bytes)
398 .with_effective_canister_id(effective_canister_id)
399 .build()
400 .map(|result: (CreateCanisterOut,)| (result.0.canister_id,)))
401 }
402
403 pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
405 match self.effective_id {
406 EffectiveId::Canister(_) => self.build()?.call().await,
407 EffectiveId::Subnet(subnet_id) => {
408 let prepared = self.prepare()?;
409 let response = subnet_call(
410 prepared.canister,
411 prepared.method,
412 prepared.arg_bytes,
413 subnet_id,
414 )
415 .await?;
416 match response {
417 CallResponse::Response(bytes) => {
418 let (out,): (CreateCanisterOut,) = decode_args(&bytes)
419 .map_err(|e| AgentError::CandidError(Box::new(e)))?;
420 Ok(CallResponse::Response((out.canister_id,)))
421 }
422 CallResponse::Poll(request_id) => Ok(CallResponse::Poll(request_id)),
423 }
424 }
425 }
426 }
427
428 pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
430 match self.effective_id {
431 EffectiveId::Canister(_) => self.build()?.call_and_wait().await,
432 EffectiveId::Subnet(subnet_id) => {
433 let prepared = self.prepare()?;
434 subnet_call_and_wait(
435 prepared.canister,
436 prepared.method,
437 prepared.arg_bytes,
438 subnet_id,
439 )
440 .await
441 }
442 }
443 }
444
445 fn prepare(self) -> Result<PreparedCreate<'agent, 'canister>, AgentError> {
446 let settings = if let Some(settings) = self.settings {
447 reject_mixed_settings(
448 self.controllers.is_some()
449 || self.compute_allocation.is_some()
450 || self.memory_allocation.is_some()
451 || self.freezing_threshold.is_some()
452 || self.reserved_cycles_limit.is_some()
453 || self.wasm_memory_limit.is_some()
454 || self.wasm_memory_threshold.is_some()
455 || self.log_visibility.is_some()
456 || self.log_memory_limit.is_some()
457 || self.environment_variables.is_some()
458 || self.snapshot_visibility.is_some(),
459 )?;
460 settings
461 } else {
462 let controllers = match self.controllers {
463 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
464 Some(Ok(x)) => Some(x),
465 None => None,
466 };
467 let compute_allocation = match self.compute_allocation {
468 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
469 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
470 None => None,
471 };
472 let memory_allocation = match self.memory_allocation {
473 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
474 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
475 None => None,
476 };
477 let freezing_threshold = match self.freezing_threshold {
478 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
479 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
480 None => None,
481 };
482 let reserved_cycles_limit = match self.reserved_cycles_limit {
483 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
484 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
485 None => None,
486 };
487 let wasm_memory_limit = match self.wasm_memory_limit {
488 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
489 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
490 None => None,
491 };
492 let wasm_memory_threshold = match self.wasm_memory_threshold {
493 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
494 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
495 None => None,
496 };
497 let log_visibility = match self.log_visibility {
498 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
499 Some(Ok(x)) => Some(x),
500 None => None,
501 };
502 let log_memory_limit = match self.log_memory_limit {
503 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
504 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
505 None => None,
506 };
507 let environment_variables = match self.environment_variables {
508 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
509 Some(Ok(x)) => Some(x),
510 None => None,
511 };
512 let snapshot_visibility = match self.snapshot_visibility {
513 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
514 Some(Ok(x)) => Some(x),
515 None => None,
516 };
517 CanisterSettings {
518 controllers,
519 compute_allocation,
520 memory_allocation,
521 freezing_threshold,
522 reserved_cycles_limit,
523 wasm_memory_limit,
524 wasm_memory_threshold,
525 log_visibility,
526 log_memory_limit,
527 environment_variables,
528 snapshot_visibility,
529 }
530 };
531
532 let (method, arg_bytes) = if self.is_provisional_create {
533 #[derive(CandidType)]
534 struct In {
535 amount: Option<Nat>,
536 settings: CanisterSettings,
537 specified_id: Option<Principal>,
538 }
539 let in_arg = In {
540 amount: self.amount.map(Nat::from),
541 settings,
542 specified_id: self.specified_id,
543 };
544 (
545 MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref(),
546 Encode!(&in_arg).map_err(|e| AgentError::CandidError(Box::new(e)))?,
547 )
548 } else {
549 (
550 MgmtMethod::CreateCanister.as_ref(),
551 Encode!(&settings).map_err(|e| AgentError::CandidError(Box::new(e)))?,
552 )
553 };
554
555 Ok(PreparedCreate {
556 canister: self.canister,
557 effective_id: self.effective_id,
558 method,
559 arg_bytes,
560 })
561 }
562}
563
564#[derive(Deserialize, CandidType)]
565struct CreateCanisterOut {
566 canister_id: Principal,
567}
568
569struct PreparedCreate<'agent, 'canister> {
570 canister: &'canister Canister<'agent>,
571 effective_id: EffectiveId,
572 method: &'static str,
573 arg_bytes: Vec<u8>,
574}
575
576async fn subnet_call_and_wait(
579 canister: &Canister<'_>,
580 method: &str,
581 arg_bytes: Vec<u8>,
582 subnet_id: Principal,
583) -> Result<(Principal,), AgentError> {
584 let agent = canister.agent;
585 let signed = agent
586 .update(&Principal::management_canister(), method)
587 .with_arg(arg_bytes)
588 .sign()?;
589 let effective_id = EffectiveId::Subnet(subnet_id);
590 let response = agent
591 .update_signed(effective_id, signed.signed_update)
592 .await?;
593 let bytes = match response {
594 CallResponse::Response(bytes) => bytes,
595 CallResponse::Poll(request_id) => {
596 let signed_status = agent.sign_request_status(effective_id, request_id)?;
597 agent
598 .wait_signed(
599 &request_id,
600 effective_id,
601 signed_status.signed_request_status,
602 )
603 .await?
604 .0
605 }
606 };
607 let (out,): (CreateCanisterOut,) =
608 decode_args(&bytes).map_err(|e| AgentError::CandidError(Box::new(e)))?;
609 Ok((out.canister_id,))
610}
611
612async fn subnet_call(
615 canister: &Canister<'_>,
616 method: &str,
617 arg_bytes: Vec<u8>,
618 subnet_id: Principal,
619) -> Result<CallResponse<Vec<u8>>, AgentError> {
620 let agent = canister.agent;
621 let signed = agent
622 .update(&Principal::management_canister(), method)
623 .with_arg(arg_bytes)
624 .sign()?;
625 agent
626 .update_signed(EffectiveId::Subnet(subnet_id), signed.signed_update)
627 .await
628}
629
630#[cfg_attr(target_family = "wasm", async_trait(?Send))]
631#[cfg_attr(not(target_family = "wasm"), async_trait)]
632impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
633 type Value = (Principal,);
634
635 async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
636 self.call().await
637 }
638
639 async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
640 self.call_and_wait().await
641 }
642}
643
644impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
645 type IntoFuture = CallFuture<'agent, (Principal,)>;
646 type Output = Result<(Principal,), AgentError>;
647
648 fn into_future(self) -> Self::IntoFuture {
649 Box::pin(self.call_and_wait())
650 }
651}
652
653#[doc(hidden)]
654#[deprecated(since = "0.42.0", note = "Please use UpgradeFlags instead")]
655pub type CanisterUpgradeOptions = UpgradeFlags;
656
657#[doc(hidden)]
658#[deprecated(since = "0.42.0", note = "Please use CanisterInstallMode instead")]
659pub type InstallMode = CanisterInstallMode;
660
661#[doc(hidden)]
662#[deprecated(since = "0.42.0", note = "Please use InstallCodeArgs instead")]
663pub type CanisterInstall = InstallCodeArgs;
664
665#[derive(Debug)]
667pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
668 canister: &'canister Canister<'agent>,
669 canister_id: Principal,
670 wasm: &'canister [u8],
671 arg: Argument,
672 mode: Option<CanisterInstallMode>,
673}
674
675impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
676 pub fn builder(
678 canister: &'canister Canister<'agent>,
679 canister_id: &Principal,
680 wasm: &'canister [u8],
681 ) -> Self {
682 Self {
683 canister,
684 canister_id: *canister_id,
685 wasm,
686 arg: Default::default(),
687 mode: None,
688 }
689 }
690
691 pub fn with_arg<Argument: CandidType>(
694 mut self,
695 arg: Argument,
696 ) -> InstallCodeBuilder<'agent, 'canister> {
697 self.arg.set_idl_arg(arg);
698 self
699 }
700 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
703 assert!(self.arg.0.is_none(), "argument is being set more than once");
704 self.arg = Argument::from_candid(tuple);
705 self
706 }
707 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
709 self.arg.set_raw_arg(arg);
710 self
711 }
712
713 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
715 Self {
716 mode: Some(mode),
717 ..self
718 }
719 }
720
721 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
724 Ok(self
725 .canister
726 .update(MgmtMethod::InstallCode.as_ref())
727 .with_arg(InstallCodeArgs {
728 mode: self.mode.unwrap_or(CanisterInstallMode::Install),
729 canister_id: self.canister_id,
730 wasm_module: self.wasm.to_owned(),
731 arg: self.arg.serialize()?,
732 sender_canister_version: None,
733 })
734 .with_effective_canister_id(self.canister_id)
735 .build())
736 }
737
738 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
740 self.build()?.call().await
741 }
742
743 pub async fn call_and_wait(self) -> Result<(), AgentError> {
745 self.build()?.call_and_wait().await
746 }
747}
748
749#[cfg_attr(target_family = "wasm", async_trait(?Send))]
750#[cfg_attr(not(target_family = "wasm"), async_trait)]
751impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
752 type Value = ();
753
754 async fn call(self) -> Result<CallResponse<()>, AgentError> {
755 self.build()?.call().await
756 }
757
758 async fn call_and_wait(self) -> Result<(), AgentError> {
759 self.build()?.call_and_wait().await
760 }
761}
762
763impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
764 type IntoFuture = CallFuture<'agent, ()>;
765 type Output = Result<(), AgentError>;
766
767 fn into_future(self) -> Self::IntoFuture {
768 AsyncCall::call_and_wait(self)
769 }
770}
771
772#[derive(Debug)]
774pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
775 canister: &'canister Canister<'agent>,
776 target_canister: Principal,
777 store_canister: Option<Principal>,
778 chunk_hashes_list: Vec<ChunkHash>,
779 wasm_module_hash: Vec<u8>,
780 arg: Argument,
781 mode: CanisterInstallMode,
782}
783
784impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
785 pub fn builder(
787 canister: &'canister Canister<'agent>,
788 target_canister: Principal,
789 wasm_module_hash: &[u8],
790 ) -> Self {
791 Self {
792 canister,
793 target_canister,
794 wasm_module_hash: wasm_module_hash.to_vec(),
795 store_canister: None,
796 chunk_hashes_list: vec![],
797 arg: Argument::new(),
798 mode: CanisterInstallMode::Install,
799 }
800 }
801
802 pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
804 self.chunk_hashes_list = chunk_hashes;
805 self
806 }
807
808 pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
810 self.store_canister = Some(store_canister);
811 self
812 }
813
814 pub fn with_arg(mut self, argument: impl CandidType) -> Self {
817 self.arg.set_idl_arg(argument);
818 self
819 }
820
821 pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
824 assert!(self.arg.0.is_none(), "argument is being set more than once");
825 self.arg = Argument::from_candid(argument);
826 self
827 }
828
829 pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
831 self.arg.set_raw_arg(argument);
832 self
833 }
834
835 pub fn with_install_mode(mut self, mode: CanisterInstallMode) -> Self {
837 self.mode = mode;
838 self
839 }
840
841 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
843 #[derive(CandidType)]
844 struct In {
845 mode: CanisterInstallMode,
846 target_canister: Principal,
847 store_canister: Option<Principal>,
848 chunk_hashes_list: Vec<ChunkHash>,
849 wasm_module_hash: Vec<u8>,
850 arg: Vec<u8>,
851 sender_canister_version: Option<u64>,
852 }
853 let Self {
854 mode,
855 target_canister,
856 store_canister,
857 chunk_hashes_list,
858 wasm_module_hash,
859 arg,
860 ..
861 } = self;
862 Ok(self
863 .canister
864 .update(MgmtMethod::InstallChunkedCode.as_ref())
865 .with_arg(In {
866 mode,
867 target_canister,
868 store_canister,
869 chunk_hashes_list,
870 wasm_module_hash,
871 arg: arg.serialize()?,
872 sender_canister_version: None,
873 })
874 .with_effective_canister_id(target_canister)
875 .build())
876 }
877
878 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
880 self.build()?.call().await
881 }
882
883 pub async fn call_and_wait(self) -> Result<(), AgentError> {
885 self.build()?.call_and_wait().await
886 }
887}
888
889#[cfg_attr(target_family = "wasm", async_trait(?Send))]
890#[cfg_attr(not(target_family = "wasm"), async_trait)]
891impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
892 type Value = ();
893
894 async fn call(self) -> Result<CallResponse<()>, AgentError> {
895 self.call().await
896 }
897
898 async fn call_and_wait(self) -> Result<(), AgentError> {
899 self.call_and_wait().await
900 }
901}
902
903impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
904 type IntoFuture = CallFuture<'agent, ()>;
905 type Output = Result<(), AgentError>;
906
907 fn into_future(self) -> Self::IntoFuture {
908 AsyncCall::call_and_wait(self)
909 }
910}
911
912#[derive(Debug)]
918pub struct InstallBuilder<'agent, 'canister, 'builder> {
919 canister: &'canister ManagementCanister<'agent>,
920 canister_id: Principal,
921 wasm: &'builder [u8],
924 arg: Argument,
925 mode: CanisterInstallMode,
926}
927
928impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
929 const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
932
933 pub fn builder(
935 canister: &'canister ManagementCanister<'agent>,
936 canister_id: &Principal,
937 wasm: &'builder [u8],
938 ) -> Self {
939 Self {
940 canister,
941 canister_id: *canister_id,
942 wasm,
943 arg: Default::default(),
944 mode: CanisterInstallMode::Install,
945 }
946 }
947
948 pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
951 self.arg.set_idl_arg(arg);
952 self
953 }
954 pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
957 assert!(self.arg.0.is_none(), "argument is being set more than once");
958 self.arg = Argument::from_candid(tuple);
959 self
960 }
961 pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
963 self.arg.set_raw_arg(arg);
964 self
965 }
966
967 pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
969 Self { mode, ..self }
970 }
971
972 pub async fn call_and_wait(self) -> Result<(), AgentError> {
975 self.call_and_wait_with_progress()
976 .await
977 .try_for_each(|_| ready(Ok(())))
978 .await
979 }
980
981 pub async fn call_and_wait_with_progress(
985 self,
986 ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
987 let stream_res = async move {
988 let arg = self.arg.serialize()?;
989 let stream: BoxStream<'_, _> =
990 if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
991 Box::pin(
992 async move {
993 self.canister
994 .install_code(&self.canister_id, self.wasm)
995 .with_raw_arg(arg)
996 .with_mode(self.mode)
997 .call_and_wait()
998 .await
999 }
1000 .into_stream(),
1001 )
1002 } else {
1003 let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
1004 let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
1005 let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
1006 let mut to_upload_chunks = vec![];
1007 for (hash, chunk) in &all_chunks {
1008 if !existing_chunks.contains(hash) {
1009 to_upload_chunks.push(*chunk);
1010 }
1011 }
1012
1013 let upload_chunks_stream = FuturesUnordered::new();
1014 for chunk in to_upload_chunks {
1015 upload_chunks_stream.push(async move {
1016 let (_res,) = self
1017 .canister
1018 .upload_chunk(&self.canister_id, &ic_management_canister_types::UploadChunkArgs {
1019 canister_id: self.canister_id,
1020 chunk: chunk.to_vec(),
1021 })
1022 .call_and_wait()
1023 .await?;
1024 Ok(())
1025 });
1026 }
1027 let install_chunked_code_stream = async move {
1028 let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
1029 self.canister
1030 .install_chunked_code(
1031 &self.canister_id,
1032 &Sha256::digest(self.wasm),
1033 )
1034 .with_chunk_hashes(results)
1035 .with_raw_arg(arg)
1036 .with_install_mode(self.mode)
1037 .call_and_wait()
1038 .await
1039 }
1040 .into_stream();
1041 let clear_store_stream = async move {
1042 self.canister
1043 .clear_chunk_store(&self.canister_id)
1044 .call_and_wait()
1045 .await
1046 }
1047 .into_stream();
1048
1049 Box::pin(
1050 upload_chunks_stream
1051 .chain(install_chunked_code_stream)
1052 .chain(clear_store_stream ),
1053 )
1054 };
1055 Ok(stream)
1056 }.await;
1057 match stream_res {
1058 Ok(stream) => stream,
1059 Err(err) => Box::pin(stream::once(async { Err(err) })),
1060 }
1061 }
1062}
1063
1064impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
1065 for InstallBuilder<'agent, 'canister, 'builder>
1066{
1067 type IntoFuture = CallFuture<'builder, ()>;
1068 type Output = Result<(), AgentError>;
1069
1070 fn into_future(self) -> Self::IntoFuture {
1071 Box::pin(self.call_and_wait())
1072 }
1073}
1074
1075#[derive(Debug)]
1077pub struct UpdateSettingsBuilder<'agent, 'canister: 'agent> {
1078 canister: &'canister Canister<'agent>,
1079 canister_id: Principal,
1080 controllers: Option<Result<Vec<Principal>, AgentError>>,
1081 compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
1082 memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1083 freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1084 reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1085 wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1086 wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1087 log_visibility: Option<Result<LogVisibility, AgentError>>,
1088 log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
1089 environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
1090 snapshot_visibility: Option<Result<SnapshotVisibility, AgentError>>,
1091 settings: Option<CanisterSettings>,
1092}
1093
1094impl<'agent, 'canister: 'agent> UpdateSettingsBuilder<'agent, 'canister> {
1095 pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1097 Self {
1098 canister,
1099 canister_id: *canister_id,
1100 controllers: None,
1101 compute_allocation: None,
1102 memory_allocation: None,
1103 freezing_threshold: None,
1104 reserved_cycles_limit: None,
1105 wasm_memory_limit: None,
1106 wasm_memory_threshold: None,
1107 log_visibility: None,
1108 log_memory_limit: None,
1109 environment_variables: None,
1110 snapshot_visibility: None,
1111 settings: None,
1112 }
1113 }
1114
1115 pub fn with_controller<C, E>(self, controller: C) -> Self
1117 where
1118 E: std::fmt::Display,
1119 C: TryInto<Principal, Error = E>,
1120 {
1121 let controller_result = controller
1122 .try_into()
1123 .map_err(|e| AgentError::MessageError(format!("{e}")));
1124 let controllers = match (controller_result, self.controllers) {
1125 (_, Some(Err(sticky))) => Some(Err(sticky)),
1126 (Err(e), _) => Some(Err(e)),
1127 (Ok(controller), Some(Ok(mut controllers))) => {
1128 controllers.push(controller);
1129 Some(Ok(controllers))
1130 }
1131 (Ok(controller), None) => Some(Ok(vec![controller])),
1132 };
1133 Self {
1134 controllers,
1135 ..self
1136 }
1137 }
1138
1139 pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1141 where
1142 E: std::fmt::Display,
1143 C: TryInto<ComputeAllocation, Error = E>,
1144 {
1145 Self {
1146 compute_allocation: Some(
1147 compute_allocation
1148 .try_into()
1149 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1150 ),
1151 ..self
1152 }
1153 }
1154
1155 pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1157 where
1158 E: std::fmt::Display,
1159 C: TryInto<MemoryAllocation, Error = E>,
1160 {
1161 Self {
1162 memory_allocation: Some(
1163 memory_allocation
1164 .try_into()
1165 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1166 ),
1167 ..self
1168 }
1169 }
1170
1171 pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1173 where
1174 E: std::fmt::Display,
1175 C: TryInto<FreezingThreshold, Error = E>,
1176 {
1177 Self {
1178 freezing_threshold: Some(
1179 freezing_threshold
1180 .try_into()
1181 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1182 ),
1183 ..self
1184 }
1185 }
1186
1187 pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1189 where
1190 E: std::fmt::Display,
1191 C: TryInto<ReservedCyclesLimit, Error = E>,
1192 {
1193 Self {
1194 reserved_cycles_limit: Some(
1195 limit
1196 .try_into()
1197 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1198 ),
1199 ..self
1200 }
1201 }
1202
1203 pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1205 where
1206 E: std::fmt::Display,
1207 C: TryInto<WasmMemoryLimit, Error = E>,
1208 {
1209 Self {
1210 wasm_memory_limit: Some(
1211 wasm_memory_limit
1212 .try_into()
1213 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1214 ),
1215 ..self
1216 }
1217 }
1218
1219 pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1221 where
1222 E: std::fmt::Display,
1223 C: TryInto<WasmMemoryLimit, Error = E>,
1224 {
1225 Self {
1226 wasm_memory_threshold: Some(
1227 wasm_memory_threshold
1228 .try_into()
1229 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1230 ),
1231 ..self
1232 }
1233 }
1234
1235 pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
1237 where
1238 E: std::fmt::Display,
1239 C: TryInto<LogMemoryLimit, Error = E>,
1240 {
1241 Self {
1242 log_memory_limit: Some(
1243 log_memory_limit
1244 .try_into()
1245 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1246 ),
1247 ..self
1248 }
1249 }
1250
1251 pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1253 where
1254 E: std::fmt::Display,
1255 C: TryInto<LogVisibility, Error = E>,
1256 {
1257 Self {
1258 log_visibility: Some(
1259 log_visibility
1260 .try_into()
1261 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1262 ),
1263 ..self
1264 }
1265 }
1266
1267 pub fn with_environment_variables<C, E>(self, environment_variables: C) -> Self
1269 where
1270 E: std::fmt::Display,
1271 C: TryInto<Vec<EnvironmentVariable>, Error = E>,
1272 {
1273 Self {
1274 environment_variables: Some(
1275 environment_variables
1276 .try_into()
1277 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1278 ),
1279 ..self
1280 }
1281 }
1282
1283 pub fn with_snapshot_visibility<C, E>(self, snapshot_visibility: C) -> Self
1285 where
1286 E: std::fmt::Display,
1287 C: TryInto<SnapshotVisibility, Error = E>,
1288 {
1289 Self {
1290 snapshot_visibility: Some(
1291 snapshot_visibility
1292 .try_into()
1293 .map_err(|e| AgentError::MessageError(format!("{e}"))),
1294 ),
1295 ..self
1296 }
1297 }
1298
1299 pub fn with_canister_settings(self, settings: CanisterSettings) -> Self {
1309 Self {
1310 settings: Some(settings),
1311 ..self
1312 }
1313 }
1314
1315 pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1318 #[derive(CandidType)]
1319 struct In {
1320 canister_id: Principal,
1321 settings: CanisterSettings,
1322 }
1323
1324 let settings = if let Some(settings) = self.settings {
1325 reject_mixed_settings(
1326 self.controllers.is_some()
1327 || self.compute_allocation.is_some()
1328 || self.memory_allocation.is_some()
1329 || self.freezing_threshold.is_some()
1330 || self.reserved_cycles_limit.is_some()
1331 || self.wasm_memory_limit.is_some()
1332 || self.wasm_memory_threshold.is_some()
1333 || self.log_visibility.is_some()
1334 || self.log_memory_limit.is_some()
1335 || self.environment_variables.is_some()
1336 || self.snapshot_visibility.is_some(),
1337 )?;
1338 settings
1339 } else {
1340 let controllers = match self.controllers {
1341 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1342 Some(Ok(x)) => Some(x),
1343 None => None,
1344 };
1345 let compute_allocation = match self.compute_allocation {
1346 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1347 Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1348 None => None,
1349 };
1350 let memory_allocation = match self.memory_allocation {
1351 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1352 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1353 None => None,
1354 };
1355 let freezing_threshold = match self.freezing_threshold {
1356 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1357 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1358 None => None,
1359 };
1360 let reserved_cycles_limit = match self.reserved_cycles_limit {
1361 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1362 Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1363 None => None,
1364 };
1365 let wasm_memory_limit = match self.wasm_memory_limit {
1366 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1367 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1368 None => None,
1369 };
1370 let wasm_memory_threshold = match self.wasm_memory_threshold {
1371 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1372 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1373 None => None,
1374 };
1375 let log_visibility = match self.log_visibility {
1376 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1377 Some(Ok(x)) => Some(x),
1378 None => None,
1379 };
1380 let log_memory_limit = match self.log_memory_limit {
1381 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1382 Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1383 None => None,
1384 };
1385 let environment_variables = match self.environment_variables {
1386 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1387 Some(Ok(x)) => Some(x),
1388 None => None,
1389 };
1390 let snapshot_visibility = match self.snapshot_visibility {
1391 Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1392 Some(Ok(x)) => Some(x),
1393 None => None,
1394 };
1395 CanisterSettings {
1396 controllers,
1397 compute_allocation,
1398 memory_allocation,
1399 freezing_threshold,
1400 reserved_cycles_limit,
1401 wasm_memory_limit,
1402 wasm_memory_threshold,
1403 log_visibility,
1404 log_memory_limit,
1405 environment_variables,
1406 snapshot_visibility,
1407 }
1408 };
1409
1410 Ok(self
1411 .canister
1412 .update(MgmtMethod::UpdateSettings.as_ref())
1413 .with_arg(In {
1414 canister_id: self.canister_id,
1415 settings,
1416 })
1417 .with_effective_canister_id(self.canister_id)
1418 .build())
1419 }
1420
1421 pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1423 self.build()?.call().await
1424 }
1425
1426 pub async fn call_and_wait(self) -> Result<(), AgentError> {
1428 self.build()?.call_and_wait().await
1429 }
1430}
1431
1432#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1433#[cfg_attr(not(target_family = "wasm"), async_trait)]
1434impl<'agent, 'canister: 'agent> AsyncCall for UpdateSettingsBuilder<'agent, 'canister> {
1435 type Value = ();
1436 async fn call(self) -> Result<CallResponse<()>, AgentError> {
1437 self.build()?.call().await
1438 }
1439
1440 async fn call_and_wait(self) -> Result<(), AgentError> {
1441 self.build()?.call_and_wait().await
1442 }
1443}
1444
1445impl<'agent, 'canister: 'agent> IntoFuture for UpdateSettingsBuilder<'agent, 'canister> {
1446 type IntoFuture = CallFuture<'agent, ()>;
1447 type Output = Result<(), AgentError>;
1448 fn into_future(self) -> Self::IntoFuture {
1449 AsyncCall::call_and_wait(self)
1450 }
1451}
1452
1453#[cfg(not(target_family = "wasm"))]
1454type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1455#[cfg(target_family = "wasm")]
1456type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;