ic_utils/interfaces/management_canister/
builders.rs

1//! Builder interfaces for some method calls of the management canister.
2
3#[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};
20pub use ic_management_canister_types::{
21    CanisterInstallMode, CanisterSettings, InstallCodeArgs, UpgradeFlags, WasmMemoryPersistence,
22};
23use sha2::{Digest, Sha256};
24use std::{
25    collections::BTreeSet,
26    convert::{From, TryInto},
27    future::IntoFuture,
28    pin::Pin,
29};
30
31/// A builder for a `create_canister` call.
32#[derive(Debug)]
33pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
34    canister: &'canister Canister<'agent>,
35    effective_canister_id: Principal,
36    controllers: Option<Result<Vec<Principal>, AgentError>>,
37    compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
38    memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
39    freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
40    reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
41    wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
42    wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
43    log_visibility: Option<Result<LogVisibility, AgentError>>,
44    is_provisional_create: bool,
45    amount: Option<u128>,
46    specified_id: Option<Principal>,
47}
48
49impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
50    /// Create an `CreateCanister` builder, which is also an `AsyncCall` implementation.
51    pub fn builder(canister: &'canister Canister<'agent>) -> Self {
52        Self {
53            canister,
54            effective_canister_id: Principal::management_canister(),
55            controllers: None,
56            compute_allocation: None,
57            memory_allocation: None,
58            freezing_threshold: None,
59            reserved_cycles_limit: None,
60            wasm_memory_limit: None,
61            wasm_memory_threshold: None,
62            log_visibility: None,
63            is_provisional_create: false,
64            amount: None,
65            specified_id: None,
66        }
67    }
68
69    /// Until developers can convert real ICP tokens to provision a new canister with cycles,
70    /// the system provides the `provisional_create_canister_with_cycles` method.
71    /// It behaves as `create_canister`, but initializes the canister’s balance with amount fresh cycles
72    /// (using `MAX_CANISTER_BALANCE` if amount = null, else capping the balance at `MAX_CANISTER_BALANCE`).
73    /// Cycles added to this call via `ic0.call_cycles_add` are returned to the caller.
74    /// This method is only available in local development instances, and will be removed in the future.
75    #[allow(clippy::wrong_self_convention)]
76    pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
77        Self {
78            is_provisional_create: true,
79            amount,
80            ..self
81        }
82    }
83
84    /// Specify the canister id.
85    ///
86    /// The `effective_canister_id` will also be set with the same value so that ic-ref can determine
87    /// the target subnet of this request. The replica implementation ignores it.
88    pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
89        Self {
90            is_provisional_create: true,
91            specified_id: Some(specified_id),
92            effective_canister_id: specified_id,
93            ..self
94        }
95    }
96
97    /// Pass in an effective canister id for the update call.
98    pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
99    where
100        E: std::fmt::Display,
101        C: TryInto<Principal, Error = E>,
102    {
103        match effective_canister_id.try_into() {
104            Ok(effective_canister_id) => Self {
105                effective_canister_id,
106                ..self
107            },
108            Err(_) => self,
109        }
110    }
111
112    /// Pass in an optional controller for the canister. If this is [`None`],
113    /// it will revert the controller to default.
114    pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
115    where
116        E: std::fmt::Display,
117        C: TryInto<Principal, Error = E>,
118    {
119        let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
120            ca.try_into()
121                .map_err(|e| AgentError::MessageError(format!("{e}")))
122        });
123        let controllers: Option<Result<Vec<Principal>, _>> =
124            match (controller_to_add, self.controllers) {
125                (_, Some(Err(sticky))) => Some(Err(sticky)),
126                (Some(Err(e)), _) => Some(Err(e)),
127                (None, _) => None,
128                (Some(Ok(controller)), Some(Ok(controllers))) => {
129                    let mut controllers = controllers;
130                    controllers.push(controller);
131                    Some(Ok(controllers))
132                }
133                (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
134            };
135        Self {
136            controllers,
137            ..self
138        }
139    }
140
141    /// Pass in a designated controller for the canister.
142    pub fn with_controller<C, E>(self, controller: C) -> Self
143    where
144        E: std::fmt::Display,
145        C: TryInto<Principal, Error = E>,
146    {
147        self.with_optional_controller(Some(controller))
148    }
149
150    /// Pass in a compute allocation optional value for the canister. If this is [`None`],
151    /// it will revert the compute allocation to default.
152    pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
153    where
154        E: std::fmt::Display,
155        C: TryInto<ComputeAllocation, Error = E>,
156    {
157        Self {
158            compute_allocation: compute_allocation.map(|ca| {
159                ca.try_into()
160                    .map_err(|e| AgentError::MessageError(format!("{e}")))
161            }),
162            ..self
163        }
164    }
165
166    /// Pass in a compute allocation value for the canister.
167    pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
168    where
169        E: std::fmt::Display,
170        C: TryInto<ComputeAllocation, Error = E>,
171    {
172        self.with_optional_compute_allocation(Some(compute_allocation))
173    }
174
175    /// Pass in a memory allocation optional value for the canister. If this is [`None`],
176    /// it will revert the memory allocation to default.
177    pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
178    where
179        E: std::fmt::Display,
180        C: TryInto<MemoryAllocation, Error = E>,
181    {
182        Self {
183            memory_allocation: memory_allocation.map(|ma| {
184                ma.try_into()
185                    .map_err(|e| AgentError::MessageError(format!("{e}")))
186            }),
187            ..self
188        }
189    }
190
191    /// Pass in a memory allocation value for the canister.
192    pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
193    where
194        E: std::fmt::Display,
195        C: TryInto<MemoryAllocation, Error = E>,
196    {
197        self.with_optional_memory_allocation(Some(memory_allocation))
198    }
199
200    /// Pass in a freezing threshold optional value for the canister. If this is [`None`],
201    /// it will revert the freezing threshold to default.
202    pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
203    where
204        E: std::fmt::Display,
205        C: TryInto<FreezingThreshold, Error = E>,
206    {
207        Self {
208            freezing_threshold: freezing_threshold.map(|ma| {
209                ma.try_into()
210                    .map_err(|e| AgentError::MessageError(format!("{e}")))
211            }),
212            ..self
213        }
214    }
215
216    /// Pass in a freezing threshold value for the canister.
217    pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
218    where
219        E: std::fmt::Display,
220        C: TryInto<FreezingThreshold, Error = E>,
221    {
222        self.with_optional_freezing_threshold(Some(freezing_threshold))
223    }
224
225    /// Pass in a reserved cycles limit value for the canister.
226    pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
227    where
228        E: std::fmt::Display,
229        C: TryInto<ReservedCyclesLimit, Error = E>,
230    {
231        self.with_optional_reserved_cycles_limit(Some(limit))
232    }
233
234    /// Pass in a reserved cycles limit optional value for the canister. If this is [`None`],
235    /// it will create the canister with the default limit.
236    pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
237    where
238        E: std::fmt::Display,
239        C: TryInto<ReservedCyclesLimit, Error = E>,
240    {
241        Self {
242            reserved_cycles_limit: limit.map(|limit| {
243                limit
244                    .try_into()
245                    .map_err(|e| AgentError::MessageError(format!("{e}")))
246            }),
247            ..self
248        }
249    }
250
251    /// Pass in a Wasm memory limit value for the canister.
252    pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
253    where
254        E: std::fmt::Display,
255        C: TryInto<WasmMemoryLimit, Error = E>,
256    {
257        self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
258    }
259
260    /// Pass in a Wasm memory limit optional value for the canister. If this is [`None`],
261    /// it will revert the Wasm memory limit to default.
262    pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
263    where
264        E: std::fmt::Display,
265        C: TryInto<WasmMemoryLimit, Error = E>,
266    {
267        Self {
268            wasm_memory_limit: wasm_memory_limit.map(|limit| {
269                limit
270                    .try_into()
271                    .map_err(|e| AgentError::MessageError(format!("{e}")))
272            }),
273            ..self
274        }
275    }
276
277    /// Pass in a Wasm memory threshold value for the canister.
278    pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
279    where
280        E: std::fmt::Display,
281        C: TryInto<WasmMemoryLimit, Error = E>,
282    {
283        self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
284    }
285
286    /// Pass in a Wasm memory threshold optional value for the canister. If this is [`None`],
287    /// it will revert the Wasm memory threshold to default.
288    pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
289    where
290        E: std::fmt::Display,
291        C: TryInto<WasmMemoryLimit, Error = E>,
292    {
293        Self {
294            wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
295                limit
296                    .try_into()
297                    .map_err(|e| AgentError::MessageError(format!("{e}")))
298            }),
299            ..self
300        }
301    }
302
303    /// Pass in a log visibility setting for the canister.
304    pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
305    where
306        E: std::fmt::Display,
307        C: TryInto<LogVisibility, Error = E>,
308    {
309        self.with_optional_log_visibility(Some(log_visibility))
310    }
311
312    /// Pass in a log visibility optional setting for the canister. If this is [`None`],
313    /// it will revert the log visibility to default.
314    pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
315    where
316        E: std::fmt::Display,
317        C: TryInto<LogVisibility, Error = E>,
318    {
319        Self {
320            log_visibility: log_visibility.map(|visibility| {
321                visibility
322                    .try_into()
323                    .map_err(|e| AgentError::MessageError(format!("{e}")))
324            }),
325            ..self
326        }
327    }
328
329    /// Create an [`AsyncCall`] implementation that, when called, will create a
330    /// canister.
331    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
332        let controllers = match self.controllers {
333            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
334            Some(Ok(x)) => Some(x),
335            None => None,
336        };
337        let compute_allocation = match self.compute_allocation {
338            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
339            Some(Ok(x)) => Some(Nat::from(u8::from(x))),
340            None => None,
341        };
342        let memory_allocation = match self.memory_allocation {
343            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
344            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
345            None => None,
346        };
347        let freezing_threshold = match self.freezing_threshold {
348            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
349            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
350            None => None,
351        };
352        let reserved_cycles_limit = match self.reserved_cycles_limit {
353            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
354            Some(Ok(x)) => Some(Nat::from(u128::from(x))),
355            None => None,
356        };
357        let wasm_memory_limit = match self.wasm_memory_limit {
358            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
359            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
360            None => None,
361        };
362        let wasm_memory_threshold = match self.wasm_memory_threshold {
363            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
364            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
365            None => None,
366        };
367        let log_visibility = match self.log_visibility {
368            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
369            Some(Ok(x)) => Some(x),
370            None => None,
371        };
372
373        #[derive(Deserialize, CandidType)]
374        struct Out {
375            canister_id: Principal,
376        }
377
378        let async_builder = if self.is_provisional_create {
379            #[derive(CandidType)]
380            struct In {
381                amount: Option<Nat>,
382                settings: CanisterSettings,
383                specified_id: Option<Principal>,
384            }
385            let in_arg = In {
386                amount: self.amount.map(Nat::from),
387                settings: CanisterSettings {
388                    controllers,
389                    compute_allocation,
390                    memory_allocation,
391                    freezing_threshold,
392                    reserved_cycles_limit,
393                    wasm_memory_limit,
394                    wasm_memory_threshold,
395                    log_visibility,
396                },
397                specified_id: self.specified_id,
398            };
399            self.canister
400                .update(MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref())
401                .with_arg(in_arg)
402                .with_effective_canister_id(self.effective_canister_id)
403        } else {
404            self.canister
405                .update(MgmtMethod::CreateCanister.as_ref())
406                .with_arg(CanisterSettings {
407                    controllers,
408                    compute_allocation,
409                    memory_allocation,
410                    freezing_threshold,
411                    reserved_cycles_limit,
412                    wasm_memory_limit,
413                    wasm_memory_threshold,
414                    log_visibility,
415                })
416                .with_effective_canister_id(self.effective_canister_id)
417        };
418
419        Ok(async_builder
420            .build()
421            .map(|result: (Out,)| (result.0.canister_id,)))
422    }
423
424    /// Make a call. This is equivalent to the [`AsyncCall::call`].
425    pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
426        self.build()?.call().await
427    }
428
429    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
430    pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
431        self.build()?.call_and_wait().await
432    }
433}
434
435#[cfg_attr(target_family = "wasm", async_trait(?Send))]
436#[cfg_attr(not(target_family = "wasm"), async_trait)]
437impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
438    type Value = (Principal,);
439
440    async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
441        self.build()?.call().await
442    }
443
444    async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
445        self.build()?.call_and_wait().await
446    }
447}
448
449impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
450    type IntoFuture = CallFuture<'agent, (Principal,)>;
451    type Output = Result<(Principal,), AgentError>;
452
453    fn into_future(self) -> Self::IntoFuture {
454        AsyncCall::call_and_wait(self)
455    }
456}
457
458#[doc(hidden)]
459#[deprecated(since = "0.42.0", note = "Please use UpgradeFlags instead")]
460pub type CanisterUpgradeOptions = UpgradeFlags;
461
462#[doc(hidden)]
463#[deprecated(since = "0.42.0", note = "Please use CanisterInstallMode instead")]
464pub type InstallMode = CanisterInstallMode;
465
466#[doc(hidden)]
467#[deprecated(since = "0.42.0", note = "Please use InstallCodeArgs instead")]
468pub type CanisterInstall = InstallCodeArgs;
469
470/// A builder for an `install_code` call.
471#[derive(Debug)]
472pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
473    canister: &'canister Canister<'agent>,
474    canister_id: Principal,
475    wasm: &'canister [u8],
476    arg: Argument,
477    mode: Option<CanisterInstallMode>,
478}
479
480impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
481    /// Create an `InstallCode` builder, which is also an `AsyncCall` implementation.
482    pub fn builder(
483        canister: &'canister Canister<'agent>,
484        canister_id: &Principal,
485        wasm: &'canister [u8],
486    ) -> Self {
487        Self {
488            canister,
489            canister_id: *canister_id,
490            wasm,
491            arg: Default::default(),
492            mode: None,
493        }
494    }
495
496    /// Set the argument to the installation, which will be passed to the init
497    /// method of the canister. Can be called at most once.
498    pub fn with_arg<Argument: CandidType>(
499        mut self,
500        arg: Argument,
501    ) -> InstallCodeBuilder<'agent, 'canister> {
502        self.arg.set_idl_arg(arg);
503        self
504    }
505    /// Set the argument with multiple arguments as tuple to the installation,
506    /// which will be passed to the init method of the canister. Can be called at most once.
507    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
508        assert!(self.arg.0.is_none(), "argument is being set more than once");
509        self.arg = Argument::from_candid(tuple);
510        self
511    }
512    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
513    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
514        self.arg.set_raw_arg(arg);
515        self
516    }
517
518    /// Pass in the [`CanisterInstallMode`].
519    pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
520        Self {
521            mode: Some(mode),
522            ..self
523        }
524    }
525
526    /// Create an [`AsyncCall`] implementation that, when called, will install the
527    /// canister.
528    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
529        Ok(self
530            .canister
531            .update(MgmtMethod::InstallCode.as_ref())
532            .with_arg(InstallCodeArgs {
533                mode: self.mode.unwrap_or(CanisterInstallMode::Install),
534                canister_id: self.canister_id,
535                wasm_module: self.wasm.to_owned(),
536                arg: self.arg.serialize()?,
537                sender_canister_version: None,
538            })
539            .with_effective_canister_id(self.canister_id)
540            .build())
541    }
542
543    /// Make a call. This is equivalent to the [`AsyncCall::call`].
544    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
545        self.build()?.call().await
546    }
547
548    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
549    pub async fn call_and_wait(self) -> Result<(), AgentError> {
550        self.build()?.call_and_wait().await
551    }
552}
553
554#[cfg_attr(target_family = "wasm", async_trait(?Send))]
555#[cfg_attr(not(target_family = "wasm"), async_trait)]
556impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
557    type Value = ();
558
559    async fn call(self) -> Result<CallResponse<()>, AgentError> {
560        self.build()?.call().await
561    }
562
563    async fn call_and_wait(self) -> Result<(), AgentError> {
564        self.build()?.call_and_wait().await
565    }
566}
567
568impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
569    type IntoFuture = CallFuture<'agent, ()>;
570    type Output = Result<(), AgentError>;
571
572    fn into_future(self) -> Self::IntoFuture {
573        AsyncCall::call_and_wait(self)
574    }
575}
576
577/// A builder for an `install_chunked_code` call.
578#[derive(Debug)]
579pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
580    canister: &'canister Canister<'agent>,
581    target_canister: Principal,
582    store_canister: Option<Principal>,
583    chunk_hashes_list: Vec<ChunkHash>,
584    wasm_module_hash: Vec<u8>,
585    arg: Argument,
586    mode: CanisterInstallMode,
587}
588
589impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
590    /// Create an `InstallChunkedCodeBuilder`.
591    pub fn builder(
592        canister: &'canister Canister<'agent>,
593        target_canister: Principal,
594        wasm_module_hash: &[u8],
595    ) -> Self {
596        Self {
597            canister,
598            target_canister,
599            wasm_module_hash: wasm_module_hash.to_vec(),
600            store_canister: None,
601            chunk_hashes_list: vec![],
602            arg: Argument::new(),
603            mode: CanisterInstallMode::Install,
604        }
605    }
606
607    /// Set the chunks to install. These must previously have been set with [`ManagementCanister::upload_chunk`].
608    pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
609        self.chunk_hashes_list = chunk_hashes;
610        self
611    }
612
613    /// Set the canister to pull uploaded chunks from. By default this is the same as the target canister.
614    pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
615        self.store_canister = Some(store_canister);
616        self
617    }
618
619    /// Set the argument to the installation, which will be passed to the init
620    /// method of the canister. Can be called at most once.
621    pub fn with_arg(mut self, argument: impl CandidType) -> Self {
622        self.arg.set_idl_arg(argument);
623        self
624    }
625
626    /// Set the argument with multiple arguments as tuple to the installation,
627    /// which will be passed to the init method of the canister. Can be called at most once.
628    pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
629        assert!(self.arg.0.is_none(), "argument is being set more than once");
630        self.arg = Argument::from_candid(argument);
631        self
632    }
633
634    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
635    pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
636        self.arg.set_raw_arg(argument);
637        self
638    }
639
640    /// Set the [`CanisterInstallMode`].
641    pub fn with_install_mode(mut self, mode: CanisterInstallMode) -> Self {
642        self.mode = mode;
643        self
644    }
645
646    /// Create an [`AsyncCall`] implementation that, when called, will install the canister.
647    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
648        #[derive(CandidType)]
649        struct In {
650            mode: CanisterInstallMode,
651            target_canister: Principal,
652            store_canister: Option<Principal>,
653            chunk_hashes_list: Vec<ChunkHash>,
654            wasm_module_hash: Vec<u8>,
655            arg: Vec<u8>,
656            sender_canister_version: Option<u64>,
657        }
658        let Self {
659            mode,
660            target_canister,
661            store_canister,
662            chunk_hashes_list,
663            wasm_module_hash,
664            arg,
665            ..
666        } = self;
667        Ok(self
668            .canister
669            .update(MgmtMethod::InstallChunkedCode.as_ref())
670            .with_arg(In {
671                mode,
672                target_canister,
673                store_canister,
674                chunk_hashes_list,
675                wasm_module_hash,
676                arg: arg.serialize()?,
677                sender_canister_version: None,
678            })
679            .with_effective_canister_id(target_canister)
680            .build())
681    }
682
683    /// Make the call. This is equivalent to [`AsyncCall::call`].
684    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
685        self.build()?.call().await
686    }
687
688    /// Make the call. This is equivalent to [`AsyncCall::call_and_wait`].
689    pub async fn call_and_wait(self) -> Result<(), AgentError> {
690        self.build()?.call_and_wait().await
691    }
692}
693
694#[cfg_attr(target_family = "wasm", async_trait(?Send))]
695#[cfg_attr(not(target_family = "wasm"), async_trait)]
696impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
697    type Value = ();
698
699    async fn call(self) -> Result<CallResponse<()>, AgentError> {
700        self.call().await
701    }
702
703    async fn call_and_wait(self) -> Result<(), AgentError> {
704        self.call_and_wait().await
705    }
706}
707
708impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
709    type IntoFuture = CallFuture<'agent, ()>;
710    type Output = Result<(), AgentError>;
711
712    fn into_future(self) -> Self::IntoFuture {
713        AsyncCall::call_and_wait(self)
714    }
715}
716
717/// A builder for a [`ManagementCanister::install`] call. This automatically selects one-shot installation or chunked installation depending on module size.
718///
719/// # Warnings
720///
721/// This will clear chunked code storage if chunked installation is used. Do not use with canisters that you are manually uploading chunked code to.
722#[derive(Debug)]
723pub struct InstallBuilder<'agent, 'canister, 'builder> {
724    canister: &'canister ManagementCanister<'agent>,
725    canister_id: Principal,
726    // more precise lifetimes are used here at risk of annoying the user
727    // because `wasm` may be memory-mapped which is tricky to lifetime
728    wasm: &'builder [u8],
729    arg: Argument,
730    mode: CanisterInstallMode,
731}
732
733impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
734    // Messages are a maximum of 2MiB. Thus basic installation should cap the wasm and arg size at 1.85MiB, since
735    // the current API is definitely not going to produce 150KiB of framing data for it.
736    const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
737
738    /// Create a canister installation builder.
739    pub fn builder(
740        canister: &'canister ManagementCanister<'agent>,
741        canister_id: &Principal,
742        wasm: &'builder [u8],
743    ) -> Self {
744        Self {
745            canister,
746            canister_id: *canister_id,
747            wasm,
748            arg: Default::default(),
749            mode: CanisterInstallMode::Install,
750        }
751    }
752
753    /// Set the argument to the installation, which will be passed to the init
754    /// method of the canister. Can be called at most once.
755    pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
756        self.arg.set_idl_arg(arg);
757        self
758    }
759    /// Set the argument with multiple arguments as tuple to the installation,
760    /// which will be passed to the init method of the canister. Can be called at most once.
761    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
762        assert!(self.arg.0.is_none(), "argument is being set more than once");
763        self.arg = Argument::from_candid(tuple);
764        self
765    }
766    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
767    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
768        self.arg.set_raw_arg(arg);
769        self
770    }
771
772    /// Pass in the [`CanisterInstallMode`].
773    pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
774        Self { mode, ..self }
775    }
776
777    /// Invoke the installation process. This may result in many calls which may take several seconds;
778    /// use [`call_and_wait_with_progress`](Self::call_and_wait_with_progress) if you want progress reporting.
779    pub async fn call_and_wait(self) -> Result<(), AgentError> {
780        self.call_and_wait_with_progress()
781            .await
782            .try_for_each(|_| ready(Ok(())))
783            .await
784    }
785
786    /// Invoke the installation process. The returned stream must be iterated to completion; it is used to track progress,
787    /// as installation may take arbitrarily long, and is intended to be passed to functions like `indicatif::ProgressBar::wrap_stream`.
788    /// There are exactly [`size_hint().0`](Stream::size_hint) steps.
789    pub async fn call_and_wait_with_progress(
790        self,
791    ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
792        let stream_res = /* try { */ async move {
793            let arg = self.arg.serialize()?;
794            let stream: BoxStream<'_, _> =
795                if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
796                    Box::pin(
797                        async move {
798                            self.canister
799                                .install_code(&self.canister_id, self.wasm)
800                                .with_raw_arg(arg)
801                                .with_mode(self.mode)
802                                .call_and_wait()
803                                .await
804                        }
805                        .into_stream(),
806                    )
807                } else {
808                    let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
809                    let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
810                    let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
811                    let mut to_upload_chunks = vec![];
812                    for (hash, chunk) in &all_chunks {
813                        if !existing_chunks.contains(hash) {
814                            to_upload_chunks.push(*chunk);
815                        }
816                    }
817
818                    let upload_chunks_stream = FuturesUnordered::new();
819                    for chunk in to_upload_chunks {
820                        upload_chunks_stream.push(async move {
821                            let (_res,) = self
822                                .canister
823                                .upload_chunk(&self.canister_id, &ic_management_canister_types::UploadChunkArgs {
824                                    canister_id: self.canister_id,
825                                    chunk: chunk.to_vec(),
826                                })
827                                .call_and_wait()
828                                .await?;
829                            Ok(())
830                        });
831                    }
832                    let install_chunked_code_stream = async move {
833                        let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
834                        self.canister
835                            .install_chunked_code(
836                                &self.canister_id,
837                                &Sha256::digest(self.wasm),
838                            )
839                            .with_chunk_hashes(results)
840                            .with_raw_arg(arg)
841                            .with_install_mode(self.mode)
842                            .call_and_wait()
843                            .await
844                    }
845                    .into_stream();
846                    let clear_store_stream = async move {
847                        self.canister
848                            .clear_chunk_store(&self.canister_id)
849                            .call_and_wait()
850                            .await
851                    }
852                    .into_stream();
853
854                    Box::pin(
855                        upload_chunks_stream
856                            .chain(install_chunked_code_stream)
857                            .chain(clear_store_stream                        ),
858                    )
859                };
860            Ok(stream)
861        }.await;
862        match stream_res {
863            Ok(stream) => stream,
864            Err(err) => Box::pin(stream::once(async { Err(err) })),
865        }
866    }
867}
868
869impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
870    for InstallBuilder<'agent, 'canister, 'builder>
871{
872    type IntoFuture = CallFuture<'builder, ()>;
873    type Output = Result<(), AgentError>;
874
875    fn into_future(self) -> Self::IntoFuture {
876        Box::pin(self.call_and_wait())
877    }
878}
879
880/// A builder for an `update_settings` call.
881#[derive(Debug)]
882pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
883    canister: &'canister Canister<'agent>,
884    canister_id: Principal,
885    controllers: Option<Result<Vec<Principal>, AgentError>>,
886    compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
887    memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
888    freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
889    reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
890    wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
891    wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
892    log_visibility: Option<Result<LogVisibility, AgentError>>,
893}
894
895impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
896    /// Create an `UpdateCanister` builder, which is also an `AsyncCall` implementation.
897    pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
898        Self {
899            canister,
900            canister_id: *canister_id,
901            controllers: None,
902            compute_allocation: None,
903            memory_allocation: None,
904            freezing_threshold: None,
905            reserved_cycles_limit: None,
906            wasm_memory_limit: None,
907            wasm_memory_threshold: None,
908            log_visibility: None,
909        }
910    }
911
912    /// Pass in an optional controller for the canister. If this is [`None`],
913    /// it will revert the controller to default.
914    pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
915    where
916        E: std::fmt::Display,
917        C: TryInto<Principal, Error = E>,
918    {
919        let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
920            ca.try_into()
921                .map_err(|e| AgentError::MessageError(format!("{e}")))
922        });
923        let controllers: Option<Result<Vec<Principal>, _>> =
924            match (controller_to_add, self.controllers) {
925                (_, Some(Err(sticky))) => Some(Err(sticky)),
926                (Some(Err(e)), _) => Some(Err(e)),
927                (None, _) => None,
928                (Some(Ok(controller)), Some(Ok(controllers))) => {
929                    let mut controllers = controllers;
930                    controllers.push(controller);
931                    Some(Ok(controllers))
932                }
933                (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
934            };
935
936        Self {
937            controllers,
938            ..self
939        }
940    }
941
942    /// Pass in a designated controller for the canister.
943    pub fn with_controller<C, E>(self, controller: C) -> Self
944    where
945        E: std::fmt::Display,
946        C: TryInto<Principal, Error = E>,
947    {
948        self.with_optional_controller(Some(controller))
949    }
950
951    /// Pass in a compute allocation optional value for the canister. If this is [`None`],
952    /// it will revert the compute allocation to default.
953    pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
954    where
955        E: std::fmt::Display,
956        C: TryInto<ComputeAllocation, Error = E>,
957    {
958        Self {
959            compute_allocation: compute_allocation.map(|ca| {
960                ca.try_into()
961                    .map_err(|e| AgentError::MessageError(format!("{e}")))
962            }),
963            ..self
964        }
965    }
966
967    /// Pass in a compute allocation value for the canister.
968    pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
969    where
970        E: std::fmt::Display,
971        C: TryInto<ComputeAllocation, Error = E>,
972    {
973        self.with_optional_compute_allocation(Some(compute_allocation))
974    }
975
976    /// Pass in a memory allocation optional value for the canister. If this is [`None`],
977    /// it will revert the memory allocation to default.
978    pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
979    where
980        E: std::fmt::Display,
981        C: TryInto<MemoryAllocation, Error = E>,
982    {
983        Self {
984            memory_allocation: memory_allocation.map(|ma| {
985                ma.try_into()
986                    .map_err(|e| AgentError::MessageError(format!("{e}")))
987            }),
988            ..self
989        }
990    }
991
992    /// Pass in a memory allocation value for the canister.
993    pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
994    where
995        E: std::fmt::Display,
996        C: TryInto<MemoryAllocation, Error = E>,
997    {
998        self.with_optional_memory_allocation(Some(memory_allocation))
999    }
1000
1001    /// Pass in a freezing threshold optional value for the canister. If this is [`None`],
1002    /// it will revert the freezing threshold to default.
1003    pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
1004    where
1005        E: std::fmt::Display,
1006        C: TryInto<FreezingThreshold, Error = E>,
1007    {
1008        Self {
1009            freezing_threshold: freezing_threshold.map(|ma| {
1010                ma.try_into()
1011                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1012            }),
1013            ..self
1014        }
1015    }
1016
1017    /// Pass in a freezing threshold value for the canister.
1018    pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1019    where
1020        E: std::fmt::Display,
1021        C: TryInto<FreezingThreshold, Error = E>,
1022    {
1023        self.with_optional_freezing_threshold(Some(freezing_threshold))
1024    }
1025
1026    /// Pass in a reserved cycles limit value for the canister.
1027    pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1028    where
1029        E: std::fmt::Display,
1030        C: TryInto<ReservedCyclesLimit, Error = E>,
1031    {
1032        self.with_optional_reserved_cycles_limit(Some(limit))
1033    }
1034
1035    /// Pass in a reserved cycles limit optional value for the canister.
1036    /// If this is [`None`], leaves the reserved cycles limit unchanged.
1037    pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
1038    where
1039        E: std::fmt::Display,
1040        C: TryInto<ReservedCyclesLimit, Error = E>,
1041    {
1042        Self {
1043            reserved_cycles_limit: limit.map(|ma| {
1044                ma.try_into()
1045                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1046            }),
1047            ..self
1048        }
1049    }
1050
1051    /// Pass in a Wasm memory limit value for the canister.
1052    pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1053    where
1054        E: std::fmt::Display,
1055        C: TryInto<WasmMemoryLimit, Error = E>,
1056    {
1057        self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
1058    }
1059
1060    /// Pass in a Wasm memory limit optional value for the canister. If this is [`None`],
1061    /// leaves the Wasm memory limit unchanged.
1062    pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
1063    where
1064        E: std::fmt::Display,
1065        C: TryInto<WasmMemoryLimit, Error = E>,
1066    {
1067        Self {
1068            wasm_memory_limit: wasm_memory_limit.map(|limit| {
1069                limit
1070                    .try_into()
1071                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1072            }),
1073            ..self
1074        }
1075    }
1076
1077    /// Pass in a Wasm memory threshold value for the canister.
1078    pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1079    where
1080        E: std::fmt::Display,
1081        C: TryInto<WasmMemoryLimit, Error = E>,
1082    {
1083        self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
1084    }
1085
1086    /// Pass in a Wasm memory threshold value for the canister. If this is [`None`],
1087    /// leaves the memory threshold unchanged.
1088    pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
1089    where
1090        E: std::fmt::Display,
1091        C: TryInto<WasmMemoryLimit, Error = E>,
1092    {
1093        Self {
1094            wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
1095                limit
1096                    .try_into()
1097                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1098            }),
1099            ..self
1100        }
1101    }
1102
1103    /// Pass in a log visibility setting for the canister.
1104    pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1105    where
1106        E: std::fmt::Display,
1107        C: TryInto<LogVisibility, Error = E>,
1108    {
1109        self.with_optional_log_visibility(Some(log_visibility))
1110    }
1111
1112    /// Pass in a log visibility optional setting for the canister. If this is [`None`],
1113    /// leaves the log visibility unchanged.
1114    pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
1115    where
1116        E: std::fmt::Display,
1117        C: TryInto<LogVisibility, Error = E>,
1118    {
1119        Self {
1120            log_visibility: log_visibility.map(|limit| {
1121                limit
1122                    .try_into()
1123                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1124            }),
1125            ..self
1126        }
1127    }
1128
1129    /// Create an [`AsyncCall`] implementation that, when called, will update a
1130    /// canisters settings.
1131    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1132        #[derive(CandidType)]
1133        struct In {
1134            canister_id: Principal,
1135            settings: CanisterSettings,
1136        }
1137
1138        let controllers = match self.controllers {
1139            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1140            Some(Ok(x)) => Some(x),
1141            None => None,
1142        };
1143        let compute_allocation = match self.compute_allocation {
1144            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1145            Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1146            None => None,
1147        };
1148        let memory_allocation = match self.memory_allocation {
1149            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1150            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1151            None => None,
1152        };
1153        let freezing_threshold = match self.freezing_threshold {
1154            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1155            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1156            None => None,
1157        };
1158        let reserved_cycles_limit = match self.reserved_cycles_limit {
1159            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1160            Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1161            None => None,
1162        };
1163        let wasm_memory_limit = match self.wasm_memory_limit {
1164            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1165            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1166            None => None,
1167        };
1168        let wasm_memory_threshold = match self.wasm_memory_threshold {
1169            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1170            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1171            None => None,
1172        };
1173        let log_visibility = match self.log_visibility {
1174            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1175            Some(Ok(x)) => Some(x),
1176            None => None,
1177        };
1178
1179        Ok(self
1180            .canister
1181            .update(MgmtMethod::UpdateSettings.as_ref())
1182            .with_arg(In {
1183                canister_id: self.canister_id,
1184                settings: CanisterSettings {
1185                    controllers,
1186                    compute_allocation,
1187                    memory_allocation,
1188                    freezing_threshold,
1189                    reserved_cycles_limit,
1190                    wasm_memory_limit,
1191                    wasm_memory_threshold,
1192                    log_visibility,
1193                },
1194            })
1195            .with_effective_canister_id(self.canister_id)
1196            .build())
1197    }
1198
1199    /// Make a call. This is equivalent to the [`AsyncCall::call`].
1200    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1201        self.build()?.call().await
1202    }
1203
1204    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
1205    pub async fn call_and_wait(self) -> Result<(), AgentError> {
1206        self.build()?.call_and_wait().await
1207    }
1208}
1209
1210#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1211#[cfg_attr(not(target_family = "wasm"), async_trait)]
1212impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> {
1213    type Value = ();
1214    async fn call(self) -> Result<CallResponse<()>, AgentError> {
1215        self.build()?.call().await
1216    }
1217
1218    async fn call_and_wait(self) -> Result<(), AgentError> {
1219        self.build()?.call_and_wait().await
1220    }
1221}
1222
1223impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> {
1224    type IntoFuture = CallFuture<'agent, ()>;
1225    type Output = Result<(), AgentError>;
1226    fn into_future(self) -> Self::IntoFuture {
1227        AsyncCall::call_and_wait(self)
1228    }
1229}
1230
1231#[cfg(not(target_family = "wasm"))]
1232type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1233#[cfg(target_family = "wasm")]
1234type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;