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