use candid::utils::ArgumentEncoder;
use candid::{encode_args, encode_one, CandidType};
use ic_kit_sys::types::{RejectionCode, CANDID_EMPTY_ARG};
use ic_types::Principal;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static REQUEST_ID: AtomicU64 = AtomicU64::new(0);
pub type IncomingRequestId = RequestId;
pub type OutgoingRequestId = RequestId;
#[derive(Hash, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct RequestId(u64);
impl RequestId {
pub fn new() -> Self {
Self(REQUEST_ID.fetch_add(1, Ordering::SeqCst))
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum EntryMode {
Init,
PreUpgrade,
PostUpgrade,
Heartbeat,
InspectMessage,
Update,
Query,
ReplyCallback,
RejectCallback,
CleanupCallback,
CustomTask,
}
pub struct Env {
pub balance: u128,
pub entry_mode: EntryMode,
pub sender: Principal,
pub method_name: Option<String>,
pub cycles_available: u128,
pub cycles_refunded: u128,
pub args: Vec<u8>,
pub rejection_code: RejectionCode,
pub rejection_message: String,
pub time: u64,
}
pub type TaskFn = Box<dyn FnOnce() + Send + RefUnwindSafe + UnwindSafe>;
pub enum Message {
CustomTask {
request_id: IncomingRequestId,
task: TaskFn,
env: Env,
},
Request {
request_id: IncomingRequestId,
env: Env,
},
Reply {
reply_to: OutgoingRequestId,
env: Env,
},
}
#[derive(Debug)]
pub struct CanisterCall {
pub sender: Principal,
pub request_id: RequestId,
pub callee: Principal,
pub method: String,
pub payment: u128,
pub arg: Vec<u8>,
}
impl From<CanisterCall> for Message {
fn from(call: CanisterCall) -> Self {
Message::Request {
request_id: call.request_id,
env: Env::default()
.with_entry_mode(EntryMode::Update)
.with_sender(call.sender)
.with_method_name(call.method)
.with_cycles_available(call.payment)
.with_raw_args(call.arg),
}
}
}
impl Default for Env {
fn default() -> Self {
Env {
balance: 100_000_000_000_000,
entry_mode: EntryMode::CustomTask,
sender: Principal::anonymous(),
method_name: None,
cycles_available: 0,
cycles_refunded: 0,
args: CANDID_EMPTY_ARG.to_vec(),
rejection_code: RejectionCode::NoError,
rejection_message: String::new(),
time: now(),
}
}
}
impl Env {
pub fn update<S: Into<String>>(method_name: S) -> Self {
Self::default()
.with_entry_mode(EntryMode::Update)
.with_method_name(method_name)
}
pub fn query<S: Into<String>>(method_name: S) -> Self {
Self::default()
.with_entry_mode(EntryMode::Query)
.with_method_name(method_name)
}
pub fn init() -> Self {
Self::default().with_entry_mode(EntryMode::Init)
}
pub fn pre_upgrade() -> Self {
Self::default().with_entry_mode(EntryMode::PreUpgrade)
}
pub fn post_upgrade() -> Self {
Self::default().with_entry_mode(EntryMode::PostUpgrade)
}
pub fn heartbeat() -> Self {
Self::default().with_entry_mode(EntryMode::Heartbeat)
}
pub fn with_balance(mut self, balance: u128) -> Self {
self.balance = balance;
self
}
pub fn with_time(mut self, time: u64) -> Self {
self.time = time;
self
}
pub fn with_entry_mode(mut self, mode: EntryMode) -> Self {
self.entry_mode = mode;
self
}
pub fn with_sender(mut self, sender: Principal) -> Self {
self.sender = sender;
self
}
pub fn with_method_name<S: Into<String>>(mut self, method_name: S) -> Self {
self.method_name = Some(method_name.into());
self
}
pub fn with_cycles_available(mut self, cycles: u128) -> Self {
self.cycles_available = cycles;
self
}
pub fn with_cycles_refunded(mut self, cycles: u128) -> Self {
self.cycles_refunded = cycles;
self
}
pub fn with_raw_args<A: Into<Vec<u8>>>(mut self, argument: A) -> Self {
self.args = argument.into();
self
}
pub fn with_args<T: ArgumentEncoder>(mut self, arguments: T) -> Self {
self.args = encode_args(arguments).unwrap();
self
}
pub fn with_arg<T: CandidType>(mut self, argument: T) -> Self {
self.args = encode_one(argument).unwrap();
self
}
pub fn with_rejection_code(mut self, rejection_code: RejectionCode) -> Self {
self.rejection_code = rejection_code;
self
}
pub fn with_rejection_message<S: Into<String>>(mut self, rejection_message: S) -> Self {
self.rejection_message = rejection_message.into();
self
}
}
impl Env {
pub fn get_entry_point_name(&self) -> String {
match &self.entry_mode {
EntryMode::Init => "canister_init".to_string(),
EntryMode::PreUpgrade => "canister_pre_upgrade".to_string(),
EntryMode::PostUpgrade => "canister_post_upgrade".to_string(),
EntryMode::Heartbeat => "canister_heartbeat".to_string(),
EntryMode::InspectMessage => "canister_inspect_message".to_string(),
EntryMode::Update => {
format!(
"canister_update {}",
self.method_name.as_ref().unwrap_or(&String::new())
)
}
EntryMode::Query => format!(
"canister_query {}",
self.method_name.as_ref().unwrap_or(&String::new())
),
EntryMode::ReplyCallback => "reply callback".to_string(),
EntryMode::RejectCallback => "reject callback".to_string(),
EntryMode::CleanupCallback => "cleanup callback".to_string(),
EntryMode::CustomTask => "ic-kit: custom".to_string(),
}
}
pub fn get_possible_entry_point_name(&self) -> String {
match &self.entry_mode {
EntryMode::Update => {
format!(
"canister_query {}",
self.method_name.as_ref().unwrap_or(&String::new())
)
}
EntryMode::Query => format!(
"canister_update {}",
self.method_name.as_ref().unwrap_or(&String::new())
),
_ => self.get_entry_point_name(),
}
}
}
fn now() -> u64 {
let now = SystemTime::now();
let unix = now
.duration_since(UNIX_EPOCH)
.expect("ic-kit-runtime: could not retrieve unix time.");
unix.as_nanos() as u64
}