use crate::{
cdk::structures::{
BTreeMap, DefaultMemoryImpl, Storable, cell::Cell, memory::VirtualMemory, storable::Bound,
},
ids::IntentResourceKey,
storage::{
prelude::*,
stable::memory::intent::{
INTENT_META_ID, INTENT_PENDING_ID, INTENT_RECORDS_ID, INTENT_TOTALS_ID,
},
},
};
use std::{
borrow::Cow,
cell::RefCell,
fmt::{self, Display},
};
pub const INTENT_STORE_SCHEMA_VERSION: u32 = 1;
eager_static! {
static INTENT_META: RefCell<Cell<IntentStoreMetaRecord, VirtualMemory<DefaultMemoryImpl>>> =
RefCell::new(Cell::init(
ic_memory!(IntentStoreMetaRecord, INTENT_META_ID),
IntentStoreMetaRecord::default(),
));
}
eager_static! {
static INTENT_RECORDS: RefCell<
BTreeMap<IntentId, IntentRecord, VirtualMemory<DefaultMemoryImpl>>
> = RefCell::new(
BTreeMap::init(ic_memory!(IntentRecord, INTENT_RECORDS_ID)),
);
}
eager_static! {
static INTENT_TOTALS: RefCell<
BTreeMap<IntentResourceKey, IntentResourceTotalsRecord, VirtualMemory<DefaultMemoryImpl>>
> = RefCell::new(
BTreeMap::init(ic_memory!(IntentResourceTotalsRecord, INTENT_TOTALS_ID)),
);
}
eager_static! {
static INTENT_PENDING: RefCell<
BTreeMap<IntentId, IntentPendingEntryRecord, VirtualMemory<DefaultMemoryImpl>>
> = RefCell::new(
BTreeMap::init(ic_memory!(IntentPendingEntryRecord, INTENT_PENDING_ID)),
);
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct IntentId(pub u64);
impl Storable for IntentId {
const BOUND: Bound = Bound::Bounded {
max_size: 8,
is_fixed_size: true,
};
fn to_bytes(&self) -> Cow<'_, [u8]> {
Cow::Owned(self.0.to_be_bytes().to_vec())
}
fn into_bytes(self) -> Vec<u8> {
self.0.to_be_bytes().to_vec()
}
fn from_bytes(bytes: Cow<[u8]>) -> Self {
let b = bytes.as_ref();
if b.len() != 8 {
return Self::default();
}
let mut arr = [0u8; 8];
arr.copy_from_slice(b);
Self(u64::from_be_bytes(arr))
}
}
impl Display for IntentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum IntentState {
Pending,
Committed,
Aborted,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IntentRecord {
pub id: IntentId,
pub resource_key: IntentResourceKey,
pub quantity: u64,
pub state: IntentState,
pub created_at: u64,
pub ttl_secs: Option<u64>,
}
impl IntentRecord {
pub const STORABLE_MAX_SIZE: u32 = 256;
}
impl_storable_bounded!(IntentRecord, IntentRecord::STORABLE_MAX_SIZE, false);
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IntentStoreMetaRecord {
pub schema_version: u32,
pub next_intent_id: IntentId,
pub pending_total: u64,
pub committed_total: u64,
pub aborted_total: u64,
}
impl IntentStoreMetaRecord {
pub const STORABLE_MAX_SIZE: u32 = 96;
}
impl Default for IntentStoreMetaRecord {
fn default() -> Self {
Self {
schema_version: INTENT_STORE_SCHEMA_VERSION,
next_intent_id: IntentId(1),
pending_total: 0,
committed_total: 0,
aborted_total: 0,
}
}
}
impl_storable_bounded!(
IntentStoreMetaRecord,
IntentStoreMetaRecord::STORABLE_MAX_SIZE,
false
);
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct IntentResourceTotalsRecord {
pub reserved_qty: u64,
pub committed_qty: u64,
pub pending_count: u64,
}
impl IntentResourceTotalsRecord {
pub const STORABLE_MAX_SIZE: u32 = 64;
}
impl_storable_bounded!(
IntentResourceTotalsRecord,
IntentResourceTotalsRecord::STORABLE_MAX_SIZE,
false
);
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IntentPendingEntryRecord {
pub resource_key: IntentResourceKey,
pub quantity: u64,
pub created_at: u64,
pub ttl_secs: Option<u64>,
}
impl IntentPendingEntryRecord {
pub const STORABLE_MAX_SIZE: u32 = 224;
}
impl_storable_bounded!(
IntentPendingEntryRecord,
IntentPendingEntryRecord::STORABLE_MAX_SIZE,
false
);
pub struct IntentStore;
impl IntentStore {
#[must_use]
pub(crate) fn meta() -> IntentStoreMetaRecord {
INTENT_META.with_borrow(|cell| *cell.get())
}
pub(crate) fn set_meta(meta: IntentStoreMetaRecord) {
INTENT_META.with_borrow_mut(|cell| cell.set(meta));
}
#[must_use]
pub(crate) fn get_record(id: IntentId) -> Option<IntentRecord> {
INTENT_RECORDS.with_borrow(|map| map.get(&id))
}
pub(crate) fn insert_record(record: IntentRecord) -> Option<IntentRecord> {
INTENT_RECORDS.with_borrow_mut(|map| map.insert(record.id, record))
}
#[must_use]
pub(crate) fn get_totals(key: &IntentResourceKey) -> Option<IntentResourceTotalsRecord> {
INTENT_TOTALS.with_borrow(|map| map.get(key))
}
pub(crate) fn set_totals(
key: IntentResourceKey,
totals: IntentResourceTotalsRecord,
) -> Option<IntentResourceTotalsRecord> {
INTENT_TOTALS.with_borrow_mut(|map| map.insert(key, totals))
}
#[must_use]
pub(crate) fn get_pending(id: IntentId) -> Option<IntentPendingEntryRecord> {
INTENT_PENDING.with_borrow(|map| map.get(&id))
}
pub(crate) fn insert_pending(
id: IntentId,
entry: IntentPendingEntryRecord,
) -> Option<IntentPendingEntryRecord> {
INTENT_PENDING.with_borrow_mut(|map| map.insert(id, entry))
}
pub(crate) fn remove_pending(id: IntentId) -> Option<IntentPendingEntryRecord> {
INTENT_PENDING.with_borrow_mut(|map| map.remove(&id))
}
pub(crate) fn with_pending_entries<R>(
f: impl FnOnce(
&BTreeMap<IntentId, IntentPendingEntryRecord, VirtualMemory<DefaultMemoryImpl>>,
) -> R,
) -> R {
INTENT_PENDING.with_borrow(|map| f(map))
}
}
#[cfg(test)]
impl IntentStore {
pub(crate) fn reset_for_tests() {
INTENT_RECORDS.with_borrow_mut(BTreeMap::clear);
INTENT_TOTALS.with_borrow_mut(BTreeMap::clear);
INTENT_PENDING.with_borrow_mut(BTreeMap::clear);
INTENT_META.with_borrow_mut(|cell| cell.set(IntentStoreMetaRecord::default()));
}
}