Skip to main content

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