Skip to main content

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}