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