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