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