1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
use crate::futures;
use crate::futures::CallFuture;
use crate::ic::Cycles;
use crate::utils::arg_data_raw;
use candid::utils::{ArgumentDecoder, ArgumentEncoder};
use candid::{decode_args, decode_one, encode_args, encode_one, CandidType, Principal};
use ic_kit_sys::ic0;
use serde::de::DeserializeOwned;
pub use ic_kit_sys::types::{CallError, CANDID_EMPTY_ARG};
/// A call builder that let's you create an inter-canister call which can be then sent to the
/// destination.
pub struct CallBuilder {
canister_id: Principal,
method_name: String,
payment: Cycles,
arg: Option<Vec<u8>>,
}
impl CallBuilder {
/// Create a new call constructor, calling this method does nothing unless one of the perform
/// methods are called.
pub fn new<S: Into<String>>(canister_id: Principal, method_name: S) -> Self {
Self {
canister_id,
method_name: method_name.into(),
payment: 0,
arg: None,
}
}
/// Use the given candid tuple value as the argument.
///
/// # Panics
///
/// This method panics if the argument for this call is already set via a prior
/// call to any of the `with_args`, `with_arg` or `with_arg_raw`.
///
/// Use `clear_args` if you want to reset the arguments.
pub fn with_args<T: ArgumentEncoder>(mut self, arguments: T) -> Self {
assert!(self.arg.is_none(), "Call arguments can only be set once.");
self.arg = Some(encode_args(arguments).unwrap());
self
}
/// Shorthand for `with_args((argument, ))`.
///
/// # Panics
///
/// This method panics if the argument for this call is already set via a prior
/// call to any of the `with_args`, `with_arg` or `with_arg_raw`.
///
/// Use `clear_args` if you want to reset the arguments.
pub fn with_arg<T: CandidType>(mut self, argument: T) -> Self {
assert!(self.arg.is_none(), "Call arguments can only be set once.");
self.arg = Some(encode_one(argument).unwrap());
self
}
/// Set the raw argument that can be used for this call, this does not use candid to serialize
/// the call argument and uses the provided raw buffer as the argument.
///
/// Be sure that you know what you're doing when using this method.
///
/// # Panics
///
/// This method panics if the argument for this call is already set via a prior
/// call to any of the `with_args`, `with_arg` or `with_arg_raw`.
///
/// Use `clear_args` if you want to reset the arguments.
pub fn with_arg_raw<A: Into<Vec<u8>>>(mut self, argument: A) -> Self {
assert!(self.arg.is_none(), "Call arguments can only be set once.");
self.arg = Some(argument.into());
self
}
/// Clear any arguments set for this call. After calling this method you can call with_arg*
/// methods again without the panic.
pub fn clear_args(&mut self) {
self.arg = None;
}
/// Set the payment amount for the canister. This will overwrite any previously added cycles
/// to this call, use `add_payment` if you want to increment the amount of used cycles in
/// this call.
///
/// # Safety
///
/// Be sure that your canister has the provided amount of cycles upon performing the call,
/// since any of the perform methods will just trap the canister if the provided payment
/// amount is larger than the amount of canister's balance.
pub fn with_payment(mut self, payment: Cycles) -> Self {
self.payment = payment;
self
}
/// Add the given provided amount of cycles to the cycles already provided to this call.
pub fn add_payment(mut self, payment: Cycles) -> Self {
self.payment += payment;
self
}
/// Should be called after the `ic0::call_new` to set the call arguments.
#[inline(always)]
unsafe fn ic0_internal_call_perform(&self) -> i32 {
#[cfg(not(feature = "experimental-cycles128"))]
ic0::call_cycles_add(self.payment as i64);
#[cfg(feature = "experimental-cycles128")]
if self.payment > 0 && self.payment < (u64::MAX as u128) {
ic0::call_cycles_add(self.payment as i64);
} else if self.payment > 0 {
let high = (self.payment >> 64) as u64 as i64;
let low = (self.payment & (1 << 64)) as u64 as i64;
ic0::call_cycles_add128(high, low);
}
let args_raw = self.arg.as_deref().unwrap_or(CANDID_EMPTY_ARG);
if !args_raw.is_empty() {
ic0::call_data_append(args_raw.as_ptr() as isize, args_raw.len() as isize);
}
ic0::call_perform()
}
/// Perform a call when you do not care about the response in anyway. We advise you to use this
/// method when you can since it is probably cheaper.
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
pub fn perform_one_way(self) {
let callee = self.canister_id.as_slice();
let method = self.method_name.as_str();
unsafe {
ic0::call_new(
callee.as_ptr() as isize,
callee.len() as isize,
method.as_ptr() as isize,
method.len() as isize,
-1,
-1,
-1,
-1,
);
self.ic0_internal_call_perform();
}
}
/// Perform the call and return a future that can will be resolved in any of the callbacks.
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
#[must_use]
fn perform_internal(&self) -> CallFuture {
let future = unsafe {
let future = futures::call_new(self.canister_id, self.method_name.as_str());
let e_code = self.ic0_internal_call_perform();
if e_code != 0 {
future.mark_ready()
} else {
future
}
};
future
}
/// Use this method when you want to perform a call and only care about the delivery status
/// of the call and don't need the returned buffer in anyway.
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
pub async fn perform_rejection(&self) -> Result<(), CallError> {
let future = self.perform_internal();
// if the future is already ready, it indicates a `ic0::call_perform` non-zero response.
if future.is_ready() {
return Err(CallError::CouldNotSend);
}
// await for the call to comeback.
future.await;
let rejection_code = unsafe { ic0::msg_reject_code() };
if rejection_code == 0 {
return Ok(());
}
let rejection_message_size = unsafe { ic0::msg_reject_msg_size() } as usize;
let mut bytes = vec![0u8; rejection_message_size];
unsafe {
ic0::msg_reject_msg_copy(
bytes.as_mut_ptr() as isize,
0,
rejection_message_size as isize,
);
}
Err(CallError::Rejected(
rejection_code.into(),
String::from_utf8_lossy(&bytes).to_string(),
))
}
/// Perform the call and return the raw response buffer without decoding it.
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
pub async fn perform_raw(&self) -> Result<Vec<u8>, CallError> {
self.perform_rejection().await?;
Ok(arg_data_raw())
}
/// Perform the call and return a future which will resolve to the candid decoded response. Or
/// any of the errors that might happen, consider looking at other alternatives of this method
/// as well if you don't care about the response or want the raw/non-decoded response.
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
pub async fn perform<R: for<'a> ArgumentDecoder<'a>>(&self) -> Result<R, CallError> {
let bytes = self.perform_raw().await?;
match decode_args(&bytes) {
Err(_) => Err(CallError::ResponseDeserializationError(bytes)),
Ok(r) => Ok(r),
}
}
/// Perform the call and return a future which will resolve to the candid decoded response.
/// Unlink perform, this method only expects a result with one argument from the canister,
/// and decodes the arguments using the candid's decode_one.
///
///
/// # Traps
///
/// This method traps if the amount determined in the `payment` is larger than the canister's
/// balance at the time of invocation.
pub async fn perform_one<T>(&self) -> Result<T, CallError>
where
T: DeserializeOwned + CandidType,
{
let bytes = self.perform_raw().await?;
match decode_one(&bytes) {
Err(_) => Err(CallError::ResponseDeserializationError(bytes)),
Ok(r) => Ok(r),
}
}
}