use crate::api::trap;
use candid::utils::{ArgumentDecoder, ArgumentEncoder};
use candid::{decode_args, encode_args, write_args, CandidType, 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::task::{Context, Poll, Waker};
#[cfg(all(
target_arch = "wasm32-unknown-unknown",
not(target_feature = "atomics")
))]
#[allow(dead_code)]
mod rc {
use std::cell::{RefCell, RefMut};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
pub(crate) type InnerCell<T> = RefCell<T>;
pub(crate) struct WasmCell<T>(Rc<InnerCell<T>>);
unsafe impl<T> Send for WasmCell<T> {}
unsafe impl<T> Sync for WasmCell<T> {}
impl<T> WasmCell<T> {
pub fn new(val: T) -> Self {
WasmCell(Rc::new(InnerCell::new(val)))
}
pub fn into_raw(self) -> *const InnerCell<T> {
Rc::into_raw(self.0)
}
pub unsafe fn from_raw(ptr: *const InnerCell<T>) -> Self {
Self(Rc::from_raw(ptr))
}
pub fn borrow_mut(&self) -> RefMut<'_, T> {
self.0.borrow_mut()
}
pub fn as_ptr(&self) -> *const InnerCell<T> {
self.0.as_ptr() as *const _
}
}
impl<O, T: Future<Output = O>> Future for WasmCell<T> {
type Output = O;
#[allow(unused_mut)]
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { Pin::new_unchecked(&mut *self.0.borrow_mut()) }.poll(ctx)
}
}
impl<T> Clone for WasmCell<T> {
fn clone(&self) -> Self {
WasmCell(Rc::clone(&self.0))
}
}
}
#[cfg(not(target_arch = "wasm32-unknown-unknown"))]
#[allow(dead_code)]
mod rc {
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll};
pub(crate) type InnerCell<T> = Mutex<T>;
pub(crate) struct WasmCell<T>(Arc<InnerCell<T>>);
impl<T> WasmCell<T> {
pub fn new(val: T) -> Self {
WasmCell(Arc::new(InnerCell::new(val)))
}
pub fn into_raw(self) -> *const InnerCell<T> {
Arc::into_raw(self.0)
}
pub unsafe fn from_raw(ptr: *const InnerCell<T>) -> Self {
Self(unsafe { Arc::from_raw(ptr) })
}
pub fn borrow_mut(&self) -> MutexGuard<'_, T> {
self.0.lock().unwrap()
}
pub fn as_ptr(&self) -> *const InnerCell<T> {
Arc::<_>::as_ptr(&self.0)
}
}
impl<O, T: Future<Output = O>> Future for WasmCell<T> {
type Output = O;
#[allow(unused_mut)]
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe { Pin::new_unchecked(&mut *self.0.lock().unwrap()) }.poll(ctx)
}
}
impl<T> Clone for WasmCell<T> {
fn clone(&self) -> Self {
WasmCell(Arc::clone(&self.0))
}
}
}
use rc::{InnerCell, WasmCell};
#[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<R: serde::de::DeserializeOwned> {
result: Option<CallResult<R>>,
waker: Option<Waker>,
}
struct CallFuture<R: serde::de::DeserializeOwned> {
state: rc::WasmCell<CallFutureState<R>>,
}
impl<R: serde::de::DeserializeOwned> Future for CallFuture<R> {
type Output = Result<R, (RejectionCode, String)>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let self_ref = Pin::into_ref(self);
let mut state = self_ref.state.borrow_mut();
if let Some(result) = state.result.take() {
Poll::Ready(result)
} else {
state.waker = Some(context.waker().clone());
Poll::Pending
}
}
}
unsafe fn callback(state_ptr: *const InnerCell<CallFutureState<Vec<u8>>>) {
let state = unsafe { WasmCell::from_raw(state_ptr) };
{
state.borrow_mut().result = Some(match reject_code() {
RejectionCode::NoError => Ok(arg_data_raw()),
n => Err((n, reject_message())),
});
}
let w = state.borrow_mut().waker.take();
if let Some(waker) = w {
waker.wake()
}
}
unsafe fn cleanup(state_ptr: *const InnerCell<CallFutureState<Vec<u8>>>) {
let state = unsafe { WasmCell::from_raw(state_ptr) };
state.borrow_mut().result = Some(Err((RejectionCode::NoError, "cleanup".to_string())));
let w = state.borrow_mut().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(
id: Principal,
method: &str,
args_raw: &[u8],
payment: u64,
) -> impl Future<Output = CallResult<Vec<u8>>> {
call_raw_internal(id, method, args_raw, move || {
if payment > 0 {
unsafe {
ic0::call_cycles_add(payment as i64);
}
}
})
}
pub fn call_raw128(
id: Principal,
method: &str,
args_raw: &[u8],
payment: u128,
) -> impl Future<Output = CallResult<Vec<u8>>> {
call_raw_internal(id, method, args_raw, move || {
add_payment(payment);
})
}
fn call_raw_internal(
id: Principal,
method: &str,
args_raw: &[u8],
payment_func: impl FnOnce(),
) -> impl Future<Output = CallResult<Vec<u8>>> {
let callee = id.as_slice();
let state = WasmCell::new(CallFutureState {
result: None,
waker: None,
});
let state_ptr = WasmCell::into_raw(state.clone());
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 as usize as i32,
state_ptr as i32,
callback as usize as i32,
state_ptr as i32,
);
ic0::call_data_append(args_raw.as_ptr() as i32, args_raw.len() as i32);
payment_func();
ic0::call_on_cleanup(cleanup as usize as i32, state_ptr as i32);
ic0::call_perform()
};
if err_code != 0 {
let mut state = state.borrow_mut();
state.result = Some(Err((
RejectionCode::from(err_code),
"Couldn't send message".to_string(),
)));
}
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>> {
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::<T>)
}
}
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>> {
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::<T>)
}
}
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>> {
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::<T>)
}
}
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);
}
}
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() };
}
pub fn arg_data<R: for<'a> ArgumentDecoder<'a>>() -> R {
let bytes = arg_data_raw();
match decode_args(&bytes) {
Err(e) => trap(&format!("failed to decode call arguments: {:?}", e)),
Ok(r) => 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()
}
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"))
}
}