canic_core/storage/stable/
intent.rs1use crate::{
8 cdk::structures::{
9 DefaultMemoryImpl, Storable, cell::Cell, memory::VirtualMemory, storable::Bound,
10 },
11 ids::{IntentId, IntentResourceKey},
12 storage::{
13 prelude::*,
14 stable::memory::intent::{
15 INTENT_META_ID, INTENT_PENDING_ID, INTENT_RECORDS_ID, INTENT_TOTALS_ID,
16 },
17 },
18};
19use ic_memory::stable_structures::btreemap::BTreeMap as StableBtreeMap;
20use std::{borrow::Cow, cell::RefCell};
21
22pub const INTENT_STORE_SCHEMA_VERSION: u32 = 1;
27
28eager_static! {
29 static INTENT_META: RefCell<Cell<IntentStoreMetaRecord, VirtualMemory<DefaultMemoryImpl>>> =
30 RefCell::new(Cell::init(
31 crate::ic_memory_key!("canic.core.intent_meta.v1", IntentStoreMetaRecord, INTENT_META_ID),
32 IntentStoreMetaRecord::default(),
33 ));
34}
35
36eager_static! {
37 static INTENT_RECORDS: RefCell<
38 StableBtreeMap<IntentId, IntentRecord, VirtualMemory<DefaultMemoryImpl>>
39 > = RefCell::new(
40 StableBtreeMap::init(crate::ic_memory_key!("canic.core.intent_records.v1", IntentRecord, INTENT_RECORDS_ID)),
41 );
42}
43
44eager_static! {
45 static INTENT_TOTALS: RefCell<
46 StableBtreeMap<IntentResourceKey, IntentResourceTotalsRecord, VirtualMemory<DefaultMemoryImpl>>
47 > = RefCell::new(
48 StableBtreeMap::init(crate::ic_memory_key!("canic.core.intent_totals.v1", IntentResourceTotalsRecord, INTENT_TOTALS_ID)),
49 );
50}
51
52eager_static! {
53 static INTENT_PENDING: RefCell<
54 StableBtreeMap<IntentId, IntentPendingEntryRecord, VirtualMemory<DefaultMemoryImpl>>
55 > = RefCell::new(
56 StableBtreeMap::init(crate::ic_memory_key!("canic.core.intent_pending.v1", IntentPendingEntryRecord, INTENT_PENDING_ID)),
57 );
58}
59
60impl Storable for IntentId {
61 const BOUND: Bound = Bound::Bounded {
62 max_size: 8,
63 is_fixed_size: true,
64 };
65
66 fn to_bytes(&self) -> Cow<'_, [u8]> {
67 Cow::Owned(self.0.to_be_bytes().to_vec())
68 }
69
70 fn into_bytes(self) -> Vec<u8> {
71 self.0.to_be_bytes().to_vec()
72 }
73
74 fn from_bytes(bytes: Cow<[u8]>) -> Self {
75 let b = bytes.as_ref();
76
77 if b.len() != 8 {
78 return Self::default();
79 }
80
81 let mut arr = [0u8; 8];
82 arr.copy_from_slice(b);
83
84 Self(u64::from_be_bytes(arr))
85 }
86}
87
88#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub enum IntentState {
94 Pending,
95 Committed,
96 Aborted,
97}
98
99#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
104pub struct IntentRecord {
105 pub id: IntentId,
106 pub resource_key: IntentResourceKey,
107 pub quantity: u64,
108 pub state: IntentState,
109 pub created_at: u64,
110 pub ttl_secs: Option<u64>,
112}
113
114impl IntentRecord {
115 pub const STORABLE_MAX_SIZE: u32 = 256;
116}
117
118impl_storable_bounded!(IntentRecord, IntentRecord::STORABLE_MAX_SIZE, false);
119
120#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
125pub struct IntentStoreMetaRecord {
126 pub schema_version: u32,
127 pub next_intent_id: IntentId,
128 pub pending_total: u64,
129 pub committed_total: u64,
130 pub aborted_total: u64,
131}
132
133impl IntentStoreMetaRecord {
134 pub const STORABLE_MAX_SIZE: u32 = 96;
135}
136
137impl Default for IntentStoreMetaRecord {
138 fn default() -> Self {
139 Self {
140 schema_version: INTENT_STORE_SCHEMA_VERSION,
141 next_intent_id: IntentId(1),
142 pending_total: 0,
143 committed_total: 0,
144 aborted_total: 0,
145 }
146 }
147}
148
149impl_storable_bounded!(
150 IntentStoreMetaRecord,
151 IntentStoreMetaRecord::STORABLE_MAX_SIZE,
152 false
153);
154
155#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
160pub struct IntentResourceTotalsRecord {
161 pub reserved_qty: u64,
162 pub committed_qty: u64,
163 pub pending_count: u64,
164}
165
166impl IntentResourceTotalsRecord {
167 pub const STORABLE_MAX_SIZE: u32 = 64;
168}
169
170impl_storable_bounded!(
171 IntentResourceTotalsRecord,
172 IntentResourceTotalsRecord::STORABLE_MAX_SIZE,
173 false
174);
175
176#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
181pub struct IntentPendingEntryRecord {
182 pub resource_key: IntentResourceKey,
183 pub quantity: u64,
184 pub created_at: u64,
185 pub ttl_secs: Option<u64>,
187}
188
189impl IntentPendingEntryRecord {
190 pub const STORABLE_MAX_SIZE: u32 = 224;
191}
192
193impl_storable_bounded!(
194 IntentPendingEntryRecord,
195 IntentPendingEntryRecord::STORABLE_MAX_SIZE,
196 false
197);
198
199pub struct IntentStore;
204
205impl IntentStore {
206 #[must_use]
211 pub(crate) fn meta() -> IntentStoreMetaRecord {
212 INTENT_META.with_borrow(|cell| *cell.get())
213 }
214
215 pub(crate) fn set_meta(meta: IntentStoreMetaRecord) {
216 INTENT_META.with_borrow_mut(|cell| cell.set(meta));
217 }
218
219 #[must_use]
224 pub(crate) fn get_record(id: IntentId) -> Option<IntentRecord> {
225 INTENT_RECORDS.with_borrow(|map| map.get(&id))
226 }
227
228 pub(crate) fn insert_record(record: IntentRecord) -> Option<IntentRecord> {
229 INTENT_RECORDS.with_borrow_mut(|map| map.insert(record.id, record))
230 }
231
232 #[must_use]
237 pub(crate) fn get_totals(key: &IntentResourceKey) -> Option<IntentResourceTotalsRecord> {
238 INTENT_TOTALS.with_borrow(|map| map.get(key))
239 }
240
241 pub(crate) fn set_totals(
242 key: IntentResourceKey,
243 totals: IntentResourceTotalsRecord,
244 ) -> Option<IntentResourceTotalsRecord> {
245 INTENT_TOTALS.with_borrow_mut(|map| map.insert(key, totals))
246 }
247
248 #[must_use]
253 pub(crate) fn get_pending(id: IntentId) -> Option<IntentPendingEntryRecord> {
254 INTENT_PENDING.with_borrow(|map| map.get(&id))
255 }
256
257 pub(crate) fn insert_pending(
258 id: IntentId,
259 entry: IntentPendingEntryRecord,
260 ) -> Option<IntentPendingEntryRecord> {
261 INTENT_PENDING.with_borrow_mut(|map| map.insert(id, entry))
262 }
263
264 pub(crate) fn remove_pending(id: IntentId) -> Option<IntentPendingEntryRecord> {
265 INTENT_PENDING.with_borrow_mut(|map| map.remove(&id))
266 }
267
268 pub(crate) fn with_pending_entries<R>(
269 f: impl FnOnce(
270 &StableBtreeMap<IntentId, IntentPendingEntryRecord, VirtualMemory<DefaultMemoryImpl>>,
271 ) -> R,
272 ) -> R {
273 INTENT_PENDING.with_borrow(|map| f(map))
274 }
275}
276
277#[cfg(test)]
284impl IntentStore {
285 pub(crate) fn reset_for_tests() {
286 INTENT_RECORDS.with_borrow_mut(StableBtreeMap::clear_new);
287 INTENT_TOTALS.with_borrow_mut(StableBtreeMap::clear_new);
288 INTENT_PENDING.with_borrow_mut(StableBtreeMap::clear_new);
289 INTENT_META.with_borrow_mut(|cell| cell.set(IntentStoreMetaRecord::default()));
290 }
291}