use crate::api::trap;
use candid::utils::{decode_args_with_config_debug, ArgumentDecoder, ArgumentEncoder};
use candid::{
decode_args, encode_args, write_args, CandidType, DecoderConfig, Deserialize, Principal,
};
use serde::ser::Error;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock, Weak};
use std::task::{Context, Poll, Waker};
#[allow(missing_docs)]
#[repr(i32)]
#[derive(CandidType, Deserialize, Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RejectionCode {
NoError = 0,
SysFatal = 1,
SysTransient = 2,
DestinationInvalid = 3,
CanisterReject = 4,
CanisterError = 5,
Unknown,
}
impl From<i32> for RejectionCode {
fn from(code: i32) -> Self {
match code {
0 => RejectionCode::NoError,
1 => RejectionCode::SysFatal,
2 => RejectionCode::SysTransient,
3 => RejectionCode::DestinationInvalid,
4 => RejectionCode::CanisterReject,
5 => RejectionCode::CanisterError,
_ => RejectionCode::Unknown,
}
}
}
impl From<u32> for RejectionCode {
fn from(code: u32) -> Self {
RejectionCode::from(code as i32)
}
}
pub type CallResult<R> = Result<R, (RejectionCode, String)>;
struct CallFutureState<T: AsRef<[u8]>> {
result: Option<CallResult<Vec<u8>>>,
waker: Option<Waker>,
id: Principal,
method: String,
arg: T,
payment: u128,
}
struct CallFuture<T: AsRef<[u8]>> {
state: Arc<RwLock<CallFutureState<T>>>,
}
impl<T: AsRef<[u8]>> Future for CallFuture<T> {
type Output = CallResult<Vec<u8>>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let self_ref = Pin::into_inner(self);
let mut state = self_ref.state.write().unwrap();
if let Some(result) = state.result.take() {
Poll::Ready(result)
} else {
if state.waker.is_none() {
let callee = state.id.as_slice();
let method = &state.method;
let args = state.arg.as_ref();
let payment = state.payment;
let state_ptr = Weak::into_raw(Arc::downgrade(&self_ref.state));
let err_code = unsafe {
ic0::call_new(
callee.as_ptr() as i32,
callee.len() as i32,
method.as_ptr() as i32,
method.len() as i32,
callback::<T> as usize as i32,
state_ptr as i32,
callback::<T> as usize as i32,
state_ptr as i32,
);
ic0::call_data_append(args.as_ptr() as i32, args.len() as i32);
add_payment(payment);
ic0::call_on_cleanup(cleanup::<T> as usize as i32, state_ptr as i32);
ic0::call_perform()
};
if err_code != 0 {
let result = Err((
RejectionCode::from(err_code),
"Couldn't send message".to_string(),
));
state.result = Some(result.clone());
return Poll::Ready(result);
}
}
state.waker = Some(context.waker().clone());
Poll::Pending
}
}
}
unsafe extern "C" fn callback<T: AsRef<[u8]>>(state_ptr: *const RwLock<CallFutureState<T>>) {
let state = unsafe { Weak::from_raw(state_ptr) };
if let Some(state) = state.upgrade() {
{
state.write().unwrap().result = Some(match reject_code() {
RejectionCode::NoError => Ok(arg_data_raw()),
n => Err((n, reject_message())),
});
}
let w = state.write().unwrap().waker.take();
if let Some(waker) = w {
waker.wake()
}
}
}
unsafe extern "C" fn cleanup<T: AsRef<[u8]>>(state_ptr: *const RwLock<CallFutureState<T>>) {
let state = unsafe { Weak::from_raw(state_ptr) };
if let Some(state) = state.upgrade() {
state.write().unwrap().result = Some(Err((RejectionCode::NoError, "cleanup".to_string())));
let w = state.write().unwrap().waker.take();
if let Some(waker) = w {
crate::futures::CLEANUP.store(true, Ordering::Relaxed);
waker.wake();
crate::futures::CLEANUP.store(false, Ordering::Relaxed);
}
}
}
fn add_payment(payment: u128) {
if payment == 0 {
return;
}
let high = (payment >> 64) as u64;
let low = (payment & u64::MAX as u128) as u64;
unsafe {
ic0::call_cycles_add128(high as i64, low as i64);
}
}
pub fn notify_with_payment128<T: ArgumentEncoder>(
id: Principal,
method: &str,
args: T,
payment: u128,
) -> Result<(), RejectionCode> {
let args_raw = encode_args(args).expect("failed to encode arguments");
notify_raw(id, method, &args_raw, payment)
}
pub fn notify<T: ArgumentEncoder>(
id: Principal,
method: &str,
args: T,
) -> Result<(), RejectionCode> {
notify_with_payment128(id, method, args, 0)
}
pub fn notify_raw(
id: Principal,
method: &str,
args_raw: &[u8],
payment: u128,
) -> Result<(), RejectionCode> {
let callee = id.as_slice();
let err_code = unsafe {
ic0::call_new(
callee.as_ptr() as i32,
callee.len() as i32,
method.as_ptr() as i32,
method.len() as i32,
-1,
-1,
-1,
-1,
);
add_payment(payment);
ic0::call_data_append(args_raw.as_ptr() as i32, args_raw.len() as i32);
ic0::call_perform()
};
match err_code {
0 => Ok(()),
c => Err(RejectionCode::from(c)),
}
}
pub fn call_raw<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
id: Principal,
method: &str,
args_raw: T,
payment: u64,
) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
call_raw_internal(id, method, args_raw, payment.into())
}
pub fn call_raw128<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
id: Principal,
method: &str,
args_raw: T,
payment: u128,
) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
call_raw_internal(id, method, args_raw, payment)
}
fn call_raw_internal<'a, T: AsRef<[u8]> + Send + Sync + 'a>(
id: Principal,
method: &str,
args_raw: T,
payment: u128,
) -> impl Future<Output = CallResult<Vec<u8>>> + Send + Sync + 'a {
let state = Arc::new(RwLock::new(CallFutureState {
result: None,
waker: None,
id,
method: method.to_string(),
arg: args_raw,
payment,
}));
CallFuture { state }
}
fn decoder_error_to_reject<T>(err: candid::error::Error) -> (RejectionCode, String) {
(
RejectionCode::CanisterError,
format!(
"failed to decode canister response as {}: {}",
std::any::type_name::<T>(),
err
),
)
}
pub fn call<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
id: Principal,
method: &str,
args: T,
) -> impl Future<Output = CallResult<R>> + Send + Sync {
let args_raw = encode_args(args).expect("Failed to encode arguments.");
let fut = call_raw(id, method, args_raw, 0);
async {
let bytes = fut.await?;
decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
}
}
pub fn call_with_payment<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
id: Principal,
method: &str,
args: T,
cycles: u64,
) -> impl Future<Output = CallResult<R>> + Send + Sync {
let args_raw = encode_args(args).expect("Failed to encode arguments.");
let fut = call_raw(id, method, args_raw, cycles);
async {
let bytes = fut.await?;
decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
}
}
pub fn call_with_payment128<T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
id: Principal,
method: &str,
args: T,
cycles: u128,
) -> impl Future<Output = CallResult<R>> + Send + Sync {
let args_raw = encode_args(args).expect("Failed to encode arguments.");
let fut = call_raw128(id, method, args_raw, cycles);
async {
let bytes = fut.await?;
decode_args(&bytes).map_err(decoder_error_to_reject::<R>)
}
}
pub fn call_with_config<'b, T: ArgumentEncoder, R: for<'a> ArgumentDecoder<'a>>(
id: Principal,
method: &'b str,
args: T,
cycles: u128,
arg_config: &'b ArgDecoderConfig,
) -> impl Future<Output = CallResult<R>> + Send + Sync + 'b {
let args_raw = encode_args(args).expect("Failed to encode arguments.");
let fut = call_raw128(id, method, args_raw, cycles);
async move {
let bytes = fut.await?;
let config = arg_config.to_candid_config();
let pre_cycles = if arg_config.debug {
Some(crate::api::performance_counter(0))
} else {
None
};
match decode_args_with_config_debug(&bytes, &config) {
Err(e) => Err(decoder_error_to_reject::<R>(e)),
Ok((r, cost)) => {
if arg_config.debug {
print_decoding_debug_info(&format!("{method} return"), &cost, pre_cycles);
}
Ok(r)
}
}
}
}
fn print_decoding_debug_info(title: &str, cost: &DecoderConfig, pre_cycles: Option<u64>) {
use crate::api::{performance_counter, print};
let pre_cycles = pre_cycles.unwrap_or(0);
let instrs = performance_counter(0) - pre_cycles;
print(format!("[Debug] {title} decoding instructions: {instrs}"));
if let Some(n) = cost.decoding_quota {
print(format!("[Debug] {title} decoding cost: {n}"));
}
if let Some(n) = cost.skipping_quota {
print(format!("[Debug] {title} skipping cost: {n}"));
}
}
pub fn result<T: for<'a> ArgumentDecoder<'a>>() -> Result<T, String> {
match reject_code() {
RejectionCode::NoError => {
decode_args(&arg_data_raw()).map_err(|e| format!("Failed to decode arguments: {}", e))
}
_ => Err(reject_message()),
}
}
pub fn reject_code() -> RejectionCode {
let code = unsafe { ic0::msg_reject_code() };
RejectionCode::from(code)
}
pub fn reject_message() -> String {
let len: u32 = unsafe { ic0::msg_reject_msg_size() as u32 };
let mut bytes = vec![0u8; len as usize];
unsafe {
ic0::msg_reject_msg_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
}
String::from_utf8_lossy(&bytes).into_owned()
}
pub fn reject(message: &str) {
let err_message = message.as_bytes();
unsafe {
ic0::msg_reject(err_message.as_ptr() as i32, err_message.len() as i32);
}
}
#[derive(Debug, Copy, Clone)]
pub struct CallReplyWriter;
impl std::io::Write for CallReplyWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
unsafe {
ic0::msg_reply_data_append(buf.as_ptr() as i32, buf.len() as i32);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub fn reply<T: ArgumentEncoder>(reply: T) {
write_args(&mut CallReplyWriter, reply).expect("Could not encode reply.");
unsafe {
ic0::msg_reply();
}
}
pub fn msg_cycles_available() -> u64 {
unsafe { ic0::msg_cycles_available() as u64 }
}
pub fn msg_cycles_available128() -> u128 {
let mut recv = 0u128;
unsafe {
ic0::msg_cycles_available128(&mut recv as *mut u128 as i32);
}
recv
}
pub fn msg_cycles_refunded() -> u64 {
unsafe { ic0::msg_cycles_refunded() as u64 }
}
pub fn msg_cycles_refunded128() -> u128 {
let mut recv = 0u128;
unsafe {
ic0::msg_cycles_refunded128(&mut recv as *mut u128 as i32);
}
recv
}
pub fn msg_cycles_accept(max_amount: u64) -> u64 {
unsafe { ic0::msg_cycles_accept(max_amount as i64) as u64 }
}
pub fn msg_cycles_accept128(max_amount: u128) -> u128 {
let high = (max_amount >> 64) as u64;
let low = (max_amount & u64::MAX as u128) as u64;
let mut recv = 0u128;
unsafe {
ic0::msg_cycles_accept128(high as i64, low as i64, &mut recv as *mut u128 as i32);
}
recv
}
pub fn arg_data_raw() -> Vec<u8> {
let len: usize = unsafe { ic0::msg_arg_data_size() as usize };
let mut bytes = Vec::with_capacity(len);
unsafe {
ic0::msg_arg_data_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
bytes.set_len(len);
}
bytes
}
pub fn arg_data_raw_size() -> usize {
unsafe { ic0::msg_arg_data_size() as usize }
}
pub fn reply_raw(buf: &[u8]) {
if !buf.is_empty() {
unsafe { ic0::msg_reply_data_append(buf.as_ptr() as i32, buf.len() as i32) }
};
unsafe { ic0::msg_reply() };
}
#[derive(Debug)]
pub struct ArgDecoderConfig {
pub decoding_quota: Option<usize>,
pub skipping_quota: Option<usize>,
pub debug: bool,
}
impl ArgDecoderConfig {
fn to_candid_config(&self) -> DecoderConfig {
let mut config = DecoderConfig::new();
if let Some(n) = self.decoding_quota {
config.set_decoding_quota(n);
}
if let Some(n) = self.skipping_quota {
config.set_skipping_quota(n);
}
if self.debug {
config.set_full_error_message(true);
}
config
}
}
impl Default for ArgDecoderConfig {
fn default() -> Self {
Self {
decoding_quota: None,
skipping_quota: Some(10_000),
debug: false,
}
}
}
pub fn arg_data<R: for<'a> ArgumentDecoder<'a>>(arg_config: ArgDecoderConfig) -> R {
let bytes = arg_data_raw();
let config = arg_config.to_candid_config();
let res = decode_args_with_config_debug(&bytes, &config);
match res {
Err(e) => trap(&format!("failed to decode call arguments: {:?}", e)),
Ok((r, cost)) => {
if arg_config.debug {
print_decoding_debug_info("Argument", &cost, None);
}
r
}
}
}
pub fn accept_message() {
unsafe {
ic0::accept_message();
}
}
pub fn method_name() -> String {
let len: u32 = unsafe { ic0::msg_method_name_size() as u32 };
let mut bytes = vec![0u8; len as usize];
unsafe {
ic0::msg_method_name_copy(bytes.as_mut_ptr() as i32, 0, len as i32);
}
String::from_utf8_lossy(&bytes).into_owned()
}
#[deprecated(
since = "0.11.3",
note = "This method conceptually doesn't belong to this module. Please use `ic_cdk::api::performance_counter` instead."
)]
pub fn performance_counter(counter_type: u32) -> u64 {
unsafe { ic0::performance_counter(counter_type as i32) as u64 }
}
#[derive(Debug, Copy, Clone, Default)]
pub struct ManualReply<T: ?Sized>(PhantomData<T>);
impl<T: ?Sized> ManualReply<T> {
#[allow(clippy::self_named_constructors)]
pub const fn empty() -> Self {
Self(PhantomData)
}
pub fn all<U>(value: U) -> Self
where
U: ArgumentEncoder,
{
reply(value);
Self::empty()
}
pub fn one<U>(value: U) -> Self
where
U: CandidType,
{
reply((value,));
Self::empty()
}
pub fn reject(message: impl AsRef<str>) -> Self {
reject(message.as_ref());
Self::empty()
}
}
impl<T> CandidType for ManualReply<T>
where
T: CandidType + ?Sized,
{
fn _ty() -> candid::types::Type {
T::_ty()
}
fn idl_serialize<S>(&self, _: S) -> Result<(), S::Error>
where
S: candid::types::Serializer,
{
Err(S::Error::custom("`Empty` cannot be serialized"))
}
}
pub fn is_recovering_from_trap() -> bool {
crate::futures::CLEANUP.load(Ordering::Relaxed)
}