ic_cdk/
call.rs

1//! Inter-canister Call API
2//!
3//! This module provides the necessary APIs to make and manage inter-canister calls within a canister.
4//! It offers a builder pattern to configure and execute calls, allowing for flexible and customizable interactions
5//! between canisters.
6//!
7//! # Overview
8//!
9//! The primary type in this module is [`Call`], which represents an inter-canister call. For detailed usage and examples,
10//! refer to the [`Call`] type documentation.
11//!
12//! ```rust, no_run
13//! # use ic_cdk::call::Call;
14//! # async fn bar() {
15//! # let canister_id = ic_cdk::api::canister_self();
16//! # let method = "foo";
17//! let result: u32 = Call::bounded_wait(canister_id, method).await.unwrap().candid().unwrap();
18//! # }
19//! ```
20//!
21//! # Error Handling
22//!
23//! The module defines various error types to handle different failure scenarios during inter-canister calls:
24//!
25//! - The base error cases:
26//!   - [`InsufficientLiquidCycleBalance`]: Errors when the liquid cycle balance is insufficient to perform the call.
27//!   - [`CallPerformFailed`]: Errors when the `ic0.call_perform` operation fails.
28//!   - [`CallRejected`]: Errors when an inter-canister call is rejected.
29//!   - [`CandidDecodeFailed`]: Errors when the response cannot be decoded as Candid.
30//! - The composite error types:
31//!   - [`enum@Error`]: The top-level error type encapsulating all possible errors.
32//!   - [`CallFailed`]: Errors related to the execution of the call itself, i.e. all the errors except for the Candid decoding failure.
33//!   - [`OnewayError`]: The error type for when sending a [`oneway`](Call::oneway) call.
34//!
35//! # Internal Details
36//!
37//! The module also includes internal types and functions to manage the state and execution of inter-canister calls,
38//! such as [`CallFuture`] and its associated state management.
39
40use crate::api::{cost_call, msg_arg_data, msg_reject_code, msg_reject_msg};
41use crate::{futures::is_recovering_from_trap, trap};
42use candid::utils::{ArgumentDecoder, ArgumentEncoder, encode_args_ref};
43use candid::{CandidType, Deserialize, Principal, decode_args, decode_one, encode_one};
44use ic_cdk_executor::{MethodHandle, TaskHandle};
45use std::borrow::Cow;
46use std::future::IntoFuture;
47use std::mem;
48use std::pin::Pin;
49use std::sync::{Arc, RwLock};
50use std::task::{Context, Poll, Waker};
51use thiserror::Error;
52
53pub use ic_error_types::RejectCode;
54
55/// Inter-canister Call.
56///
57/// This type enables the configuration and execution of inter-canister calls using a builder pattern.
58///
59/// # Constructors
60///
61/// [`Call`] has two constructors that differentiate whether the call's response is waited for an unbounded amount of time or not.
62/// - [`bounded_wait`][Self::bounded_wait]: wait boundedly (defaults with 300-second timeout).
63/// - [`unbounded_wait`][Self::unbounded_wait]: wait unboundedly.
64///
65/// # Configuration
66///
67/// Before execution, a [`Call`] can be configured in following aspects:
68///
69/// - Arguments:
70///   - [`with_arg`][Self::with_arg]: single `CandidType` value that will be encoded.
71///   - [`with_args`][Self::with_args]: a tuple of multiple `CandidType` values that will be encoded.
72///   - [`with_raw_args`][Self::with_raw_args]: raw bytes that won't be encoded.
73///   - *Note*: If no methods in this category are invoked, the [`Call`] defaults to sending a **Candid empty tuple `()`**.
74/// - Cycles:
75///   - [`with_cycles`][Self::with_cycles]: set the cycles attached in this call.
76/// - Response waiting timeout:
77///   - [`change_timeout`][Self::change_timeout]: change the timeout for **`bounded_wait`** call.
78///
79/// Please note that all the configuration methods are chainable and can be called multiple times.
80/// For each **aspect** of the call, the **last** configuration takes effect.
81///
82/// ## Example
83///
84/// ```rust, no_run
85/// # use ic_cdk::call::Call;
86/// # async fn bar() {
87/// # let canister_id = ic_cdk::api::canister_self();
88/// # let method = "foo";
89/// let call = Call::bounded_wait(canister_id, method)
90///     .with_raw_args(&[1,0])
91///     .with_cycles(1000)
92///     .change_timeout(5)
93///     .with_arg(42)
94///     .with_cycles(2000);
95/// # }
96/// ```
97///
98/// The `call` above will have the following configuration in effect:
99/// - Arguments: `42` encoded as Candid bytes.
100/// - Attach 2000 cycles.
101/// - Boundedly waiting for response with a 5-second timeout.
102///
103/// # Execution
104///
105/// A [`Call`] can be executed in two ways:
106/// - **Asynchronously**: Convert to a [`CallFuture`] and await the response.
107///   - Direct approach: Use `.await` on the call (e.g., `call.await`).
108///   - Collective approach: Use [`IntoFuture::into_future`] to obtain futures explicitly,
109///     then combine them with `join!`, `select!`, or other combinators.
110/// - **One-way**: Send a call with [`oneway`][Self::oneway] when you don't need a response.
111///
112/// ## Example
113///
114/// ```rust, no_run
115/// # use ic_cdk::call::Call;
116/// # use candid::Principal;
117/// # async fn bar() {
118/// # let canister_id : Principal = todo!();
119/// # let method: &str = todo!();
120/// # let canister_id1 : Principal = todo!();
121/// # let method1: &str = todo!();
122/// # let canister_id2 : Principal = todo!();
123/// # let method2: &str = todo!();
124/// let response = Call::bounded_wait(canister_id, method).await;
125/// let calls = vec![
126///   Call::bounded_wait(canister_id1, method1).into_future(),
127///   Call::bounded_wait(canister_id2, method2).into_future(),
128/// ];
129/// let responses = futures::future::join_all(calls).await;
130/// Call::bounded_wait(canister_id, method).oneway().unwrap();
131/// # }
132/// ```
133///
134/// # Decoding the response
135///
136/// If an asynchronous [`Call`] succeeds, the response can be decoded in two ways:
137/// - [`candid`][Response::candid]: decode the response as a single Candid type.
138/// - [`candid_tuple`][Response::candid_tuple]: decode the response as a tuple of Candid types.
139///
140/// ## Example
141///
142/// ```rust, no_run
143/// # use ic_cdk::call::{Call, Response};
144/// # async fn bar() {
145/// # let canister_id = ic_cdk::api::canister_self();
146/// # let method = "foo";
147/// let res: Response = Call::bounded_wait(canister_id, method).await.unwrap();
148/// let result: u32 = res.candid().unwrap();
149/// let result_tuple: (u32,) = res.candid_tuple().unwrap();
150/// # }
151/// ```
152///
153/// <div class="warning">
154///
155/// Using an inter-canister call creates the possibility that your async function will be canceled partway through.
156/// Read the [`futures`](crate::futures) module docs for why and how this happens.
157///
158/// </div>
159#[derive(Debug, Clone)]
160pub struct Call<'m, 'a> {
161    canister_id: Principal,
162    method: &'m str,
163    cycles: u128,
164    timeout_seconds: Option<u32>,
165    encoded_args: Cow<'a, [u8]>,
166}
167
168// Constructors
169impl<'m> Call<'m, '_> {
170    /// Constructs a [`Call`] which will **boundedly** wait for response.
171    ///
172    /// # Note
173    ///
174    /// The bounded waiting is set with a default 300-second timeout.
175    /// It aligns with the `MAX_CALL_TIMEOUT` constant in the current IC implementation.
176    /// The timeout can be changed using the [`change_timeout`][Self::change_timeout] method.
177    ///
178    /// To unboundedly wait for response, use the [`Call::unbounded_wait`] constructor instead.
179    #[must_use]
180    pub fn bounded_wait(canister_id: Principal, method: &'m str) -> Self {
181        Self {
182            canister_id,
183            method,
184            cycles: 0,
185            // Default to 300-second timeout.
186            timeout_seconds: Some(300),
187            // Bytes for empty arguments.
188            // `candid::Encode!(&()).unwrap()`
189            encoded_args: Cow::Owned(vec![0x44, 0x49, 0x44, 0x4c, 0x00, 0x00]),
190        }
191    }
192
193    /// Constructs a [`Call`] which will **unboundedly** wait for response.
194    ///
195    /// To boundedly wait for response, use the  [`Call::bounded_wait`] constructor instead.
196    #[must_use]
197    pub fn unbounded_wait(canister_id: Principal, method: &'m str) -> Self {
198        Self {
199            canister_id,
200            method,
201            cycles: 0,
202            timeout_seconds: None,
203            // Bytes for empty arguments.
204            // `candid::Encode!(&()).unwrap()`
205            encoded_args: Cow::Owned(vec![0x44, 0x49, 0x44, 0x4c, 0x00, 0x00]),
206        }
207    }
208}
209
210// Configuration
211impl<'a> Call<'_, 'a> {
212    /// Sets the argument for the call.
213    ///
214    /// The argument must implement [`CandidType`].
215    #[must_use]
216    pub fn with_arg<A: CandidType>(self, arg: A) -> Self {
217        Self {
218            encoded_args: Cow::Owned(encode_one(&arg).unwrap_or_else(panic_when_encode_fails)),
219            ..self
220        }
221    }
222
223    /// Sets the arguments for the call.
224    ///
225    /// The arguments are a tuple of types, each implementing [`CandidType`].
226    #[must_use]
227    pub fn with_args<A: ArgumentEncoder>(self, args: &A) -> Self {
228        Self {
229            encoded_args: Cow::Owned(encode_args_ref(args).unwrap_or_else(panic_when_encode_fails)),
230            ..self
231        }
232    }
233
234    /// Sets the arguments for the call as raw bytes.
235    ///
236    /// # Note
237    ///
238    /// This method just borrows the bytes, so it is useful when making multiple calls with the same argument data.
239    ///
240    /// The `Call` object will be tied to the lifetime of the argument bytes,
241    /// which may prevent storing the call in collections or returning it from functions
242    /// if the arguments don't live long enough.
243    ///
244    /// For cases where you need to transfer ownership of the arguments bytes consider using [`Self::take_raw_args`] instead.
245    #[must_use]
246    pub fn with_raw_args(self, raw_args: &'a [u8]) -> Self {
247        Self {
248            encoded_args: Cow::Borrowed(raw_args),
249            ..self
250        }
251    }
252
253    /// Sets the arguments for the call as raw bytes and consumes the bytes.
254    ///
255    /// # Note
256    ///
257    /// This method takes ownership of the arguments bytes, so it is useful
258    /// when you want to store the call in collections or return a `Call` from functions.
259    ///
260    /// For cases where you want to make multiple calls with the same argument data,
261    /// consider using [`Self::with_raw_args`] instead to avoid unnecessary cloning.
262    #[must_use]
263    pub fn take_raw_args(self, raw_args: Vec<u8>) -> Self {
264        Self {
265            encoded_args: Cow::Owned(raw_args),
266            ..self
267        }
268    }
269
270    /// Sets the cycles payment for the call.
271    ///
272    /// # Note
273    ///
274    /// The behavior of this method when invoked multiple times is as follows:
275    /// - Overrides any previously set cycle value
276    /// - Last invocation determines the final cycles amount
277    /// - Does not accumulate cycles across multiple invocations
278    #[must_use]
279    pub fn with_cycles(mut self, cycles: u128) -> Self {
280        self.cycles = cycles;
281        self
282    }
283
284    /// Changes the timeout for bounded response waiting.
285    ///
286    /// If invoked multiple times, the last value takes effect.
287    ///
288    /// The timeout value is silently capped by the `MAX_CALL_TIMEOUT` constant which is currently set to 300 seconds.
289    /// Therefore, setting a timeout greater than 300 seconds will actually result in a 300-second timeout.
290    ///
291    /// # Panics
292    ///
293    /// This method will panic if invoked on an unbounded response waiting call constructed by [`Call::unbounded_wait`] .
294    ///
295    /// # Note
296    ///
297    /// A timeout of 0 second **DOES NOT** mean unbounded response waiting.
298    /// The call would most likely time out (result in a [`SysUnknown`](RejectCode::SysUnknown) reject).
299    /// Unless it's a call to the canister on the same subnet,
300    /// and the execution manages to schedule both the request and the response in the same round.
301    ///
302    /// To unboundedly wait for response, use the [`Call::unbounded_wait`] constructor instead.
303    #[must_use]
304    pub fn change_timeout(mut self, timeout_seconds: u32) -> Self {
305        match self.timeout_seconds {
306            Some(_) => self.timeout_seconds = Some(timeout_seconds),
307            None => {
308                panic!("Cannot set a timeout for an instance created with Call::unbounded_wait")
309            }
310        }
311        self
312    }
313
314    /// Returns the amount of cycles a canister needs to be above the freezing threshold in order to
315    /// successfully perform this call. Takes into account the attached cycles ([`with_cycles`](Self::with_cycles))
316    /// as well as
317    /// - the method name byte length
318    /// - the payload length
319    /// - the cost of transmitting the request
320    /// - the cost for the reservation of response transmission (may be partially refunded)
321    /// - the cost for the reservation of callback execution (may be partially refunded).
322    #[must_use]
323    pub fn get_cost(&self) -> u128 {
324        self.cycles.saturating_add(cost_call(
325            self.method.len() as u64,
326            self.encoded_args.len() as u64,
327        ))
328    }
329}
330
331/// Response of a successful call.
332#[derive(Debug)]
333pub struct Response(Vec<u8>);
334
335impl Response {
336    /// Gets the raw bytes of the response.
337    pub fn into_bytes(self) -> Vec<u8> {
338        self.0
339    }
340
341    /// Decodes the response as a single Candid type.
342    pub fn candid<R>(&self) -> Result<R, CandidDecodeFailed>
343    where
344        R: CandidType + for<'de> Deserialize<'de>,
345    {
346        decode_one(&self.0).map_err(|e| CandidDecodeFailed {
347            type_name: std::any::type_name::<R>().to_string(),
348            candid_error: e.to_string(),
349        })
350    }
351
352    /// Decodes the response as a tuple of Candid types.
353    pub fn candid_tuple<R>(&self) -> Result<R, CandidDecodeFailed>
354    where
355        R: for<'de> ArgumentDecoder<'de>,
356    {
357        decode_args(&self.0).map_err(|e| CandidDecodeFailed {
358            type_name: std::any::type_name::<R>().to_string(),
359            candid_error: e.to_string(),
360        })
361    }
362}
363
364impl PartialEq<&[u8]> for Response {
365    fn eq(&self, other: &&[u8]) -> bool {
366        self.0 == *other
367    }
368}
369
370impl PartialEq<Vec<u8>> for Response {
371    fn eq(&self, other: &Vec<u8>) -> bool {
372        self.0 == *other
373    }
374}
375
376impl PartialEq for Response {
377    fn eq(&self, other: &Self) -> bool {
378        self.0 == other.0
379    }
380}
381
382impl std::ops::Deref for Response {
383    type Target = [u8];
384
385    fn deref(&self) -> &Self::Target {
386        &self.0
387    }
388}
389
390impl AsRef<[u8]> for Response {
391    fn as_ref(&self) -> &[u8] {
392        &self.0
393    }
394}
395
396impl std::borrow::Borrow<[u8]> for Response {
397    fn borrow(&self) -> &[u8] {
398        &self.0
399    }
400}
401
402// Errors ---------------------------------------------------------------------
403
404/// Represents errors that can occur during inter-canister calls.
405///
406/// This is the top-level error type for the inter-canister call API.
407///
408/// This encapsulates all possible errors that can arise, including:
409/// - Insufficient liquid cycle balance.
410/// - `ic0.call_perform` failed.
411/// - Asynchronously rejected.
412/// - Candid decoding of the response failed.
413#[derive(Error, Debug, Clone)]
414pub enum Error {
415    /// The liquid cycle balance is insufficient to perform the call.
416    #[error(transparent)]
417    InsufficientLiquidCycleBalance(#[from] InsufficientLiquidCycleBalance),
418
419    /// The `ic0.call_perform` operation failed.
420    #[error(transparent)]
421    CallPerformFailed(#[from] CallPerformFailed),
422
423    /// The inter-canister call is rejected.
424    #[error(transparent)]
425    CallRejected(#[from] CallRejected),
426
427    /// The response from the inter-canister call could not be decoded as Candid.
428    ///
429    /// This variant wraps errors that occur when attempting to decode the response
430    /// into the expected Candid type.
431    #[error(transparent)]
432    CandidDecodeFailed(#[from] CandidDecodeFailed),
433}
434
435/// The error type when awaiting a [`CallFuture`].
436///
437/// This encapsulates all possible [`enum@Error`] except for the [`CandidDecodeFailed`] variant.
438#[derive(Error, Debug, Clone)]
439pub enum CallFailed {
440    /// The liquid cycle balance is insufficient to perform the call.
441    #[error(transparent)]
442    InsufficientLiquidCycleBalance(#[from] InsufficientLiquidCycleBalance),
443
444    /// The `ic0.call_perform` operation failed.
445    #[error(transparent)]
446    CallPerformFailed(#[from] CallPerformFailed),
447
448    /// The inter-canister call is rejected.
449    #[error(transparent)]
450    CallRejected(#[from] CallRejected),
451}
452
453/// The error type of [`Call::oneway`].
454///
455/// This encapsulates all possible errors that can occur when sending a oneway call.
456/// Therefore, it includes the [`InsufficientLiquidCycleBalance`] and [`CallPerformFailed`] variants.
457#[derive(Error, Debug, Clone)]
458pub enum OnewayError {
459    /// The liquid cycle balance is insufficient to perform the call.
460    #[error(transparent)]
461    InsufficientLiquidCycleBalance(#[from] InsufficientLiquidCycleBalance),
462    /// The `ic0.call_perform` operation failed.
463    #[error(transparent)]
464    CallPerformFailed(#[from] CallPerformFailed),
465}
466
467impl From<OnewayError> for Error {
468    fn from(e: OnewayError) -> Self {
469        match e {
470            OnewayError::InsufficientLiquidCycleBalance(e) => {
471                Error::InsufficientLiquidCycleBalance(e)
472            }
473            OnewayError::CallPerformFailed(e) => Error::CallPerformFailed(e),
474        }
475    }
476}
477
478impl From<CallFailed> for Error {
479    fn from(e: CallFailed) -> Self {
480        match e {
481            CallFailed::InsufficientLiquidCycleBalance(e) => {
482                Error::InsufficientLiquidCycleBalance(e)
483            }
484            CallFailed::CallPerformFailed(e) => Error::CallPerformFailed(e),
485            CallFailed::CallRejected(e) => Error::CallRejected(e),
486        }
487    }
488}
489
490/// Represents an error that occurs when the liquid cycle balance is insufficient to perform the call.
491///
492/// The liquid cycle balance is determined by [`canister_liquid_cycle_balance`](crate::api::canister_liquid_cycle_balance).
493/// The cost of the call is determined by [`Call::get_cost`].
494///
495/// The call won't be performed if the former is less than the latter.
496#[derive(Error, Debug, Clone)]
497#[error("insufficient liquid cycles balance, available: {available}, required: {required}")]
498pub struct InsufficientLiquidCycleBalance {
499    /// The liquid cycle balance available in the canister.
500    pub available: u128,
501    /// The required cycles to perform the call.
502    pub required: u128,
503}
504
505/// Represents an error that occurs when the `ic0.call_perform` operation fails.
506///
507/// This error type indicates that the underlying `ic0.call_perform` operation
508/// returned a non-zero code, signaling a failure.
509#[derive(Error, Debug, Clone)]
510#[error("call perform failed")]
511pub struct CallPerformFailed;
512
513/// Represents an error that occurs when an inter-canister call is rejected.
514///
515/// The [`reject_code`][`Self::reject_code`] and [`reject_message`][`Self::reject_message`]
516/// are exposed to provide details of the rejection.
517///
518/// This is wrapped by the [`CallFailed::CallRejected`] variant.
519#[derive(Error, Debug, Clone)]
520#[error("call rejected: {raw_reject_code} - {reject_message}")]
521pub struct CallRejected {
522    /// All fields are private so we will be able to change the implementation without breaking the API.
523    /// Once we have `ic0.msg_error_code` system API, we will only store the `error_code` in this struct.
524    /// It will still be possible to get the [`RejectCode`] using the public getter,
525    /// because every `error_code` can map to a [`RejectCode`].
526    raw_reject_code: u32,
527    reject_message: String,
528}
529
530/// The error type for when an unrecognized reject code is encountered.
531#[derive(Error, Debug, Clone, PartialEq, Eq)]
532#[error("unrecognized reject code: {0}")]
533pub struct UnrecognizedRejectCode(u32);
534
535impl CallRejected {
536    /// Constructs a [`CallRejected`] instance with the reject code and message.
537    ///
538    /// # Note
539    ///
540    /// This constructor is primarily intended for testing scenarios where you need to simulate
541    /// rejected inter-canister calls. In production code, instances of this error are typically
542    /// created by the system when actual rejections occur during inter-canister communication.
543    /// Use this constructor with caution outside of test environments.
544    pub fn with_rejection(raw_reject_code: u32, reject_message: String) -> Self {
545        Self {
546            raw_reject_code,
547            reject_message,
548        }
549    }
550
551    /// Gets the [`RejectCode`].
552    ///
553    /// The value is converted from [`api::msg_reject_code`](`msg_reject_code`).
554    ///
555    /// # Errors
556    ///
557    /// If the raw reject code is not recognized, this method will return an [`UnrecognizedRejectCode`] error.
558    /// This can happen if the IC produces a new reject code that hasn't been included in [`ic_error_types::RejectCode`].
559    /// Please check if your `ic-error-types` dependency is up-to-date.
560    /// If the latest version of `ic-error-types` doesn't include the new reject code, please report it to the `ic-cdk` maintainers.
561    pub fn reject_code(&self) -> Result<RejectCode, UnrecognizedRejectCode> {
562        RejectCode::try_from(u64::from(self.raw_reject_code))
563            .map_err(|_| UnrecognizedRejectCode(self.raw_reject_code))
564    }
565
566    /// Gets the raw numeric [`RejectCode`] value.
567    ///
568    /// This is a "never-fail" version of [`reject_code`](Self::reject_code) that returns the raw numeric value.
569    pub fn raw_reject_code(&self) -> u32 {
570        self.raw_reject_code
571    }
572
573    /// Retrieves the reject message associated with the call.
574    ///
575    /// This message is obtained from [`api::msg_reject_msg`](`msg_reject_msg`).
576    pub fn reject_message(&self) -> &str {
577        &self.reject_message
578    }
579}
580
581/// Represents an error that occurs when the response from an inter-canister call
582/// cannot be decoded as Candid.
583///
584/// This error type provides details about the Candid decoding failure, including
585/// the type that was being decoded and the specific Candid error that occurred.
586///
587/// This is the only possible error that can occur in [`Response::candid`] and [`Response::candid_tuple`].
588///
589/// It is wrapped by the top-level [`Error::CandidDecodeFailed`] variant.
590#[derive(Error, Debug, Clone)]
591#[error("candid decode failed for type: {type_name}, candid error: {candid_error}")]
592pub struct CandidDecodeFailed {
593    type_name: String,
594    candid_error: String,
595}
596
597/// Extension trait for error types to provide additional methods.
598pub trait CallErrorExt {
599    /// Checks if the error is a clean reject.
600    /// A clean reject means that there must be no state changes on the callee side.
601    fn is_clean_reject(&self) -> bool;
602    /// Determines if the failed call can be retried immediately within the update method
603    /// that's handling the error, as opposed to relying on a background timer or heartbeat.
604    ///
605    /// A return value of `true` indicates that an immediate retry *might* succeed, i.e., not result in another error.
606    /// However, the caller is responsible for ensuring that retries are safe in their specific context.
607    /// For idempotent methods, immediate retries are generally safe. For non-idempotent ones,
608    /// checking [`is_clean_reject`](CallErrorExt::is_clean_reject) before retrying is recommended.
609    fn is_immediately_retryable(&self) -> bool;
610}
611
612impl CallErrorExt for InsufficientLiquidCycleBalance {
613    fn is_clean_reject(&self) -> bool {
614        // The call was not performed.
615        true
616    }
617
618    fn is_immediately_retryable(&self) -> bool {
619        // Caller should top up cycles before retrying.
620        false
621    }
622}
623
624impl CallErrorExt for CallPerformFailed {
625    fn is_clean_reject(&self) -> bool {
626        true
627    }
628
629    fn is_immediately_retryable(&self) -> bool {
630        false
631    }
632}
633
634impl CallErrorExt for CallRejected {
635    fn is_clean_reject(&self) -> bool {
636        // Here we apply a conservative whitelist of reject codes that are considered clean.
637        // Once finer `error_code` is available, we can allow more cases to be clean.
638        let clean_reject_codes: Vec<u32> = vec![
639            RejectCode::SysFatal as u32,
640            RejectCode::SysTransient as u32,
641            RejectCode::DestinationInvalid as u32,
642        ];
643        clean_reject_codes.contains(&self.raw_reject_code)
644    }
645
646    fn is_immediately_retryable(&self) -> bool {
647        // Here we apply a conservative whitelist of reject codes that are considered immediately retryable.
648        // Once finer `error_code` is available, we can allow more cases to be immediately retryable.
649        let immediately_retryable_codes: Vec<u32> = vec![
650            RejectCode::SysTransient as u32,
651            RejectCode::SysUnknown as u32,
652        ];
653        immediately_retryable_codes.contains(&self.raw_reject_code)
654    }
655}
656
657impl CallErrorExt for CandidDecodeFailed {
658    fn is_clean_reject(&self) -> bool {
659        // Decoding failure suggests that the inter-canister call was successfully processed by the callee.
660        // Therefore, the callee state is likely changed (unless the method doesn't change its own state).
661        false
662    }
663
664    fn is_immediately_retryable(&self) -> bool {
665        // Decoding failure suggests a mismatch between the expected and actual response types.
666        // Either the callee or the caller has a bug, and retrying the call immediately is unlikely to succeed.
667        false
668    }
669}
670
671impl CallErrorExt for Error {
672    fn is_clean_reject(&self) -> bool {
673        match self {
674            Error::InsufficientLiquidCycleBalance(e) => e.is_clean_reject(),
675            Error::CallPerformFailed(e) => e.is_clean_reject(),
676            Error::CallRejected(e) => e.is_clean_reject(),
677            Error::CandidDecodeFailed(e) => e.is_clean_reject(),
678        }
679    }
680
681    fn is_immediately_retryable(&self) -> bool {
682        match self {
683            Error::InsufficientLiquidCycleBalance(e) => e.is_immediately_retryable(),
684            Error::CallPerformFailed(e) => e.is_immediately_retryable(),
685            Error::CallRejected(e) => e.is_immediately_retryable(),
686            Error::CandidDecodeFailed(e) => e.is_immediately_retryable(),
687        }
688    }
689}
690
691impl CallErrorExt for CallFailed {
692    fn is_clean_reject(&self) -> bool {
693        match self {
694            CallFailed::InsufficientLiquidCycleBalance(e) => e.is_clean_reject(),
695            CallFailed::CallPerformFailed(e) => e.is_clean_reject(),
696            CallFailed::CallRejected(e) => e.is_clean_reject(),
697        }
698    }
699
700    fn is_immediately_retryable(&self) -> bool {
701        match self {
702            CallFailed::InsufficientLiquidCycleBalance(e) => e.is_immediately_retryable(),
703            CallFailed::CallPerformFailed(e) => e.is_immediately_retryable(),
704            CallFailed::CallRejected(e) => e.is_immediately_retryable(),
705        }
706    }
707}
708
709impl CallErrorExt for OnewayError {
710    fn is_clean_reject(&self) -> bool {
711        match self {
712            OnewayError::InsufficientLiquidCycleBalance(e) => e.is_clean_reject(),
713            OnewayError::CallPerformFailed(e) => e.is_clean_reject(),
714        }
715    }
716
717    fn is_immediately_retryable(&self) -> bool {
718        match self {
719            OnewayError::InsufficientLiquidCycleBalance(e) => e.is_immediately_retryable(),
720            OnewayError::CallPerformFailed(e) => e.is_immediately_retryable(),
721        }
722    }
723}
724
725// Errors END -----------------------------------------------------------------
726
727/// Result of a inter-canister call.
728pub type CallResult<R> = Result<R, Error>;
729
730impl<'m, 'a> IntoFuture for Call<'m, 'a> {
731    type Output = Result<Response, CallFailed>;
732    type IntoFuture = CallFuture<'m, 'a>;
733
734    fn into_future(self) -> Self::IntoFuture {
735        CallFuture {
736            state: Arc::new(RwLock::new(CallFutureState::Prepared { call: self })),
737        }
738    }
739}
740
741// Execution
742impl Call<'_, '_> {
743    /// Sends the call and ignores the reply.
744    pub fn oneway(&self) -> Result<(), OnewayError> {
745        self.check_liquid_cycle_balance_sufficient()?;
746        match self.perform(None) {
747            0 => Ok(()),
748            _ => Err(CallPerformFailed.into()),
749        }
750    }
751
752    /// Checks if the liquid cycle balance is sufficient to perform the call.
753    fn check_liquid_cycle_balance_sufficient(&self) -> Result<(), InsufficientLiquidCycleBalance> {
754        let required = self.get_cost();
755        let available = crate::api::canister_liquid_cycle_balance();
756        if available >= required {
757            Ok(())
758        } else {
759            Err(InsufficientLiquidCycleBalance {
760                available,
761                required,
762            })
763        }
764    }
765
766    /// Performs the call.
767    ///
768    /// This is an internal helper function only for [`Self::call_oneway`] and [`CallFuture::poll`].
769    ///
770    /// # Arguments
771    ///
772    /// - `state_ptr`: An optional pointer to the internal state of the [`CallFuture`].
773    ///   - If `Some`, the call will be prepared for asynchronous execution:
774    ///     - `ic0.call_new` will be invoked with [`callback`] and state pointer.
775    ///     - `ic0.call_on_cleanup` will be invoked with [`cleanup`].
776    ///   - If `None`, the call will be prepared for oneway execution:
777    ///     - `ic0.call_new` will be invoked with invalid callback functions.
778    ///     - `ic0.call_on_cleanup` won't be invoked.
779    ///
780    /// # Returns
781    ///
782    /// The return value of `ic0.call_perform`.
783    fn perform(&self, state_opt: Option<Arc<RwLock<CallFutureState<'_, '_>>>>) -> u32 {
784        let callee = self.canister_id.as_slice();
785        let method = self.method;
786        let arg = match &self.encoded_args {
787            Cow::Owned(vec) => vec,
788            Cow::Borrowed(r) => *r,
789        };
790        let state_ptr_opt = state_opt.map(Arc::<RwLock<CallFutureState<'_, '_>>>::into_raw);
791        match state_ptr_opt {
792            Some(state_ptr) => {
793                // asynchronous execution
794                //
795                // # SAFETY:
796                // - `callback` is intended as an entrypoint and therefore can be called as both reply and reject fn
797                //      for ic0.call_new.
798                // - `cleanup` is intended as an entrypoint and therefore can be called as cleanup fn for ic0.call_on_cleanup.
799                // - `state_ptr` is a pointer created via Arc::<RwLock<CallFutureState>>::into_raw, and can therefore be passed as the userdata for
800                //      `callback` and `cleanup`.
801                // - if-and-only-if ic0.call_perform returns 0, exactly one(‡) of `callback` or `cleanup`
802                //      receive ownership of `state_ptr`
803                // - both functions deallocate `state_ptr`, and this enclosing function deallocates `state_ptr` if ic0.call_perform
804                //      returns !=0, and therefore `state_ptr`'s ownership can be passed to FFI without leaking memory.
805                //
806                // ‡ The flow from outside the WASM runtime is that the callback runs, it traps, state is rolled back,
807                //   and the cleanup callback runs afterwards. Inside the runtime, there is no difference between
808                //   'state is rolled back to before the callback was called' and 'the callback was never called'.
809                //   So from the code's perspective, exactly one function is called.
810                unsafe {
811                    ic0::call_new(
812                        callee,
813                        method,
814                        callback,
815                        state_ptr as usize,
816                        callback,
817                        state_ptr as usize,
818                    );
819                    ic0::call_on_cleanup(cleanup, state_ptr as usize);
820                }
821            }
822
823            None => {
824                ic0::call_new_oneway(callee, method);
825                // There is no `call_on_cleanup` invocation because:
826                // - the callback does not exist, and so cannot trap to require cleanup
827                // - under the current behavior of the IC, this produces an error,
828                //   which would unconditionally call the cleanup callback
829            }
830        }
831        if !arg.is_empty() {
832            ic0::call_data_append(arg);
833        }
834        if self.cycles > 0 {
835            ic0::call_cycles_add128(self.cycles);
836        }
837        if let Some(timeout_seconds) = self.timeout_seconds {
838            ic0::call_with_best_effort_response(timeout_seconds);
839        }
840        let res = ic0::call_perform();
841        if res != 0 {
842            if let Some(state_ptr) = state_ptr_opt {
843                // SAFETY:
844                // - `state_ptr_opt` is `Some` if-and-only-if ic0.call_new was called with ownership of `state`
845                // - by returning !=0, ic0.call_new relinquishes ownership of `state_ptr`; it will never be passed
846                //      to any functions
847                // therefore, there is an outstanding handle to `state`, which it is safe to deallocate
848                unsafe {
849                    Arc::from_raw(state_ptr);
850                }
851            }
852        }
853        res
854    }
855}
856
857// # Internal =================================================================
858
859/// Internal state for the Future when sending a call.
860#[derive(Debug, Default)]
861enum CallFutureState<'m, 'a> {
862    /// The future has been constructed, and the call has not yet been performed.
863    /// Needed because futures are supposed to do nothing unless polled.
864    /// Polling will attempt to fire off the request. Success returns `Pending` and transitions to `Executing`,
865    /// failure returns `Ready` and transitions to `PostComplete.`
866    Prepared { call: Call<'m, 'a> },
867    /// The call has been performed and the message is in flight. Neither callback has been called. Polling will return `Pending`.
868    /// This state will transition to `Trapped` if the future is canceled because of a trap in another future.
869    Executing {
870        waker: Waker,
871        method: MethodHandle,
872        task: Option<TaskHandle>,
873    },
874    /// `callback` has been called, so the call has been completed. This completion state has not yet been read by the user.
875    /// Polling will return `Ready` and transition to `PostComplete`.
876    Complete {
877        result: Result<Response, CallFailed>,
878    },
879    /// The completion state of `Complete` has been returned from `poll` as `Poll::Ready`. Polling again will trap.
880    #[default]
881    PostComplete,
882    /// The future (*not* the state) was canceled because of a trap in another future during `Executing`. Polling will trap.
883    Trapped,
884}
885
886/// Represents a future that resolves to the result of an inter-canister call.
887///
888/// This type is returned by [`IntoFuture::into_future`] when called on a [`Call`].
889/// The [`Call`] type implements the [`IntoFuture`] trait, allowing it to be converted
890/// into a [`CallFuture`]. The future can be awaited to retrieve the result of the call.
891#[derive(Debug)]
892pub struct CallFuture<'m, 'a> {
893    state: Arc<RwLock<CallFutureState<'m, 'a>>>,
894}
895
896impl std::future::Future for CallFuture<'_, '_> {
897    type Output = Result<Response, CallFailed>;
898
899    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
900        let self_ref = Pin::into_inner(self);
901        let mut state = self_ref.state.write().unwrap();
902        match mem::take(&mut *state) {
903            CallFutureState::Prepared { call } => {
904                if let Err(e) = call.check_liquid_cycle_balance_sufficient() {
905                    *state = CallFutureState::PostComplete;
906                    Poll::Ready(Err(e.into()))
907                } else {
908                    match call.perform(Some(self_ref.state.clone())) {
909                        0 => {
910                            // call_perform returns 0 means the call was successfully enqueued.
911                            *state = CallFutureState::Executing {
912                                waker: context.waker().clone(),
913                                method: ic_cdk_executor::extend_current_method_context(),
914                                task: TaskHandle::current(),
915                            };
916                            Poll::Pending
917                        }
918                        _ => {
919                            *state = CallFutureState::PostComplete;
920                            Poll::Ready(Err(CallPerformFailed.into()))
921                        }
922                    }
923                }
924            }
925            CallFutureState::Executing { method, task, .. } => {
926                *state = CallFutureState::Executing {
927                    waker: context.waker().clone(),
928                    method,
929                    task,
930                };
931                Poll::Pending
932            }
933            CallFutureState::Complete { result } => {
934                *state = CallFutureState::PostComplete;
935                Poll::Ready(result)
936            }
937            CallFutureState::Trapped => trap("Call already trapped"),
938            CallFutureState::PostComplete => trap("CallFuture polled after completing"),
939        }
940    }
941}
942
943impl Drop for CallFuture<'_, '_> {
944    fn drop(&mut self) {
945        // If this future is dropped while is_recovering_from_trap is true,
946        // then it has been canceled due to a trap in another future.
947        if is_recovering_from_trap() {
948            *self.state.write().unwrap() = CallFutureState::Trapped;
949        }
950    }
951}
952
953/// The reply/reject callback for `ic0.call_new`.
954///
955/// It dereferences the future from a raw pointer, assigns the result and calls the waker.
956/// We cannot use a closure here because we pass raw pointers to the System and back.
957///
958/// # Safety
959///
960/// This function must only be passed to the IC with a pointer from `Arc::<RwLock<CallFutureState>>::into_raw` as userdata.
961unsafe extern "C" fn callback(env: usize) {
962    let state_ptr = env as *const RwLock<CallFutureState<'_, '_>>;
963    // SAFETY: This function is only ever called by the IC, and we only ever pass an Arc as userdata.
964    let state = unsafe { Arc::from_raw(state_ptr) };
965    let completed_state = CallFutureState::Complete {
966        result: match msg_reject_code() {
967            0 => Ok(Response(msg_arg_data())),
968            code => {
969                // The conversion is safe because the code is not 0.
970                Err(CallFailed::CallRejected(CallRejected {
971                    raw_reject_code: code,
972                    reject_message: msg_reject_msg(),
973                }))
974            }
975        },
976    };
977    let (waker, method) = match mem::replace(&mut *state.write().unwrap(), completed_state) {
978        CallFutureState::Executing { waker, method, .. } => (waker, method),
979        // This future has already been cancelled and waking it will do nothing.
980        // All that's left is to explicitly trap in case this is the last call being multiplexed,
981        // to replace an automatic trap from not replying.
982        CallFutureState::Trapped => trap("Call already trapped"),
983        _ => {
984            unreachable!(
985                "CallFutureState for in-flight calls should only be Executing or Trapped (callback)"
986            )
987        }
988    };
989    ic_cdk_executor::in_callback_executor_context_for(method, || {
990        waker.wake();
991    });
992}
993
994/// The cleanup callback for `ic0.call_on_cleanup`.
995///
996/// This function is called when [`callback`] was just called with the same parameter, and trapped.
997/// We can't guarantee internal consistency at this point, but we can at least e.g. drop mutex guards.
998/// Waker is a very opaque API, so the best we can do is set a global flag and proceed normally.
999///
1000/// # Safety
1001///
1002/// This function must only be passed to the IC with a pointer from `Arc::<RwLock<CallFutureState>>::into_raw` as userdata.
1003unsafe extern "C" fn cleanup(env: usize) {
1004    let state_ptr = env as *const RwLock<CallFutureState<'_, '_>>;
1005    // SAFETY: This function is only ever called by the IC, and we only ever pass a Arc as userdata.
1006    let state = unsafe { Arc::from_raw(state_ptr) };
1007    // We set the call result, even though it won't be read on the
1008    // default executor, because we can't guarantee it was called on
1009    // our executor. However, we are not allowed to inspect
1010    // reject_code() inside of a cleanup callback, so always set the
1011    // result to a reject.
1012    //
1013    // Borrowing does not trap - the rollback from the
1014    // previous trap ensures that the RwLock can be borrowed again.
1015    let err_state = CallFutureState::Complete {
1016        result: Err(CallFailed::CallRejected(CallRejected {
1017            raw_reject_code: RejectCode::CanisterReject as u32,
1018            reject_message: "cleanup".into(),
1019        })),
1020    };
1021    let (method, task) = match mem::replace(&mut *state.write().unwrap(), err_state) {
1022        CallFutureState::Executing { method, task, .. } => (method, task),
1023        CallFutureState::Trapped => {
1024            // The future has already been canceled and dropped. There is nothing
1025            // more to clean up except for the CallFutureState.
1026            return;
1027        }
1028        _ => {
1029            unreachable!(
1030                "CallFutureState for in-flight calls should only be Executing or Trapped (cleanup)"
1031            )
1032        }
1033    };
1034    ic_cdk_executor::in_trap_recovery_context_for(method, || {
1035        ic_cdk_executor::cancel_all_tasks_attached_to_current_method();
1036        if let Some(task) = task {
1037            ic_cdk_executor::cancel_task(&task);
1038        }
1039    });
1040}
1041
1042// # Internal END =============================================================
1043
1044/// Panics with an informative message when argument encoding fails.
1045///
1046/// Currently, Candid encoding only fails when heap memory is exhausted,
1047/// in which case execution would trap before reaching the unwrap.
1048///
1049/// However, since future implementations might introduce other failure cases,
1050/// we provide an informative panic message for better debuggability.
1051fn panic_when_encode_fails(err: candid::error::Error) -> Vec<u8> {
1052    panic!("failed to encode args: {err}")
1053}