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