defuse_near_utils/
lock.rs

1use std::io;
2
3use defuse_borsh_utils::adapters::{AsWrap, BorshDeserializeAs, BorshSerializeAs};
4use near_sdk::{
5    borsh::{BorshDeserialize, BorshSerialize},
6    near,
7};
8
9/// A persistent lock, which stores its state (whether it's locked or unlocked)
10/// on-chain, so that the inner value can be accessed depending on
11/// the current state of the lock.
12#[derive(Debug, Default, PartialEq, Eq)]
13#[near(serializers = [borsh, json])]
14pub struct Lock<T> {
15    #[serde(
16        default,
17        // do not serialize `false`
18        skip_serializing_if = "::core::ops::Not::not"
19    )]
20    locked: bool,
21    #[serde(flatten)]
22    value: T,
23}
24
25impl<T> Lock<T> {
26    #[must_use]
27    #[inline]
28    pub const fn new(locked: bool, value: T) -> Self {
29        Self { locked, value }
30    }
31
32    #[must_use]
33    #[inline]
34    pub const fn unlocked(value: T) -> Self {
35        Self::new(false, value)
36    }
37
38    #[must_use]
39    #[inline]
40    pub const fn locked(value: T) -> Self {
41        Self::new(true, value)
42    }
43
44    #[inline]
45    pub const fn set_locked(&mut self, locked: bool) -> &mut Self {
46        self.locked = locked;
47        self
48    }
49
50    /// # Safety
51    /// This method bypasses lock state checks. Use only when you need to access
52    /// the inner value regardless of lock state, such as for read operations
53    /// or when implementing higher-level locking logic.
54    #[inline]
55    pub const fn as_inner_unchecked(&self) -> &T {
56        &self.value
57    }
58
59    /// # Safety
60    /// This method bypasses lock state checks. Use only when you need mutable access
61    /// to the inner value regardless of lock state. Misuse can compromise locking semantics.
62    #[inline]
63    pub const fn as_inner_unchecked_mut(&mut self) -> &mut T {
64        &mut self.value
65    }
66
67    #[inline]
68    pub fn into_inner_unchecked(self) -> T {
69        self.value
70    }
71
72    #[must_use]
73    #[inline]
74    pub const fn is_locked(&self) -> bool {
75        self.locked
76    }
77
78    #[must_use]
79    #[inline]
80    pub const fn as_locked(&self) -> Option<&T> {
81        if !self.is_locked() {
82            return None;
83        }
84        Some(self.as_inner_unchecked())
85    }
86
87    #[must_use]
88    #[inline]
89    pub const fn as_locked_mut(&mut self) -> Option<&mut T> {
90        if !self.is_locked() {
91            return None;
92        }
93        Some(self.as_inner_unchecked_mut())
94    }
95
96    #[must_use]
97    #[inline]
98    pub const fn as_locked_mut_maybe_forced(&mut self, force: bool) -> Option<&mut T> {
99        if force {
100            Some(self.as_inner_unchecked_mut())
101        } else {
102            self.as_locked_mut()
103        }
104    }
105
106    #[must_use]
107    #[inline]
108    pub fn into_locked(self) -> Option<T> {
109        if !self.is_locked() {
110            return None;
111        }
112        Some(self.value)
113    }
114
115    #[must_use]
116    #[inline]
117    pub const fn lock(&mut self) -> Option<&mut T> {
118        if self.is_locked() {
119            return None;
120        }
121        self.locked = true;
122        Some(self.as_inner_unchecked_mut())
123    }
124
125    #[inline]
126    pub const fn force_lock(&mut self) -> &mut T {
127        self.locked = true;
128        self.as_inner_unchecked_mut()
129    }
130
131    #[must_use]
132    #[inline]
133    pub const fn get(&self) -> Option<&T> {
134        if self.is_locked() {
135            return None;
136        }
137        Some(self.as_inner_unchecked())
138    }
139
140    #[must_use]
141    #[inline]
142    pub const fn get_mut(&mut self) -> Option<&mut T> {
143        if self.is_locked() {
144            return None;
145        }
146        Some(self.as_inner_unchecked_mut())
147    }
148
149    #[must_use]
150    #[inline]
151    pub const fn get_mut_maybe_forced(&mut self, force: bool) -> Option<&mut T> {
152        if force {
153            Some(self.as_inner_unchecked_mut())
154        } else {
155            self.get_mut()
156        }
157    }
158
159    #[must_use]
160    #[inline]
161    pub fn into_unlocked(self) -> Option<T> {
162        if self.is_locked() {
163            return None;
164        }
165        Some(self.value)
166    }
167
168    #[must_use]
169    #[inline]
170    pub const fn unlock(&mut self) -> Option<&mut T> {
171        if !self.is_locked() {
172            return None;
173        }
174        self.locked = false;
175        Some(self.as_inner_unchecked_mut())
176    }
177
178    #[inline]
179    pub const fn force_unlock(&mut self) -> &mut T {
180        self.locked = false;
181        self.as_inner_unchecked_mut()
182    }
183
184    #[inline]
185    pub const fn as_ref(&self) -> Lock<&T> {
186        Lock::new(self.is_locked(), self.as_inner_unchecked())
187    }
188
189    #[inline]
190    pub const fn as_mut(&mut self) -> Lock<&mut T> {
191        Lock::new(self.is_locked(), self.as_inner_unchecked_mut())
192    }
193
194    #[inline]
195    pub fn map_inner_unchecked<U, F>(self, f: F) -> Lock<U>
196    where
197        F: FnOnce(T) -> U,
198    {
199        Lock::new(self.is_locked(), f(self.into_inner_unchecked()))
200    }
201}
202
203impl<T> From<T> for Lock<T> {
204    #[inline]
205    fn from(value: T) -> Self {
206        Self::unlocked(value)
207    }
208}
209
210impl<T, As> BorshSerializeAs<Lock<T>> for Lock<As>
211where
212    As: BorshSerializeAs<T>,
213{
214    #[inline]
215    fn serialize_as<W>(source: &Lock<T>, writer: &mut W) -> io::Result<()>
216    where
217        W: io::Write,
218    {
219        Lock {
220            locked: source.locked,
221            value: AsWrap::<&T, &As>::new(&source.value),
222        }
223        .serialize(writer)
224    }
225}
226
227impl<T, As> BorshDeserializeAs<Lock<T>> for Lock<As>
228where
229    As: BorshDeserializeAs<T>,
230{
231    #[inline]
232    fn deserialize_as<R>(reader: &mut R) -> io::Result<Lock<T>>
233    where
234        R: io::Read,
235    {
236        Lock::<AsWrap<T, As>>::deserialize_reader(reader).map(|v| Lock {
237            locked: v.locked,
238            value: v.value.into_inner(),
239        })
240    }
241}
242
243#[cfg(test)]
244#[test]
245fn test() {
246    let mut a = Lock::new(false, 0);
247
248    assert!(!a.is_locked());
249    assert_eq!(a.unlock(), None);
250
251    assert_eq!(a.get().copied(), Some(0));
252    *a.get_mut().unwrap() += 1;
253    assert_eq!(*a.as_inner_unchecked(), 1);
254
255    assert_eq!(a.lock().copied(), Some(1));
256    assert!(a.is_locked());
257
258    assert_eq!(a.as_locked().copied(), Some(1));
259    *a.as_locked_mut().unwrap() += 1;
260    assert_eq!(*a.as_inner_unchecked(), 2);
261}