thruput_sdk/storage/nnint.rs
1use super::keypath::{Keypath, LeafKey};
2use crate::exn::nnint_overflow;
3use crate::sys_exn::nnint::*;
4use crate::sys_exn::nnint_internal as nnint;
5use core::fmt::Debug;
6
7/// A stored integer with a **commit-time floor**, not a call-time
8/// non-negativity invariant.
9///
10/// The value is `set + add`: `set` is the last explicit [`set`](Self::set) (or
11/// the value carried from the previous transaction) and `add` is what
12/// [`inc`](Self::inc) has accumulated this transaction. The storage layer
13/// enforces, at commit, that `set + add >= min(set, 0)`:
14///
15/// - With a non-negative (or never-)`set`, the floor is `0`: the value can't be
16/// driven below zero, and a transaction that would commit a negative value
17/// reverts.
18/// - You may [`set`](Self::set) a *negative* value; the floor becomes that
19/// value. The counter can hold it, but increments still can't take it any
20/// lower than the value you set.
21///
22/// Crucially, for [`inc`](Self::inc) this is a *commit-time* constraint, not a
23/// call-time error. For a hard, call-time "never negative" check with a clear
24/// error (e.g. an ERC20 debit), use [`strict_inc`](Self::strict_inc) /
25/// [`try_strict_inc`](Self::try_strict_inc). The exact rules (and concurrency
26/// constraints) are on the `non_negative_int_*` free functions in
27/// `sys_exn::nnint`.
28#[derive(Debug)]
29pub struct NNInt {
30 key: LeafKey,
31}
32
33impl From<Keypath> for NNInt {
34 fn from(keypath: Keypath) -> Self {
35 Self {
36 key: LeafKey::from(keypath),
37 }
38 }
39}
40
41impl NNInt {
42 /// Get the current value. Aborts if location not yet set.
43 pub fn get(&self) -> i128 {
44 non_negative_int_get(self.key.get())
45 }
46
47 /// Get the current value, reporting failure as `Err` instead of
48 /// aborting (the `try_` law; see the [`exn`](mod@crate::exn) module
49 /// docs). A not-yet-set location is an `Err` for which
50 /// [`is_storage_missing`](crate::exn::LibraryExn::is_storage_missing)
51 /// is true — use
52 /// [`none_if_missing`](crate::exn::NoneIfMissing::none_if_missing) to
53 /// treat just that case as an ordinary `None`. See
54 /// [`non_negative_int_try_get`] for the full error contract.
55 pub fn try_get(&self) -> Result<i128, crate::exn::LibraryExn> {
56 non_negative_int_try_get(self.key.get())
57 }
58
59 /// Set the value, clearing accumulated [`inc`](Self::inc)s this transaction.
60 ///
61 /// A negative `set_value` is allowed: the commit-time floor becomes that
62 /// value, so the counter can hold it but increments still can't take it any
63 /// lower. A non-negative `set_value` keeps the floor at `0`. (At commit,
64 /// `value >= min(set_value, 0)` must hold.) See the [type docs](NNInt).
65 pub fn set(&mut self, set_value: i128) {
66 nnint::set_add(
67 self.key.get(),
68 Some(set_value),
69 0,
70 self.key.is_exclusive(),
71 );
72 }
73
74 /// Increment by an amount (without querying the current value).
75 ///
76 /// The floor is enforced at *commit*, not here: the transaction reverts if
77 /// the committed value would fall below `min(set, 0)` (see the
78 /// [type docs](NNInt)). For a call-time non-negativity check, use
79 /// [`strict_inc`](Self::strict_inc). Throws on overflow.
80 pub fn inc(&mut self, increment: i128) {
81 let key = self.key.get();
82 let (s, a) = nnint::get_values(key);
83 let add_value =
84 a.checked_add(increment).unwrap_or_else(|| nnint_overflow());
85 nnint::set_add(key, s, add_value, self.key.is_exclusive());
86 }
87
88 /// Increment by an amount, throwing in case of a negative result.
89 pub fn strict_inc(&mut self, increment: i128) {
90 use crate::re_throw::OrReThrow;
91 self.try_strict_inc(increment).or_re_throw()
92 }
93
94 /// Increment by an amount, like [`NNInt::strict_inc`], but report
95 /// failure to the caller instead of aborting (the `try_` law; see the
96 /// [`exn`](mod@crate::exn) module docs), so a contract can raise a
97 /// domain-specific exception. On `Err` the stored value is unmodified;
98 /// no extra storage roundtrip over [`NNInt::inc`]. The domain errors
99 /// are `NonNegativeIntOverflow`/`NonNegativeIntUnderflow`; a wrong
100 /// storage type at the key or any other syscall failure surfaces as
101 /// `LibraryExn::Syscall`.
102 pub fn try_strict_inc(
103 &mut self,
104 increment: i128,
105 ) -> Result<(), crate::exn::LibraryExn> {
106 use crate::exn::LibraryExn;
107
108 let key = self.key.get();
109 let (s, a) = match nnint::try_get_values_force(key) {
110 Ok(values) => values,
111 // A never-written location increments as if from zero.
112 Err(e) if e.is_storage_missing() => (0, 0),
113 Err(e) => return Err(e),
114 };
115
116 let add_value = a
117 .checked_add(increment)
118 .ok_or(LibraryExn::NonNegativeIntOverflow)?;
119 let total = s
120 .checked_add(add_value)
121 .ok_or(LibraryExn::NonNegativeIntOverflow)?;
122
123 if total < 0 {
124 return Err(LibraryExn::NonNegativeIntUnderflow);
125 }
126
127 nnint::try_set_add(key, Some(s), add_value, self.key.is_exclusive())
128 }
129}
130
131impl super::sealed::Sealed for NNInt {}
132impl super::node::StorageType for NNInt {}
133
134impl super::flags::Flaggable for NNInt {
135 fn storage_key(&self) -> &crate::types::StorageKey {
136 self.key.get()
137 }
138}
139
140impl super::deletable::Deletable for NNInt {
141 fn delete_internal(&mut self, exclusive: bool) {
142 let exclusive = exclusive || self.key.is_exclusive();
143 crate::sys_exn::delete::delete(self.key.get(), exclusive);
144 }
145}