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}