ic_cdk/api/call.rs
1//! APIs to make and manage calls in the canister.
2
3#![allow(deprecated)]
4use crate::api::trap;
5use candid::utils::{ArgumentDecoder, ArgumentEncoder, decode_args_with_config_debug};
6use candid::{
7 CandidType, DecoderConfig, Deserialize, Principal, decode_args, encode_args, write_args,
8};
9use ic_cdk_executor::{MethodHandle, TaskHandle};
10use serde::ser::Error;
11use std::future::Future;
12use std::marker::PhantomData;
13use std::mem;
14use std::pin::Pin;
15use std::sync::{Arc, RwLock};
16use std::task::{Context, Poll, Waker};
17
18/// Rejection code from calling another canister.
19///
20/// These can be obtained either using `reject_code()` or `reject_result()`.
21#[allow(missing_docs)]
22#[deprecated(
23 since = "0.18.0",
24 note = "Please use `ic_cdk::call::RejectCode` instead."
25)]
26#[repr(usize)]
27#[derive(CandidType, Deserialize, Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
28pub enum RejectionCode {
29 NoError = 0,
30
31 SysFatal = 1,
32 SysTransient = 2,
33 DestinationInvalid = 3,
34 CanisterReject = 4,
35 CanisterError = 5,
36
37 Unknown,
38}
39
40impl From<usize> for RejectionCode {
41 fn from(code: usize) -> Self {
42 match code {
43 0 => RejectionCode::NoError,
44 1 => RejectionCode::SysFatal,
45 2 => RejectionCode::SysTransient,
46 3 => RejectionCode::DestinationInvalid,
47 4 => RejectionCode::CanisterReject,
48 5 => RejectionCode::CanisterError,
49 _ => RejectionCode::Unknown,
50 }
51 }
52}
53
54impl From<u32> for RejectionCode {
55 fn from(code: u32) -> Self {
56 RejectionCode::from(code as usize)
57 }
58}
59
60/// The result of a Call.
61///
62/// Errors on the IC have two components; a Code and a message associated with it.
63#[deprecated(
64 since = "0.18.0",
65 note = "Please use `ic_cdk::call::CallResult` instead."
66)]
67pub type CallResult<R> = Result<R, (RejectionCode, String)>;
68
69/// Internal state for the Future when sending a call.
70#[derive(Debug, Default)]
71enum CallFutureState<T: AsRef<[u8]>> {
72 /// The future has been constructed, and the call has not yet been performed.
73 /// Needed because futures are supposed to do nothing unless polled.
74 /// Polling will attempt to fire off the request. Success returns `Pending` and transitions to `Executing`,
75 /// failure returns `Ready` and transitions to `PostComplete.`
76 Prepared {
77 id: Principal,
78 method: String,
79 arg: T,
80 payment: u128,
81 },
82 /// The call has been performed and the message is in flight. Neither callback has been called. Polling will return `Pending`.
83 /// This state will transition to `Trapped` if the future is canceled because of a trap in another future.
84 Executing {
85 waker: Waker,
86 method: MethodHandle,
87 task: Option<TaskHandle>,
88 },
89 /// `callback` has been called, so the call has been completed. This completion state has not yet been read by the user.
90 /// Polling will return `Ready` and transition to `PostComplete`.
91 Complete { result: CallResult<Vec<u8>> },
92 /// The completion state of `Complete` has been returned from `poll` as `Poll::Ready`. Polling again will trap.
93 #[default]
94 PostComplete,
95 /// The future (*not* the state) was canceled because of a trap in another future during `Executing`. Polling will trap.
96 Trapped,
97}
98
99struct CallFuture<T: AsRef<[u8]>> {
100 state: Arc<RwLock<CallFutureState<T>>>,
101}
102
103impl<T: AsRef<[u8]>> Future for CallFuture<T> {
104 type Output = CallResult<Vec<u8>>;
105
106 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
107 let self_ref = Pin::into_inner(self);
108 let mut state = self_ref.state.write().unwrap();
109 match mem::take(&mut *state) {
110 CallFutureState::Prepared {
111 id,
112 method,
113 arg,
114 payment,
115 } => {
116 let callee = id.as_slice();
117 let args = arg.as_ref();
118 let state_ptr =
119 Arc::<RwLock<CallFutureState<T>>>::into_raw(Arc::clone(&self_ref.state));
120 // SAFETY:
121 // `callback` is intended as an entrypoint and therefore can be called as both reply and reject fn
122 // for ic0.call_new.
123 // - `cleanup` is intended as an entrypoint and therefore can be called as cleanup fn for ic0.call_on_cleanup.
124 // - `state_ptr` is a pointer created via Arc::<RwLock<CallFutureState<T>>>::into_raw, and can therefore be passed as the userdata for
125 // `callback` and `cleanup`.
126 // - callback and cleanup are safe to parameterize with T because they will always be called in the
127 // Executing or Trapped states which do not contain a T.
128 // - if-and-only-if ic0.call_perform returns 0, exactly one(‡) of `callback` or `cleanup`
129 // receive ownership of `state_ptr`
130 // - both functions deallocate `state_ptr`, and this enclosing function deallocates `state_ptr` if ic0.call_perform
131 // returns !=0, and therefore `state_ptr`'s ownership can be passed to FFI without leaking memory.
132 //
133 // ‡ The flow from outside the WASM runtime is that the callback runs, it traps, state is rolled back,
134 // and the cleanup callback runs afterwards. Inside the runtime, there is no difference between
135 // 'state is rolled back to before the callback was called' and 'the callback was never called'.
136 // So from the code's perspective, exactly one function is called.
137 let err_code = unsafe {
138 ic0::call_new(
139 callee,
140 &method,
141 callback::<T>,
142 state_ptr as usize,
143 callback::<T>,
144 state_ptr as usize,
145 );
146
147 ic0::call_data_append(args);
148 add_payment(payment);
149 ic0::call_on_cleanup(cleanup::<T>, state_ptr as usize);
150 ic0::call_perform()
151 };
152
153 // 0 is a special error code meaning call succeeded.
154 if err_code != 0 {
155 *state = CallFutureState::PostComplete;
156 // SAFETY: We just constructed this from Arc::into_raw.
157 // - `state_ptr_opt` is `Some` if-and-only-if ic0.call_new was called with ownership of `state`
158 // - by returning !=0, ic0.call_new relinquishes ownership of `state_ptr`; it will never be passed
159 // to any functions
160 // therefore, there is an outstanding handle to `state`, which it is safe to deallocate
161 unsafe {
162 Arc::from_raw(state_ptr);
163 }
164 let result = Err((
165 RejectionCode::from(err_code),
166 "Couldn't send message".to_string(),
167 ));
168 return Poll::Ready(result);
169 }
170 *state = CallFutureState::Executing {
171 waker: context.waker().clone(),
172 method: ic_cdk_executor::extend_current_method_context(),
173 task: TaskHandle::current(),
174 };
175 Poll::Pending
176 }
177 CallFutureState::Executing { method, task, .. } => {
178 *state = CallFutureState::Executing {
179 waker: context.waker().clone(),
180 method,
181 task,
182 };
183 Poll::Pending
184 }
185 CallFutureState::Complete { result } => {
186 *state = CallFutureState::PostComplete;
187 Poll::Ready(result)
188 }
189 CallFutureState::Trapped => trap("Call already trapped"),
190 CallFutureState::PostComplete => trap("CallFuture polled after completing"),
191 }
192 }
193}
194
195impl<T: AsRef<[u8]>> Drop for CallFuture<T> {
196 fn drop(&mut self) {
197 // If this future is dropped while is_recovering_from_trap is true,
198 // then it has been canceled due to a trap in another future.
199 if is_recovering_from_trap() {
200 *self.state.write().unwrap() = CallFutureState::Trapped;
201 }
202 }
203}
204
205/// The callback from IC dereferences the future from a raw pointer, assigns the
206/// result and calls the waker. We cannot use a closure here because we pass raw
207/// pointers to the System and back.
208///
209/// # Safety
210///
211/// This function must only be passed to the IC with a pointer from `Arc::<RwLock<CallFutureState<T>>>::into_raw` as userdata.
212unsafe extern "C" fn callback<T: AsRef<[u8]>>(env: usize) {
213 let state_ptr = env as *const RwLock<CallFutureState<T>>;
214 // SAFETY: This function is only ever called by the IC, and we only ever pass an Arc as userdata.
215 let state = unsafe { Arc::from_raw(state_ptr) };
216 let completed_state = CallFutureState::Complete {
217 result: match reject_code() {
218 RejectionCode::NoError => Ok(arg_data_raw()),
219 n => Err((n, reject_message())),
220 },
221 };
222 let (waker, method) = match mem::replace(&mut *state.write().unwrap(), completed_state) {
223 CallFutureState::Executing { waker, method, .. } => (waker, method),
224 // This future has already been cancelled and waking it will do nothing.
225 // All that's left is to explicitly trap in case this is the last call being multiplexed,
226 // to replace an automatic trap from not replying.
227 CallFutureState::Trapped => trap("Call already trapped"),
228 _ => {
229 unreachable!(
230 "CallFutureState for in-flight calls should only be Executing or Trapped (legacy callback)"
231 )
232 }
233 };
234 ic_cdk_executor::in_callback_executor_context_for(method, || {
235 waker.wake();
236 });
237}
238
239/// This function is called when [callback] was just called with the same parameter, and trapped.
240/// We can't guarantee internal consistency at this point, but we can at least e.g. drop mutex guards.
241/// Waker is a very opaque API, so the best we can do is set a global flag and proceed normally.
242///
243/// # Safety
244///
245/// This function must only be passed to the IC with a pointer from `Arc::<RwLock<CallFutureState<T>>>::into_raw` as userdata.
246unsafe extern "C" fn cleanup<T: AsRef<[u8]>>(env: usize) {
247 let state_ptr = env as *const RwLock<CallFutureState<T>>;
248 // SAFETY: This function is only ever called by the IC, and we only ever pass an Arc as userdata.
249 let state = unsafe { Arc::from_raw(state_ptr) };
250 // We set the call result, even though it won't be read on the
251 // default executor, because we can't guarantee it was called on
252 // our executor. However, we are not allowed to inspect
253 // reject_code() inside of a cleanup callback, so always set the
254 // result to a reject.
255 //
256 // Borrowing does not trap - the rollback from the
257 // previous trap ensures that the RwLock can be borrowed again.
258 let err_state = CallFutureState::Complete {
259 result: Err((RejectionCode::NoError, "cleanup".to_string())),
260 };
261 let (method, task) = match mem::replace(&mut *state.write().unwrap(), err_state) {
262 CallFutureState::Executing { method, task, .. } => (method, task),
263 CallFutureState::Trapped => {
264 // The future has already been canceled and dropped. There is nothing
265 // more to clean up except for the CallFutureState.
266 return;
267 }
268 _ => {
269 unreachable!(
270 "CallFutureState for in-flight calls should only be Executing or Trapped (legacy cleanup)"
271 )
272 }
273 };
274 ic_cdk_executor::in_trap_recovery_context_for(method, || {
275 ic_cdk_executor::cancel_all_tasks_attached_to_current_method();
276 if let Some(task) = task {
277 ic_cdk_executor::cancel_task(&task);
278 }
279 });
280}
281
282fn add_payment(payment: u128) {
283 if payment == 0 {
284 return;
285 }
286 ic0::call_cycles_add128(payment);
287}
288
289/// Sends a one-way message with `payment` cycles attached to it that invokes `method` with
290/// arguments `args` on the principal identified by `id`, ignoring the reply.
291///
292/// Returns `Ok(())` if the message was successfully enqueued, otherwise returns a reject code.
293///
294/// # Notes
295///
296/// * The caller has no way of checking whether the destination processed the notification.
297/// The system can drop the notification if the destination does not have resources to
298/// process the message (for example, if it's out of cycles or queue slots).
299///
300/// * The callee cannot tell whether the call is one-way or not.
301/// The callee must produce replies for all incoming messages.
302///
303/// * It is safe to upgrade a canister without stopping it first if it sends out *only*
304/// one-way messages.
305///
306/// * If the payment is non-zero and the system fails to deliver the notification, the behaviour
307/// is unspecified: the funds can be either reimbursed or consumed irrevocably by the IC depending
308/// on the underlying implementation of one-way calls.
309#[deprecated(
310 since = "0.18.0",
311 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
312)]
313pub fn notify_with_payment128<T: ArgumentEncoder>(
314 id: Principal,
315 method: &str,
316 args: T,
317 payment: u128,
318) -> Result<(), RejectionCode> {
319 let args_raw = encode_args(args).expect("failed to encode arguments");
320 notify_raw(id, method, &args_raw, payment)
321}
322
323/// Like [`notify_with_payment128`], but sets the payment to zero.
324#[deprecated(
325 since = "0.18.0",
326 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
327)]
328pub fn notify<T: ArgumentEncoder>(
329 id: Principal,
330 method: &str,
331 args: T,
332) -> Result<(), RejectionCode> {
333 notify_with_payment128(id, method, args, 0)
334}
335
336/// Like [notify], but sends the argument as raw bytes, skipping Candid serialization.
337#[deprecated(
338 since = "0.18.0",
339 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
340)]
341pub fn notify_raw(
342 id: Principal,
343 method: &str,
344 args_raw: &[u8],
345 payment: u128,
346) -> Result<(), RejectionCode> {
347 let callee = id.as_slice();
348 ic0::call_new_oneway(callee, method);
349 add_payment(payment);
350 ic0::call_data_append(args_raw);
351 let err_code = ic0::call_perform();
352 match err_code {
353 0 => Ok(()),
354 c => Err(RejectionCode::from(c)),
355 }
356}
357
358/// Performs an asynchronous call to another canister and pay cycles at the same time.
359///
360/// Treats arguments and returns as raw bytes. No data serialization and deserialization is performed.
361///
362/// # Example
363///
364/// It can be called:
365///
366/// ```rust
367/// # use ic_cdk::api::call::call_raw;
368/// # fn callee_canister() -> candid::Principal { unimplemented!() }
369/// async fn call_add_user() -> Vec<u8>{
370/// call_raw(callee_canister(), "add_user", b"abcd", 1_000_000u64).await.unwrap()
371/// }
372/// ```
373#[deprecated(
374 since = "0.18.0",
375 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
376)]
377pub fn call_raw<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
378 id: Principal,
379 method: &str,
380 args_raw: T,
381 payment: u64,
382) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
383 call_raw_internal(id, method, args_raw, payment.into())
384}
385
386/// Performs an asynchronous call to another canister and pay cycles (in `u128`) at the same time.
387///
388/// Treats arguments and returns as raw bytes. No data serialization and deserialization is performed.
389/// # Example
390///
391/// It can be called:
392///
393/// ```rust
394/// # use ic_cdk::api::call::call_raw128;
395/// # fn callee_canister() -> candid::Principal { unimplemented!() }
396/// async fn call_add_user() -> Vec<u8>{
397/// call_raw128(callee_canister(), "add_user", b"abcd", 1_000_000u128).await.unwrap()
398/// }
399/// ```
400#[deprecated(
401 since = "0.18.0",
402 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
403)]
404pub fn call_raw128<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
405 id: Principal,
406 method: &str,
407 args_raw: T,
408 payment: u128,
409) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
410 call_raw_internal(id, method, args_raw, payment)
411}
412
413fn call_raw_internal<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
414 id: Principal,
415 method: &str,
416 args_raw: T,
417 payment: u128,
418) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
419 let state = Arc::new(RwLock::new(CallFutureState::Prepared {
420 id,
421 method: method.to_string(),
422 arg: args_raw,
423 payment,
424 }));
425 CallFuture { state }
426}
427
428fn decoder_error_to_reject<T>(err: candid::error::Error) -> (RejectionCode, String) {
429 (
430 RejectionCode::CanisterError,
431 format!(
432 "failed to decode canister response as {}: {}",
433 std::any::type_name::<T>(),
434 err
435 ),
436 )
437}
438
439/// Performs an asynchronous call to another canister.
440///
441/// # Example
442///
443/// Assuming that the callee canister has following interface:
444///
445/// ```text
446/// service : {
447/// add_user: (name: text) -> (nat64);
448/// }
449/// ```
450///
451/// It can be called:
452///
453/// ```rust
454/// # use ic_cdk::api::call::call;
455/// # fn callee_canister() -> candid::Principal { unimplemented!() }
456/// async fn call_add_user() -> u64 {
457/// let (user_id,) = call(callee_canister(), "add_user", ("Alice".to_string(),)).await.unwrap();
458/// user_id
459/// }
460/// ```
461///
462/// # Note
463///
464/// * Both argument and return types are tuples even if it has only one value, e.g `(user_id,)`, `("Alice".to_string(),)`.
465/// * The type annotation on return type is required. Or the return type can be inferred from the context.
466/// * The asynchronous call must be awaited in order for the inter-canister call to be made.
467/// * If the reply payload is not a valid encoding of the expected type `T`, the call results in [`RejectionCode::CanisterError`] error.
468#[deprecated(
469 since = "0.18.0",
470 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
471)]
472pub fn call<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
473 id: Principal,
474 method: &str,
475 args: T,
476) -> impl Future<Output = CallResult<R>> + Send + Sync {
477 let args_raw = encode_args(args).expect("Failed to encode arguments.");
478 let fut = call_raw(id, method, args_raw, 0);
479 async {
480 let bytes = fut.await?;
481 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
482 }
483}
484
485/// Performs an asynchronous call to another canister and pay cycles at the same time.
486///
487/// # Example
488///
489/// Assuming that the callee canister has following interface:
490///
491/// ```text
492/// service : {
493/// add_user: (name: text) -> (nat64);
494/// }
495/// ```
496///
497/// It can be called:
498///
499/// ```rust
500/// # use ic_cdk::api::call::call_with_payment;
501/// # fn callee_canister() -> candid::Principal { unimplemented!() }
502/// async fn call_add_user() -> u64 {
503/// let (user_id,) = call_with_payment(callee_canister(), "add_user", ("Alice".to_string(),), 1_000_000u64).await.unwrap();
504/// user_id
505/// }
506/// ```
507///
508/// # Note
509///
510/// * Both argument and return types are tuples even if it has only one value, e.g `(user_id,)`, `("Alice".to_string(),)`.
511/// * The type annotation on return type is required. Or the return type can be inferred from the context.
512/// * The asynchronous call must be awaited in order for the inter-canister call to be made.
513/// * If the reply payload is not a valid encoding of the expected type `T`, the call results in [`RejectionCode::CanisterError`] error.
514#[deprecated(
515 since = "0.18.0",
516 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
517)]
518pub fn call_with_payment<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
519 id: Principal,
520 method: &str,
521 args: T,
522 cycles: u64,
523) -> impl Future<Output = CallResult<R>> + Send + Sync {
524 let args_raw = encode_args(args).expect("Failed to encode arguments.");
525 let fut = call_raw(id, method, args_raw, cycles);
526 async {
527 let bytes = fut.await?;
528 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
529 }
530}
531
532/// Performs an asynchronous call to another canister and pay cycles (in `u128`) at the same time.
533///
534/// # Example
535///
536/// Assuming that the callee canister has following interface:
537///
538/// ```text
539/// service : {
540/// add_user: (name: text) -> (nat64);
541/// }
542/// ```
543///
544/// It can be called:
545///
546/// ```rust
547/// # use ic_cdk::api::call::call_with_payment128;
548/// # fn callee_canister() -> candid::Principal { unimplemented!() }
549/// async fn call_add_user() -> u64 {
550/// let (user_id,) = call_with_payment128(callee_canister(), "add_user", ("Alice".to_string(),), 1_000_000u128).await.unwrap();
551/// user_id
552/// }
553/// ```
554///
555/// # Note
556///
557/// * Both argument and return types are tuples even if it has only one value, e.g `(user_id,)`, `("Alice".to_string(),)`.
558/// * The type annotation on return type is required. Or the return type can be inferred from the context.
559/// * The asynchronous call must be awaited in order for the inter-canister call to be made.
560/// * If the reply payload is not a valid encoding of the expected type `T`, the call results in [`RejectionCode::CanisterError`] error.
561#[deprecated(
562 since = "0.18.0",
563 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
564)]
565pub fn call_with_payment128<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
566 id: Principal,
567 method: &str,
568 args: T,
569 cycles: u128,
570) -> impl Future<Output = CallResult<R>> + Send + Sync {
571 let args_raw = encode_args(args).expect("Failed to encode arguments.");
572 let fut = call_raw128(id, method, args_raw, cycles);
573 async {
574 let bytes = fut.await?;
575 decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
576 }
577}
578
579/// Performs an asynchronous call to another canister and pay cycles (in `u128`).
580/// It also allows setting a quota for decoding the return values.
581/// The decoding quota is strongly recommended when calling third-party or untrusted canisters.
582///
583/// # Example
584///
585/// Assuming that the callee canister has following interface:
586///
587/// ```text
588/// service : {
589/// add_user: (name: text) -> (nat64);
590/// }
591/// ```
592///
593/// It can be called:
594///
595/// ```rust
596/// # use ic_cdk::api::call::{call_with_config, ArgDecoderConfig};
597/// # fn callee_canister() -> candid::Principal { unimplemented!() }
598/// async fn call_add_user() -> u64 {
599/// let config = ArgDecoderConfig {
600/// // The function only returns a nat64, to accomodate future upgrades, we set a larger decoding_quota.
601/// decoding_quota: Some(10_000),
602/// // To accomodate future upgrades, reserve some skipping_quota.
603/// skipping_quota: Some(100),
604/// // Enable debug mode to print decoding instructions and cost to the replica log.
605/// debug: true,
606/// };
607/// let (user_id,) = call_with_config(callee_canister(), "add_user", ("Alice".to_string(),), 1_000_000u128, &config).await.unwrap();
608/// user_id
609/// }
610/// ```
611#[deprecated(
612 since = "0.18.0",
613 note = "Please use `ic_cdk::call::Call::unbounded_wait()` instead."
614)]
615pub fn call_with_config<'b, T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
616 id: Principal,
617 method: &'b str,
618 args: T,
619 cycles: u128,
620 arg_config: &'b ArgDecoderConfig,
621) -> impl Future<Output = CallResult<R>> + Send + Sync + 'b {
622 let args_raw = encode_args(args).expect("Failed to encode arguments.");
623 let fut = call_raw128(id, method, args_raw, cycles);
624 async move {
625 let bytes = fut.await?;
626 let config = arg_config.to_candid_config();
627 let pre_cycles = if arg_config.debug {
628 Some(crate::api::performance_counter(0))
629 } else {
630 None
631 };
632 match decode_args_with_config_debug(&bytes, &config) {
633 Err(e) => Err(decoder_error_to_reject::<R>(e)),
634 Ok((r, cost)) => {
635 if arg_config.debug {
636 print_decoding_debug_info(&format!("{method} return"), &cost, pre_cycles);
637 }
638 Ok(r)
639 }
640 }
641 }
642}
643
644fn print_decoding_debug_info(title: &str, cost: &DecoderConfig, pre_cycles: Option<u64>) {
645 use crate::api::{performance_counter, print};
646 let pre_cycles = pre_cycles.unwrap_or(0);
647 let instrs = performance_counter(0) - pre_cycles;
648 print(format!("[Debug] {title} decoding instructions: {instrs}"));
649 if let Some(n) = cost.decoding_quota {
650 print(format!("[Debug] {title} decoding cost: {n}"));
651 }
652 if let Some(n) = cost.skipping_quota {
653 print(format!("[Debug] {title} skipping cost: {n}"));
654 }
655}
656
657/// Returns a result that maps over the call
658///
659/// It will be Ok(T) if the call succeeded (with T being the `arg_data`),
660/// and [`reject_message()`] if it failed.
661#[deprecated(
662 since = "0.18.0",
663 note = "Please use `ic_cdk::api::{msg_reject_code, msg_reject_msg}` instead."
664)]
665pub fn result<T: for<'a> ArgumentDecoder<'a>>() -> Result<T, String> {
666 match reject_code() {
667 RejectionCode::NoError => {
668 decode_args(&arg_data_raw()).map_err(|e| format!("Failed to decode arguments: {e}"))
669 }
670 _ => Err(reject_message()),
671 }
672}
673
674/// Returns the rejection code for the call.
675#[deprecated(
676 since = "0.18.0",
677 note = "Please use `ic_cdk::api::msg_reject_code` instead."
678)]
679pub fn reject_code() -> RejectionCode {
680 let code = ic0::msg_reject_code();
681 RejectionCode::from(code)
682}
683
684/// Returns the rejection message.
685#[deprecated(
686 since = "0.18.0",
687 note = "Please use `ic_cdk::api::msg_reject_msg` instead."
688)]
689pub fn reject_message() -> String {
690 let len = ic0::msg_reject_msg_size();
691 let mut bytes = vec![0u8; len];
692 ic0::msg_reject_msg_copy(&mut bytes, 0);
693 String::from_utf8_lossy(&bytes).into_owned()
694}
695
696/// Rejects the current call with the message.
697#[deprecated(
698 since = "0.18.0",
699 note = "Please use `ic_cdk::api::msg_reject` instead."
700)]
701pub fn reject(message: &str) {
702 let err_message = message.as_bytes();
703 ic0::msg_reject(err_message);
704}
705
706/// An `io::Write` for message replies.
707#[derive(Debug, Copy, Clone)]
708#[deprecated(
709 since = "0.18.0",
710 note = "Please use `ic_cdk::api::msg_reply` instead."
711)]
712pub struct CallReplyWriter;
713
714impl std::io::Write for CallReplyWriter {
715 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
716 ic0::msg_reply_data_append(buf);
717 Ok(buf.len())
718 }
719
720 fn flush(&mut self) -> std::io::Result<()> {
721 Ok(())
722 }
723}
724
725/// Replies to the current call with a candid argument.
726#[deprecated(
727 since = "0.18.0",
728 note = "Please use `ic_cdk::api::msg_reply` instead."
729)]
730pub fn reply<T: ArgumentEncoder>(reply: T) {
731 write_args(&mut CallReplyWriter, reply).expect("Could not encode reply.");
732 ic0::msg_reply();
733}
734
735/// Returns the amount of cycles that were transferred by the caller
736/// of the current call, and is still available in this message.
737#[deprecated(
738 since = "0.18.0",
739 note = "Please use `ic_cdk::api::msg_cycles_available` instead."
740)]
741pub fn msg_cycles_available() -> u64 {
742 msg_cycles_available128() as u64
743}
744
745/// Returns the amount of cycles that were transferred by the caller
746/// of the current call, and is still available in this message.
747#[deprecated(
748 since = "0.18.0",
749 note = "Please use `ic_cdk::api::msg_cycles_available` instead."
750)]
751pub fn msg_cycles_available128() -> u128 {
752 ic0::msg_cycles_available128()
753}
754
755/// Returns the amount of cycles that came back with the response as a refund.
756///
757/// The refund has already been added to the canister balance automatically.
758#[deprecated(
759 since = "0.18.0",
760 note = "Please use `ic_cdk::api::msg_cycles_refunded` instead."
761)]
762pub fn msg_cycles_refunded() -> u64 {
763 msg_cycles_refunded128() as u64
764}
765
766/// Returns the amount of cycles that came back with the response as a refund.
767///
768/// The refund has already been added to the canister balance automatically.
769#[deprecated(
770 since = "0.18.0",
771 note = "Please use `ic_cdk::api::msg_cycles_refunded` instead."
772)]
773pub fn msg_cycles_refunded128() -> u128 {
774 ic0::msg_cycles_refunded128()
775}
776
777/// Moves cycles from the call to the canister balance.
778///
779/// The actual amount moved will be returned.
780#[deprecated(
781 since = "0.18.0",
782 note = "Please use `ic_cdk::api::msg_cycles_accept` instead."
783)]
784pub fn msg_cycles_accept(max_amount: u64) -> u64 {
785 msg_cycles_accept128(u128::from(max_amount)) as u64
786}
787
788/// Moves cycles from the call to the canister balance.
789///
790/// The actual amount moved will be returned.
791#[deprecated(
792 since = "0.18.0",
793 note = "Please use `ic_cdk::api::msg_cycles_accept` instead."
794)]
795pub fn msg_cycles_accept128(max_amount: u128) -> u128 {
796 ic0::msg_cycles_accept128(max_amount)
797}
798
799/// Returns the argument data as bytes.
800#[deprecated(
801 since = "0.18.0",
802 note = "Please use `ic_cdk::api::msg_arg_data` instead."
803)]
804pub fn arg_data_raw() -> Vec<u8> {
805 let len: usize = ic0::msg_arg_data_size();
806 let mut bytes = Vec::with_capacity(len);
807 ic0::msg_arg_data_copy_uninit(&mut bytes.spare_capacity_mut()[..len], 0);
808 // SAFETY: ic0.msg_arg_data_copy writes to all of `bytes[0..len]`, so `set_len` is safe to call with the new len.
809 unsafe {
810 bytes.set_len(len);
811 }
812 bytes
813}
814
815/// Gets the len of the raw-argument-data-bytes.
816#[deprecated(
817 since = "0.18.0",
818 note = "Please use `ic_cdk::api::msg_arg_data` instead."
819)]
820pub fn arg_data_raw_size() -> usize {
821 ic0::msg_arg_data_size()
822}
823
824/// Replies with the bytes passed
825#[deprecated(
826 since = "0.18.0",
827 note = "Please use `ic_cdk::api::msg_reply` instead."
828)]
829pub fn reply_raw(buf: &[u8]) {
830 if !buf.is_empty() {
831 ic0::msg_reply_data_append(buf);
832 }
833 ic0::msg_reply();
834}
835
836#[deprecated(
837 since = "0.18.0",
838 note = "Please use `candid::de::DecoderConfig` instead."
839)]
840#[derive(Debug)]
841/// Config to control the behavior of decoding canister entry point arguments.
842pub struct ArgDecoderConfig {
843 /// Limit the total amount of work the deserializer can perform. See [docs on the Candid library](https://docs.rs/candid/latest/candid/de/struct.DecoderConfig.html#method.set_decoding_quota) to understand the cost model.
844 pub decoding_quota: Option<usize>,
845 /// Limit the total amount of work for skipping unneeded data on the wire. See [docs on the Candid library](https://docs.rs/candid/latest/candid/de/struct.DecoderConfig.html#method.set_skipping_quota) to understand the skipping cost.
846 pub skipping_quota: Option<usize>,
847 /// When set to true, print instruction count and the decoding/skipping cost to the replica log.
848 pub debug: bool,
849}
850impl ArgDecoderConfig {
851 fn to_candid_config(&self) -> DecoderConfig {
852 let mut config = DecoderConfig::new();
853 if let Some(n) = self.decoding_quota {
854 config.set_decoding_quota(n);
855 }
856 if let Some(n) = self.skipping_quota {
857 config.set_skipping_quota(n);
858 }
859 if self.debug {
860 config.set_full_error_message(true);
861 }
862 config
863 }
864}
865impl Default for ArgDecoderConfig {
866 fn default() -> Self {
867 Self {
868 decoding_quota: None,
869 skipping_quota: Some(10_000),
870 debug: false,
871 }
872 }
873}
874
875/// Returns the argument data in the current call. Traps if the data cannot be
876/// decoded.
877#[deprecated(
878 since = "0.18.0",
879 note = "Please use `ic_cdk::call::msg_arg_data` instead."
880)]
881pub fn arg_data<R: for<'a> ArgumentDecoder<'a>>(arg_config: ArgDecoderConfig) -> R {
882 let bytes = arg_data_raw();
883
884 let config = arg_config.to_candid_config();
885 let res = decode_args_with_config_debug(&bytes, &config);
886 match res {
887 Err(e) => trap(format!("failed to decode call arguments: {e:?}")),
888 Ok((r, cost)) => {
889 if arg_config.debug {
890 print_decoding_debug_info("Argument", &cost, None);
891 }
892 r
893 }
894 }
895}
896
897/// Accepts the ingress message.
898#[deprecated(
899 since = "0.18.0",
900 note = "Please use `ic_cdk::api::accept_message` instead."
901)]
902pub fn accept_message() {
903 ic0::accept_message();
904}
905
906/// Returns the name of current canister method.
907#[deprecated(
908 since = "0.18.0",
909 note = "Please use `ic_cdk::api::msg_method_name` instead."
910)]
911pub fn method_name() -> String {
912 let len = ic0::msg_method_name_size();
913 let mut bytes = vec![0u8; len];
914 ic0::msg_method_name_copy(&mut bytes, 0);
915 String::from_utf8_lossy(&bytes).into_owned()
916}
917
918/// Gets the value of specified performance counter
919///
920/// See [`crate::api::performance_counter`].
921#[deprecated(
922 since = "0.11.3",
923 note = "This method conceptually doesn't belong to this module. Please use `ic_cdk::api::performance_counter` instead."
924)]
925pub fn performance_counter(counter_type: u32) -> u64 {
926 ic0::performance_counter(counter_type)
927}
928
929/// Pretends to have the Candid type `T`, but unconditionally errors
930/// when serialized.
931///
932/// Usable, but not required, as metadata when using `#[query(manual_reply = true)]`,
933/// so an accurate Candid file can still be generated.
934#[deprecated(
935 since = "0.18.0",
936 note = "Please use `std::marker::PhantomData` with manual_reply instead."
937)]
938#[derive(Debug, Copy, Clone, Default)]
939pub struct ManualReply<T: ?Sized>(PhantomData<T>);
940
941impl<T: ?Sized> ManualReply<T> {
942 /// Constructs a new `ManualReply`.
943 #[allow(clippy::self_named_constructors)]
944 pub const fn empty() -> Self {
945 Self(PhantomData)
946 }
947 /// Replies with the given value and returns a new `ManualReply`,
948 /// for a useful reply-then-return shortcut.
949 pub fn all<U>(value: U) -> Self
950 where
951 U: ArgumentEncoder,
952 {
953 reply(value);
954 Self::empty()
955 }
956 /// Replies with a one-element tuple around the given value and returns
957 /// a new `ManualReply`, for a useful reply-then-return shortcut.
958 pub fn one<U>(value: U) -> Self
959 where
960 U: CandidType,
961 {
962 reply((value,));
963 Self::empty()
964 }
965
966 /// Rejects the call with the specified message and returns a new
967 /// `ManualReply`, for a useful reply-then-return shortcut.
968 pub fn reject(message: impl AsRef<str>) -> Self {
969 reject(message.as_ref());
970 Self::empty()
971 }
972}
973
974impl<T> CandidType for ManualReply<T>
975where
976 T: CandidType + ?Sized,
977{
978 fn _ty() -> candid::types::Type {
979 T::_ty()
980 }
981 /// Unconditionally errors.
982 fn idl_serialize<S>(&self, _: S) -> Result<(), S::Error>
983 where
984 S: candid::types::Serializer,
985 {
986 Err(S::Error::custom("`Empty` cannot be serialized"))
987 }
988}
989
990/// Tells you whether the current async fn is being canceled due to a trap/panic.
991///
992/// If a function traps/panics, then the canister state is rewound to the beginning of the function.
993/// However, due to the way async works, the beginning of the function as the IC understands it is actually
994/// the most recent `await` from an inter-canister-call. This means that part of the function will have executed,
995/// and part of it won't.
996///
997/// When this happens the CDK will cancel the task, causing destructors to be run. If you need any functions to be run
998/// no matter what happens, they should happen in a destructor; the [`scopeguard`](https://docs.rs/scopeguard) crate
999/// provides a convenient wrapper for this. In a destructor, `is_recovering_from_trap` serves the same purpose as
1000/// [`is_panicking`](std::thread::panicking) - it tells you whether the destructor is executing *because* of a trap,
1001/// as opposed to just because the scope was exited, so you could e.g. implement mutex poisoning.
1002#[deprecated(
1003 since = "0.18.0",
1004 note = "Please use `ic_cdk::futures::is_recovering_from_trap` instead."
1005)]
1006pub fn is_recovering_from_trap() -> bool {
1007 crate::futures::is_recovering_from_trap()
1008}