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