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    Keep,
528    /// Reinitialize the main memory on upgrade.
529    /// Default behavior without enhanced orthogonal persistence.
530    Replace,
531}
532
533#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
534/// Upgrade options.
535pub struct CanisterUpgradeOptions {
536    /// Skip pre-upgrade hook. Only for exceptional cases, see the IC documentation. Not useful for Motoko.
537    pub skip_pre_upgrade: Option<bool>,
538    /// Support for enhanced orthogonal persistence: Retain the main memory on upgrade.
539    pub wasm_memory_persistence: Option<WasmMemoryPersistence>,
540}
541
542/// The install mode of the canister to install. If a canister is already installed,
543/// using [`InstallMode::Install`] will be an error. [`InstallMode::Reinstall`] overwrites
544/// the module, and [`InstallMode::Upgrade`] performs an Upgrade step.
545#[derive(Debug, Copy, Clone, CandidType, Deserialize, Eq, PartialEq)]
546pub enum InstallMode {
547    /// Install the module into the empty canister.
548    #[serde(rename = "install")]
549    Install,
550    /// Overwrite the canister with this module.
551    #[serde(rename = "reinstall")]
552    Reinstall,
553    /// Upgrade the canister with this module and some options.
554    #[serde(rename = "upgrade")]
555    Upgrade(Option<CanisterUpgradeOptions>),
556}
557
558/// A prepared call to `install_code`.
559#[derive(Debug, Clone, CandidType, Deserialize)]
560pub struct CanisterInstall {
561    /// The installation mode to install the module with.
562    pub mode: InstallMode,
563    /// The ID of the canister to install the module into.
564    pub canister_id: Principal,
565    /// The WebAssembly code blob to install.
566    #[serde(with = "serde_bytes")]
567    pub wasm_module: Vec<u8>,
568    /// The encoded argument to pass to the module's constructor.
569    #[serde(with = "serde_bytes")]
570    pub arg: Vec<u8>,
571}
572
573impl FromStr for InstallMode {
574    type Err = String;
575
576    fn from_str(s: &str) -> Result<Self, Self::Err> {
577        match s {
578            "install" => Ok(InstallMode::Install),
579            "reinstall" => Ok(InstallMode::Reinstall),
580            "upgrade" => Ok(InstallMode::Upgrade(None)),
581            &_ => Err(format!("Invalid install mode: {s}")),
582        }
583    }
584}
585
586/// A builder for an `install_code` call.
587#[derive(Debug)]
588pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
589    canister: &'canister Canister<'agent>,
590    canister_id: Principal,
591    wasm: &'canister [u8],
592    arg: Argument,
593    mode: Option<InstallMode>,
594}
595
596impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
597    /// Create an `InstallCode` builder, which is also an `AsyncCall` implementation.
598    pub fn builder(
599        canister: &'canister Canister<'agent>,
600        canister_id: &Principal,
601        wasm: &'canister [u8],
602    ) -> Self {
603        Self {
604            canister,
605            canister_id: *canister_id,
606            wasm,
607            arg: Default::default(),
608            mode: None,
609        }
610    }
611
612    /// Set the argument to the installation, which will be passed to the init
613    /// method of the canister. Can be called at most once.
614    pub fn with_arg<Argument: CandidType>(
615        mut self,
616        arg: Argument,
617    ) -> InstallCodeBuilder<'agent, 'canister> {
618        self.arg.set_idl_arg(arg);
619        self
620    }
621    /// Set the argument with multiple arguments as tuple to the installation,
622    /// which will be passed to the init method of the canister. Can be called at most once.
623    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
624        assert!(self.arg.0.is_none(), "argument is being set more than once");
625        self.arg = Argument::from_candid(tuple);
626        self
627    }
628    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
629    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
630        self.arg.set_raw_arg(arg);
631        self
632    }
633
634    /// Pass in the [`InstallMode`].
635    pub fn with_mode(self, mode: InstallMode) -> Self {
636        Self {
637            mode: Some(mode),
638            ..self
639        }
640    }
641
642    /// Create an [`AsyncCall`] implementation that, when called, will install the
643    /// canister.
644    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
645        Ok(self
646            .canister
647            .update(MgmtMethod::InstallCode.as_ref())
648            .with_arg(CanisterInstall {
649                mode: self.mode.unwrap_or(InstallMode::Install),
650                canister_id: self.canister_id,
651                wasm_module: self.wasm.to_owned(),
652                arg: self.arg.serialize()?,
653            })
654            .with_effective_canister_id(self.canister_id)
655            .build())
656    }
657
658    /// Make a call. This is equivalent to the [`AsyncCall::call`].
659    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
660        self.build()?.call().await
661    }
662
663    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
664    pub async fn call_and_wait(self) -> Result<(), AgentError> {
665        self.build()?.call_and_wait().await
666    }
667}
668
669#[cfg_attr(target_family = "wasm", async_trait(?Send))]
670#[cfg_attr(not(target_family = "wasm"), async_trait)]
671impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
672    type Value = ();
673
674    async fn call(self) -> Result<CallResponse<()>, AgentError> {
675        self.build()?.call().await
676    }
677
678    async fn call_and_wait(self) -> Result<(), AgentError> {
679        self.build()?.call_and_wait().await
680    }
681}
682
683impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
684    type IntoFuture = CallFuture<'agent, ()>;
685    type Output = Result<(), AgentError>;
686
687    fn into_future(self) -> Self::IntoFuture {
688        AsyncCall::call_and_wait(self)
689    }
690}
691
692/// A builder for an `install_chunked_code` call.
693#[derive(Debug)]
694pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
695    canister: &'canister Canister<'agent>,
696    target_canister: Principal,
697    store_canister: Option<Principal>,
698    chunk_hashes_list: Vec<ChunkHash>,
699    wasm_module_hash: Vec<u8>,
700    arg: Argument,
701    mode: InstallMode,
702}
703
704impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
705    /// Create an `InstallChunkedCodeBuilder`.
706    pub fn builder(
707        canister: &'canister Canister<'agent>,
708        target_canister: Principal,
709        wasm_module_hash: &[u8],
710    ) -> Self {
711        Self {
712            canister,
713            target_canister,
714            wasm_module_hash: wasm_module_hash.to_vec(),
715            store_canister: None,
716            chunk_hashes_list: vec![],
717            arg: Argument::new(),
718            mode: InstallMode::Install,
719        }
720    }
721
722    /// Set the chunks to install. These must previously have been set with [`ManagementCanister::upload_chunk`].
723    pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
724        self.chunk_hashes_list = chunk_hashes;
725        self
726    }
727
728    /// Set the canister to pull uploaded chunks from. By default this is the same as the target canister.
729    pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
730        self.store_canister = Some(store_canister);
731        self
732    }
733
734    /// Set the argument to the installation, which will be passed to the init
735    /// method of the canister. Can be called at most once.
736    pub fn with_arg(mut self, argument: impl CandidType) -> Self {
737        self.arg.set_idl_arg(argument);
738        self
739    }
740
741    /// Set the argument with multiple arguments as tuple to the installation,
742    /// which will be passed to the init method of the canister. Can be called at most once.
743    pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
744        assert!(self.arg.0.is_none(), "argument is being set more than once");
745        self.arg = Argument::from_candid(argument);
746        self
747    }
748
749    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
750    pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
751        self.arg.set_raw_arg(argument);
752        self
753    }
754
755    /// Set the [`InstallMode`].
756    pub fn with_install_mode(mut self, mode: InstallMode) -> Self {
757        self.mode = mode;
758        self
759    }
760
761    /// Create an [`AsyncCall`] implementation that, when called, will install the canister.
762    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
763        #[derive(CandidType)]
764        struct In {
765            mode: InstallMode,
766            target_canister: Principal,
767            store_canister: Option<Principal>,
768            chunk_hashes_list: Vec<ChunkHash>,
769            wasm_module_hash: Vec<u8>,
770            arg: Vec<u8>,
771            sender_canister_version: Option<u64>,
772        }
773        let Self {
774            mode,
775            target_canister,
776            store_canister,
777            chunk_hashes_list,
778            wasm_module_hash,
779            arg,
780            ..
781        } = self;
782        Ok(self
783            .canister
784            .update(MgmtMethod::InstallChunkedCode.as_ref())
785            .with_arg(In {
786                mode,
787                target_canister,
788                store_canister,
789                chunk_hashes_list,
790                wasm_module_hash,
791                arg: arg.serialize()?,
792                sender_canister_version: None,
793            })
794            .with_effective_canister_id(target_canister)
795            .build())
796    }
797
798    /// Make the call. This is equivalent to [`AsyncCall::call`].
799    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
800        self.build()?.call().await
801    }
802
803    /// Make the call. This is equivalent to [`AsyncCall::call_and_wait`].
804    pub async fn call_and_wait(self) -> Result<(), AgentError> {
805        self.build()?.call_and_wait().await
806    }
807}
808
809#[cfg_attr(target_family = "wasm", async_trait(?Send))]
810#[cfg_attr(not(target_family = "wasm"), async_trait)]
811impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
812    type Value = ();
813
814    async fn call(self) -> Result<CallResponse<()>, AgentError> {
815        self.call().await
816    }
817
818    async fn call_and_wait(self) -> Result<(), AgentError> {
819        self.call_and_wait().await
820    }
821}
822
823impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
824    type IntoFuture = CallFuture<'agent, ()>;
825    type Output = Result<(), AgentError>;
826
827    fn into_future(self) -> Self::IntoFuture {
828        AsyncCall::call_and_wait(self)
829    }
830}
831
832/// A builder for a [`ManagementCanister::install`] call. This automatically selects one-shot installation or chunked installation depending on module size.
833///
834/// # Warnings
835///
836/// This will clear chunked code storage if chunked installation is used. Do not use with canisters that you are manually uploading chunked code to.
837#[derive(Debug)]
838pub struct InstallBuilder<'agent, 'canister, 'builder> {
839    canister: &'canister ManagementCanister<'agent>,
840    canister_id: Principal,
841    // more precise lifetimes are used here at risk of annoying the user
842    // because `wasm` may be memory-mapped which is tricky to lifetime
843    wasm: &'builder [u8],
844    arg: Argument,
845    mode: InstallMode,
846}
847
848impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
849    // Messages are a maximum of 2MiB. Thus basic installation should cap the wasm and arg size at 1.85MiB, since
850    // the current API is definitely not going to produce 150KiB of framing data for it.
851    const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
852
853    /// Create a canister installation builder.
854    pub fn builder(
855        canister: &'canister ManagementCanister<'agent>,
856        canister_id: &Principal,
857        wasm: &'builder [u8],
858    ) -> Self {
859        Self {
860            canister,
861            canister_id: *canister_id,
862            wasm,
863            arg: Default::default(),
864            mode: InstallMode::Install,
865        }
866    }
867
868    /// Set the argument to the installation, which will be passed to the init
869    /// method of the canister. Can be called at most once.
870    pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
871        self.arg.set_idl_arg(arg);
872        self
873    }
874    /// Set the argument with multiple arguments as tuple to the installation,
875    /// which will be passed to the init method of the canister. Can be called at most once.
876    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
877        assert!(self.arg.0.is_none(), "argument is being set more than once");
878        self.arg = Argument::from_candid(tuple);
879        self
880    }
881    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
882    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
883        self.arg.set_raw_arg(arg);
884        self
885    }
886
887    /// Pass in the [`InstallMode`].
888    pub fn with_mode(self, mode: InstallMode) -> Self {
889        Self { mode, ..self }
890    }
891
892    /// Invoke the installation process. This may result in many calls which may take several seconds;
893    /// use [`call_and_wait_with_progress`](Self::call_and_wait_with_progress) if you want progress reporting.
894    pub async fn call_and_wait(self) -> Result<(), AgentError> {
895        self.call_and_wait_with_progress()
896            .await
897            .try_for_each(|_| ready(Ok(())))
898            .await
899    }
900
901    /// Invoke the installation process. The returned stream must be iterated to completion; it is used to track progress,
902    /// as installation may take arbitrarily long, and is intended to be passed to functions like `indicatif::ProgressBar::wrap_stream`.
903    /// There are exactly [`size_hint().0`](Stream::size_hint) steps.
904    pub async fn call_and_wait_with_progress(
905        self,
906    ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
907        let stream_res = /* try { */ async move {
908            let arg = self.arg.serialize()?;
909            let stream: BoxStream<'_, _> =
910                if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
911                    Box::pin(
912                        async move {
913                            self.canister
914                                .install_code(&self.canister_id, self.wasm)
915                                .with_raw_arg(arg)
916                                .with_mode(self.mode)
917                                .call_and_wait()
918                                .await
919                        }
920                        .into_stream(),
921                    )
922                } else {
923                    let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
924                    let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
925                    let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
926                    let mut to_upload_chunks = vec![];
927                    for (hash, chunk) in &all_chunks {
928                        if !existing_chunks.contains(hash) {
929                            to_upload_chunks.push(*chunk);
930                        }
931                    }
932
933                    let upload_chunks_stream = FuturesUnordered::new();
934                    for chunk in to_upload_chunks {
935                        upload_chunks_stream.push(async move {
936                            let (_res,) = self
937                                .canister
938                                .upload_chunk(&self.canister_id, chunk)
939                                .call_and_wait()
940                                .await?;
941                            Ok(())
942                        });
943                    }
944                    let install_chunked_code_stream = async move {
945                        let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
946                        self.canister
947                            .install_chunked_code(
948                                &self.canister_id,
949                                &Sha256::digest(self.wasm),
950                            )
951                            .with_chunk_hashes(results)
952                            .with_raw_arg(arg)
953                            .with_install_mode(self.mode)
954                            .call_and_wait()
955                            .await
956                    }
957                    .into_stream();
958                    let clear_store_stream = async move {
959                        self.canister
960                            .clear_chunk_store(&self.canister_id)
961                            .call_and_wait()
962                            .await
963                    }
964                    .into_stream();
965
966                    Box::pin(
967                        upload_chunks_stream
968                            .chain(install_chunked_code_stream)
969                            .chain(clear_store_stream                        ),
970                    )
971                };
972            Ok(stream)
973        }.await;
974        match stream_res {
975            Ok(stream) => stream,
976            Err(err) => Box::pin(stream::once(async { Err(err) })),
977        }
978    }
979}
980
981impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
982    for InstallBuilder<'agent, 'canister, 'builder>
983{
984    type IntoFuture = CallFuture<'builder, ()>;
985    type Output = Result<(), AgentError>;
986
987    fn into_future(self) -> Self::IntoFuture {
988        Box::pin(self.call_and_wait())
989    }
990}
991
992/// A builder for an `update_settings` call.
993#[derive(Debug)]
994pub struct UpdateCanisterBuilder<'agent, 'canister: 'agent> {
995    canister: &'canister Canister<'agent>,
996    canister_id: Principal,
997    controllers: Option<Result<Vec<Principal>, AgentError>>,
998    compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
999    memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1000    freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1001    reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1002    wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1003    wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1004    log_visibility: Option<Result<LogVisibility, AgentError>>,
1005}
1006
1007impl<'agent, 'canister: 'agent> UpdateCanisterBuilder<'agent, 'canister> {
1008    /// Create an `UpdateCanister` builder, which is also an `AsyncCall` implementation.
1009    pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1010        Self {
1011            canister,
1012            canister_id: *canister_id,
1013            controllers: None,
1014            compute_allocation: None,
1015            memory_allocation: None,
1016            freezing_threshold: None,
1017            reserved_cycles_limit: None,
1018            wasm_memory_limit: None,
1019            wasm_memory_threshold: None,
1020            log_visibility: None,
1021        }
1022    }
1023
1024    /// Pass in an optional controller for the canister. If this is [`None`],
1025    /// it will revert the controller to default.
1026    pub fn with_optional_controller<C, E>(self, controller: Option<C>) -> Self
1027    where
1028        E: std::fmt::Display,
1029        C: TryInto<Principal, Error = E>,
1030    {
1031        let controller_to_add: Option<Result<Principal, _>> = controller.map(|ca| {
1032            ca.try_into()
1033                .map_err(|e| AgentError::MessageError(format!("{e}")))
1034        });
1035        let controllers: Option<Result<Vec<Principal>, _>> =
1036            match (controller_to_add, self.controllers) {
1037                (_, Some(Err(sticky))) => Some(Err(sticky)),
1038                (Some(Err(e)), _) => Some(Err(e)),
1039                (None, _) => None,
1040                (Some(Ok(controller)), Some(Ok(controllers))) => {
1041                    let mut controllers = controllers;
1042                    controllers.push(controller);
1043                    Some(Ok(controllers))
1044                }
1045                (Some(Ok(controller)), None) => Some(Ok(vec![controller])),
1046            };
1047
1048        Self {
1049            controllers,
1050            ..self
1051        }
1052    }
1053
1054    /// Pass in a designated controller for the canister.
1055    pub fn with_controller<C, E>(self, controller: C) -> Self
1056    where
1057        E: std::fmt::Display,
1058        C: TryInto<Principal, Error = E>,
1059    {
1060        self.with_optional_controller(Some(controller))
1061    }
1062
1063    /// Pass in a compute allocation optional value for the canister. If this is [`None`],
1064    /// it will revert the compute allocation to default.
1065    pub fn with_optional_compute_allocation<C, E>(self, compute_allocation: Option<C>) -> Self
1066    where
1067        E: std::fmt::Display,
1068        C: TryInto<ComputeAllocation, Error = E>,
1069    {
1070        Self {
1071            compute_allocation: compute_allocation.map(|ca| {
1072                ca.try_into()
1073                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1074            }),
1075            ..self
1076        }
1077    }
1078
1079    /// Pass in a compute allocation value for the canister.
1080    pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1081    where
1082        E: std::fmt::Display,
1083        C: TryInto<ComputeAllocation, Error = E>,
1084    {
1085        self.with_optional_compute_allocation(Some(compute_allocation))
1086    }
1087
1088    /// Pass in a memory allocation optional value for the canister. If this is [`None`],
1089    /// it will revert the memory allocation to default.
1090    pub fn with_optional_memory_allocation<E, C>(self, memory_allocation: Option<C>) -> Self
1091    where
1092        E: std::fmt::Display,
1093        C: TryInto<MemoryAllocation, Error = E>,
1094    {
1095        Self {
1096            memory_allocation: memory_allocation.map(|ma| {
1097                ma.try_into()
1098                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1099            }),
1100            ..self
1101        }
1102    }
1103
1104    /// Pass in a memory allocation value for the canister.
1105    pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1106    where
1107        E: std::fmt::Display,
1108        C: TryInto<MemoryAllocation, Error = E>,
1109    {
1110        self.with_optional_memory_allocation(Some(memory_allocation))
1111    }
1112
1113    /// Pass in a freezing threshold optional value for the canister. If this is [`None`],
1114    /// it will revert the freezing threshold to default.
1115    pub fn with_optional_freezing_threshold<E, C>(self, freezing_threshold: Option<C>) -> Self
1116    where
1117        E: std::fmt::Display,
1118        C: TryInto<FreezingThreshold, Error = E>,
1119    {
1120        Self {
1121            freezing_threshold: freezing_threshold.map(|ma| {
1122                ma.try_into()
1123                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1124            }),
1125            ..self
1126        }
1127    }
1128
1129    /// Pass in a freezing threshold value for the canister.
1130    pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1131    where
1132        E: std::fmt::Display,
1133        C: TryInto<FreezingThreshold, Error = E>,
1134    {
1135        self.with_optional_freezing_threshold(Some(freezing_threshold))
1136    }
1137
1138    /// Pass in a reserved cycles limit value for the canister.
1139    pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1140    where
1141        E: std::fmt::Display,
1142        C: TryInto<ReservedCyclesLimit, Error = E>,
1143    {
1144        self.with_optional_reserved_cycles_limit(Some(limit))
1145    }
1146
1147    /// Pass in a reserved cycles limit optional value for the canister.
1148    /// If this is [`None`], leaves the reserved cycles limit unchanged.
1149    pub fn with_optional_reserved_cycles_limit<E, C>(self, limit: Option<C>) -> Self
1150    where
1151        E: std::fmt::Display,
1152        C: TryInto<ReservedCyclesLimit, Error = E>,
1153    {
1154        Self {
1155            reserved_cycles_limit: limit.map(|ma| {
1156                ma.try_into()
1157                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1158            }),
1159            ..self
1160        }
1161    }
1162
1163    /// Pass in a Wasm memory limit value for the canister.
1164    pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1165    where
1166        E: std::fmt::Display,
1167        C: TryInto<WasmMemoryLimit, Error = E>,
1168    {
1169        self.with_optional_wasm_memory_limit(Some(wasm_memory_limit))
1170    }
1171
1172    /// Pass in a Wasm memory limit optional value for the canister. If this is [`None`],
1173    /// leaves the Wasm memory limit unchanged.
1174    pub fn with_optional_wasm_memory_limit<E, C>(self, wasm_memory_limit: Option<C>) -> Self
1175    where
1176        E: std::fmt::Display,
1177        C: TryInto<WasmMemoryLimit, Error = E>,
1178    {
1179        Self {
1180            wasm_memory_limit: wasm_memory_limit.map(|limit| {
1181                limit
1182                    .try_into()
1183                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1184            }),
1185            ..self
1186        }
1187    }
1188
1189    /// Pass in a Wasm memory threshold value for the canister.
1190    pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1191    where
1192        E: std::fmt::Display,
1193        C: TryInto<WasmMemoryLimit, Error = E>,
1194    {
1195        self.with_optional_wasm_memory_threshold(Some(wasm_memory_threshold))
1196    }
1197
1198    /// Pass in a Wasm memory threshold value for the canister. If this is [`None`],
1199    /// leaves the memory threshold unchanged.
1200    pub fn with_optional_wasm_memory_threshold<E, C>(self, wasm_memory_threshold: Option<C>) -> Self
1201    where
1202        E: std::fmt::Display,
1203        C: TryInto<WasmMemoryLimit, Error = E>,
1204    {
1205        Self {
1206            wasm_memory_threshold: wasm_memory_threshold.map(|limit| {
1207                limit
1208                    .try_into()
1209                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1210            }),
1211            ..self
1212        }
1213    }
1214
1215    /// Pass in a log visibility setting for the canister.
1216    pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1217    where
1218        E: std::fmt::Display,
1219        C: TryInto<LogVisibility, Error = E>,
1220    {
1221        self.with_optional_log_visibility(Some(log_visibility))
1222    }
1223
1224    /// Pass in a log visibility optional setting for the canister. If this is [`None`],
1225    /// leaves the log visibility unchanged.
1226    pub fn with_optional_log_visibility<E, C>(self, log_visibility: Option<C>) -> Self
1227    where
1228        E: std::fmt::Display,
1229        C: TryInto<LogVisibility, Error = E>,
1230    {
1231        Self {
1232            log_visibility: log_visibility.map(|limit| {
1233                limit
1234                    .try_into()
1235                    .map_err(|e| AgentError::MessageError(format!("{e}")))
1236            }),
1237            ..self
1238        }
1239    }
1240
1241    /// Create an [`AsyncCall`] implementation that, when called, will update a
1242    /// canisters settings.
1243    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1244        #[derive(CandidType)]
1245        struct In {
1246            canister_id: Principal,
1247            settings: CanisterSettings,
1248        }
1249
1250        let controllers = match self.controllers {
1251            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1252            Some(Ok(x)) => Some(x),
1253            None => None,
1254        };
1255        let compute_allocation = match self.compute_allocation {
1256            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1257            Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1258            None => None,
1259        };
1260        let memory_allocation = match self.memory_allocation {
1261            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1262            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1263            None => None,
1264        };
1265        let freezing_threshold = match self.freezing_threshold {
1266            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1267            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1268            None => None,
1269        };
1270        let reserved_cycles_limit = match self.reserved_cycles_limit {
1271            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1272            Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1273            None => None,
1274        };
1275        let wasm_memory_limit = match self.wasm_memory_limit {
1276            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1277            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1278            None => None,
1279        };
1280        let wasm_memory_threshold = match self.wasm_memory_threshold {
1281            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1282            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1283            None => None,
1284        };
1285        let log_visibility = match self.log_visibility {
1286            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1287            Some(Ok(x)) => Some(x),
1288            None => None,
1289        };
1290
1291        Ok(self
1292            .canister
1293            .update(MgmtMethod::UpdateSettings.as_ref())
1294            .with_arg(In {
1295                canister_id: self.canister_id,
1296                settings: CanisterSettings {
1297                    controllers,
1298                    compute_allocation,
1299                    memory_allocation,
1300                    freezing_threshold,
1301                    reserved_cycles_limit,
1302                    wasm_memory_limit,
1303                    wasm_memory_threshold,
1304                    log_visibility,
1305                },
1306            })
1307            .with_effective_canister_id(self.canister_id)
1308            .build())
1309    }
1310
1311    /// Make a call. This is equivalent to the [`AsyncCall::call`].
1312    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1313        self.build()?.call().await
1314    }
1315
1316    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
1317    pub async fn call_and_wait(self) -> Result<(), AgentError> {
1318        self.build()?.call_and_wait().await
1319    }
1320}
1321
1322#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1323#[cfg_attr(not(target_family = "wasm"), async_trait)]
1324impl<'agent, 'canister: 'agent> AsyncCall for UpdateCanisterBuilder<'agent, 'canister> {
1325    type Value = ();
1326    async fn call(self) -> Result<CallResponse<()>, AgentError> {
1327        self.build()?.call().await
1328    }
1329
1330    async fn call_and_wait(self) -> Result<(), AgentError> {
1331        self.build()?.call_and_wait().await
1332    }
1333}
1334
1335impl<'agent, 'canister: 'agent> IntoFuture for UpdateCanisterBuilder<'agent, 'canister> {
1336    type IntoFuture = CallFuture<'agent, ()>;
1337    type Output = Result<(), AgentError>;
1338    fn into_future(self) -> Self::IntoFuture {
1339        AsyncCall::call_and_wait(self)
1340    }
1341}
1342
1343#[cfg(not(target_family = "wasm"))]
1344type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1345#[cfg(target_family = "wasm")]
1346type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;