bobcat_storage/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3use keccak_const::Keccak256;
4
5use array_concat::concat_arrays;
6
7pub use bobcat_maths::U;
8
9use bobcat_maths::wrapping_sub;
10
11pub use bobcat_host as host;
12
13pub use bobcat_maths as maths;
14
15macro_rules! storage_ops {
16    ($($prefix:ident),* $(,)?) => {
17        $(
18            paste::paste! {
19                pub fn [<$prefix _load>](x: &U) -> U {
20                    let mut b = [0u8; 32];
21                    unsafe { $crate::host::[<$prefix _load_bytes32>](x.as_ptr(), b.as_mut_ptr()) }
22                    U(b)
23                }
24
25                pub fn [<$prefix _load_bool>](x: &U) -> bool {
26                    [<$prefix _load>](x).into()
27                }
28
29                /// Attempt to "exchange" a value, returning whether the expected value was set.
30                pub fn [<$prefix _exchange>](k: &U, exp: &U, new: &U) -> bool {
31                    let t = [<$prefix _load>](k);
32                    if &t != exp {
33                        return false;
34                    }
35                    $crate::[<$prefix _store>](k, new);
36                    true
37                }
38
39                pub fn [<$prefix _exchange_res>](k: &U, exp: &U, new: &U) -> Result<(), U> {
40                    let t = [<$prefix _load>](k);
41                    if &t != exp {
42                        return Err(t);
43                    }
44                    [<$prefix _store>](k, new);
45                    Ok(())
46                }
47
48                /// Set the value given, checking that the value passed has the inverse
49                /// set set currently. So, passing true would check if false is set.
50                pub fn [<$prefix _exchange_bool>](k: &U, new: bool) -> bool {
51                    [<$prefix _exchange>](k, &U::from(!new), &U::from(new))
52                }
53
54                pub fn [<$prefix _exchange_bool_res>](k: &U, new: bool) -> Result<(), bool> {
55                   let x = [<$prefix _exchange_bool>](k, new);
56                   if x == !new {
57                       Ok(())
58                   } else {
59                       Err(x)
60                   }
61                }
62            }
63        )*
64    };
65}
66
67pub fn storage_store(x: &U, y: &U) {
68    unsafe { host::storage_cache_bytes32(x.as_ptr(), y.as_ptr()) }
69}
70
71pub fn storage_store_bool(x: &U, y: bool) {
72    storage_store(x, &U::from(y))
73}
74
75pub fn transient_store(x: &U, y: &U) {
76    unsafe { host::transient_store_bytes32(x.as_ptr(), y.as_ptr()) }
77}
78
79pub fn transient_store_bool(x: &U, y: bool) {
80    transient_store(x, &U::from(y))
81}
82
83pub fn flush_cache() {
84    unsafe { host::storage_flush_cache(false) }
85}
86
87pub fn flush_guard<R, F: FnOnce() -> R>(f: F) -> R {
88    let r = f();
89    flush_cache();
90    r
91}
92
93storage_ops!(storage, transient);
94
95macro_rules! storage_mutate_ops {
96    ($prefix:ident, $($op:expr),* $(,)?) => {
97        $(
98            paste::paste! {
99                pub fn [<$prefix _wrapping_ $op>](x: &U, new: &U) {
100                    [<$prefix _store>](x, &maths::[<wrapping_ $op>](&[<$prefix _load>](x), new))
101                }
102
103                pub fn [<$prefix _saturating_ $op>](x: &U, new: &U) {
104                    [<$prefix _store>](x, &maths::[<saturating_ $op>](&[<$prefix _load>](x), new))
105                }
106
107                pub fn [<$prefix _checked_ $op>](x: &U, new: &U) -> Option<()> {
108                    let y = [<$prefix _load>](x);
109                    let v = maths::[<checked_ $op>](&y, new);
110                    [<$prefix _store>](x, &v);
111                    Some(())
112                }
113
114                pub fn [<$prefix _checked_ $op _res>](x: &U, new: &U) -> U {
115                    let y = [<$prefix _load>](x);
116                    let v = maths::[<checked_ $op>](&y, new);
117                    [<$prefix _store>](x, &v);
118                    v
119                }
120            }
121        )*
122    };
123}
124
125storage_mutate_ops!(storage, add, sub, mul, div);
126storage_mutate_ops!(transient, add, sub, mul, div);
127
128#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
129pub fn slot_map_slot(k: &U, p: &U) -> U {
130    const_slot_map(k, p)
131}
132
133pub fn reentrancy_guard_entry(x: &U) {
134    assert!(x.len() <= 32, "too large");
135    assert!(transient_exchange_bool(x, true), "reentrancy alarm")
136}
137
138pub fn reentrancy_guard_exit(x: &U) {
139    assert!(x.len() <= 32, "too large");
140    transient_store(x, &U::ZERO);
141}
142
143pub fn reentrancy_guard<R>(k: &U, f: impl FnOnce() -> R) -> R {
144    reentrancy_guard_entry(k);
145    let v = f();
146    reentrancy_guard_exit(k);
147    v
148}
149
150pub fn reentrancy_guard_sel<R>(k: &[u8; 4], f: impl FnOnce() -> R) -> R {
151    reentrancy_guard::<R>(&U::from(k), f)
152}
153
154/// Compute the slot for a slice, and take it off the curve. Useful for
155/// storage slot accesses (and more).
156pub const fn const_slot_off_curve(b: &[u8]) -> U {
157    wrapping_sub(&const_keccak256(b), &U::ONE)
158}
159
160pub fn slot_off_curve(b: &[u8]) -> U {
161    // This won't result in 0 from the keccak, so we can use checked_sub to
162    // use the code the host gives us for a slightly lower codesize profile.
163    maths::checked_sub_opt(&keccak256(b), &U::ONE).unwrap()
164}
165
166#[cfg(all(target_family = "wasm", target_os = "unknown"))]
167pub fn keccak256(b: &[u8]) -> U {
168    let mut out = [0u8; 32];
169    unsafe {
170        host::native_keccak256(b.as_ptr(), b.len(), out.as_mut_ptr());
171    }
172    U(out)
173}
174
175pub const fn const_keccak256(b: &[u8]) -> U {
176    U(Keccak256::new().update(b).finalize())
177}
178
179pub const fn const_keccak256_two(x: &[u8], y: &[u8]) -> U {
180    U(Keccak256::new().update(x).update(y).finalize())
181}
182
183pub const fn const_keccak256_two_off_curve(x: &[u8], y: &[u8]) -> U {
184    wrapping_sub(&const_keccak256_two(x, y), &U::ONE)
185}
186
187#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
188pub fn keccak256(b: &[u8]) -> U {
189    const_keccak256(b)
190}
191
192pub fn reentrancy_guard_const_keccak<R>(k: &[u8], f: impl FnOnce() -> R) -> R {
193    reentrancy_guard(&const_keccak256(k), f)
194}
195
196pub fn reentrancy_guard_keccak<R>(k: &[u8], f: impl FnOnce() -> R) -> R {
197    reentrancy_guard(&keccak256(k), f)
198}
199
200/// Find the storage map slot using keccak_const. Don't do this during
201/// your runtime code, unless you want to pay the codesize price.
202pub const fn const_slot_map(k: &U, p: &U) -> U {
203    let a: [u8; 32 * 2] = concat_arrays!(k.0, p.0);
204    const_keccak256(&a)
205}
206
207#[cfg(all(target_family = "wasm", target_os = "unknown"))]
208pub fn slot_map(k: &U, p: &U) -> U {
209    let b: [u8; 32 * 2] = concat_arrays!(k.0, p.0);
210    keccak256(&b)
211}
212
213#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
214pub fn slot_map(k: &U, p: &U) -> U {
215    const_slot_map(k, p)
216}
217
218#[test]
219fn test_slot_edd25519_count() {
220    assert_eq!(
221        U::from(
222            const_hex::const_decode_to_array::<32>(
223                b"709318ac04e7c3155ef66c30be7220b3243d7e2378fa4153b5f14ebd3ea771ab"
224            )
225            .unwrap()
226        ),
227        const_slot_off_curve(b"superposition.passport.ed25519_count")
228    );
229}
230
231#[cfg(all(feature = "std", test))]
232mod test {
233    use super::*;
234
235    use proptest::prelude::*;
236
237    proptest! {
238        #[test]
239        fn test_reentrancy_guard(x in any::<[u8; 8]>()) {
240            reentrancy_guard(&U::from(x), || {
241                assert!(transient_load(&U::from(x)).is_true());
242            });
243            assert!(transient_load(&U::from(x)).is_zero());
244        }
245
246        #[test]
247        fn test_reentrancy_guard_bad(x in any::<[u8; 8]>()) {
248             let x = U::from(x);
249             transient_store(&x, &U::from(false));
250             assert!(transient_exchange_bool(&x, true));
251             assert!(!transient_exchange_bool(&x, true));
252            assert!(transient_load(&x).is_some());
253        }
254
255        #[test]
256        fn test_reentrancy_guard_sel(x in any::<[u8; 4]>()) {
257            reentrancy_guard_sel(&x, || {
258                assert!(transient_load(&U::from(x)).is_true());
259            });
260            assert!(transient_load(&U::from(x)).is_zero());
261        }
262    }
263}