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
9#[cfg(all(target_family = "wasm", target_os = "unknown"))]
10#[link(wasm_import_module = "vm_hooks")]
11unsafe extern "C" {
12 fn storage_load_bytes32(key: *const u8, out: *mut u8);
13 fn storage_cache_bytes32(key: *const u8, from: *const u8);
14 fn transient_load_bytes32(key: *const u8, dest: *mut u8);
15 fn transient_store_bytes32(key: *const u8, value: *const u8);
16 fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
17 pub fn storage_flush_cache(clear: bool);
18}
19
20#[cfg(all(
21 not(all(target_family = "wasm", target_os = "unknown")),
22 feature = "std"
23))]
24pub mod storage_host {
25 use super::*;
26
27 use std::{cell::RefCell, collections::HashMap, ptr::copy_nonoverlapping};
28
29 type WordHashMap = HashMap<U, U>;
30
31 thread_local! {
32 pub static STORAGE: RefCell<WordHashMap> = RefCell::default();
33 pub static TRANSIENT: RefCell<WordHashMap> = RefCell::default();
34 }
35
36 pub fn storage_clear() {
37 STORAGE.with(|s| s.borrow_mut().clear())
38 }
39
40 pub fn transient_clear() {
41 TRANSIENT.with(|s| s.borrow_mut().clear())
42 }
43
44 unsafe fn read_word(key: *const u8) -> U {
45 let mut r = [0u8; 32];
46 unsafe {
47 copy_nonoverlapping(key, r.as_mut_ptr(), 32);
48 }
49 U(r)
50 }
51
52 unsafe fn write_word(key: *mut u8, val: U) {
53 unsafe {
54 copy_nonoverlapping(val.as_ptr(), key, 32);
55 }
56 }
57
58 pub(crate) unsafe fn storage_load_bytes32(key: *const u8, out: *mut u8) {
59 let k = unsafe { read_word(key) };
60 let value = STORAGE.with(|s| match s.borrow().get(&k) {
61 Some(v) => *v,
62 None => U::ZERO,
63 });
64 unsafe { write_word(out, value) };
65 }
66
67 pub(crate) unsafe fn storage_cache_bytes32(key: *const u8, value: *const u8) {
68 let k = unsafe { read_word(key) };
69 let v = unsafe { read_word(value) };
70 STORAGE.with(|s| s.borrow_mut().insert(k, v));
71 }
72
73 pub(crate) unsafe fn transient_load_bytes32(key: *const u8, out: *mut u8) {
74 let k = unsafe { read_word(key) };
75 let value = TRANSIENT.with(|s| match s.borrow().get(&k) {
76 Some(v) => *v,
77 None => U::ZERO,
78 });
79 unsafe { write_word(out, value) };
80 }
81
82 pub(crate) unsafe fn transient_store_bytes32(key: *const u8, value: *const u8) {
83 let k = unsafe { read_word(key) };
84 let v = unsafe { read_word(value) };
85 TRANSIENT.with(|s| s.borrow_mut().insert(k, v));
86 }
87
88 pub unsafe fn storage_flush_cache(_: bool) {}
89}
90
91#[cfg(all(
92 not(all(target_family = "wasm", target_os = "unknown")),
93 not(feature = "std")
94))]
95mod storage_host {
96 pub(crate) unsafe fn storage_load_bytes32(_: *const u8, _: *mut u8) {}
97
98 pub(crate) unsafe fn storage_cache_bytes32(_: *const u8, _: *const u8) {}
99
100 pub(crate) unsafe fn transient_load_bytes32(_: *const u8, _: *mut u8) {}
101
102 pub(crate) unsafe fn transient_store_bytes32(_: *const u8, _: *const u8) {}
103
104 pub unsafe fn storage_flush_cache(_: bool) {}
105}
106
107#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
108use storage_host::*;
109
110#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
111pub use storage_host::storage_flush_cache;
112
113macro_rules! storage_ops {
114 ($($prefix:ident),* $(,)?) => {
115 $(
116 paste::paste! {
117 pub fn [<$prefix _load>](x: &U) -> U {
118 let mut b = [0u8; 32];
119 unsafe { [<$prefix _load_bytes32>](x.as_ptr(), b.as_mut_ptr()) }
120 U(b)
121 }
122
123 pub fn [<$prefix _load_bool>](x: &U) -> bool {
124 [<$prefix _load>](x).into()
125 }
126
127 pub fn [<$prefix _exchange>](k: &U, exp: &U, new: &U) -> bool {
129 let t = [<$prefix _load>](k);
130 if &t != exp {
131 return false;
132 }
133 [<$prefix _store>](k, new);
134 true
135 }
136
137 pub fn [<$prefix _exchange_res>](k: &U, exp: &U, new: &U) -> Result<(), U> {
138 let t = [<$prefix _load>](k);
139 if &t != exp {
140 return Err(t);
141 }
142 [<$prefix _store>](k, new);
143 Ok(())
144 }
145
146 pub fn [<$prefix _exchange_bool>](k: &U, new: bool) -> bool {
149 [<$prefix _exchange>](k, &U::from(!new), &U::from(new))
150 }
151
152 pub fn [<$prefix _exchange_bool_res>](k: &U, new: bool) -> Result<(), bool> {
153 let x = [<$prefix _exchange_bool>](k, new);
154 if x == !new {
155 Ok(())
156 } else {
157 Err(x)
158 }
159 }
160 }
161 )*
162 };
163}
164
165pub fn storage_store(x: &U, y: &U) {
166 unsafe { storage_cache_bytes32(x.as_ptr(), y.as_ptr()) }
167}
168
169pub fn storage_store_bool(x: &U, y: bool) {
170 storage_store(x, &U::from(y))
171}
172
173pub fn transient_store(x: &U, y: &U) {
174 unsafe { transient_store_bytes32(x.as_ptr(), y.as_ptr()) }
175}
176
177pub fn transient_store_bool(x: &U, y: bool) {
178 transient_store(x, &U::from(y))
179}
180
181pub fn flush_cache() {
182 unsafe { storage_flush_cache(false) }
183}
184
185pub fn flush_guard<R, F: FnOnce() -> R>(f: F) -> R {
186 let r = f();
187 flush_cache();
188 r
189}
190
191storage_ops!(storage, transient);
192
193macro_rules! storage_mutate_ops {
194 ($prefix:ident, $($op:expr),* $(,)?) => {
195 $(
196 paste::paste! {
197 pub fn [<$prefix _wrapping_ $op>](x: &U, new: &U) {
198 [<$prefix _store>](x, &bobcat_maths::[<wrapping_ $op>](&[<$prefix _load>](x), new))
199 }
200
201 pub fn [<$prefix _saturating_ $op>](x: &U, new: &U) {
202 [<$prefix _store>](x, &bobcat_maths::[<saturating_ $op>](&[<$prefix _load>](x), new))
203 }
204
205 pub fn [<$prefix _checked_ $op>](x: &U, new: &U) -> Option<()> {
206 let y = [<$prefix _load>](x);
207 let v = bobcat_maths::[<checked_ $op>](&y, new);
208 [<$prefix _store>](x, &v);
209 Some(())
210 }
211
212 pub fn [<$prefix _checked_ $op _res>](x: &U, new: &U) -> U {
213 let y = [<$prefix _load>](x);
214 let v = bobcat_maths::[<checked_ $op>](&y, new);
215 [<$prefix _store>](x, &v);
216 v
217 }
218 }
219 )*
220 };
221}
222
223storage_mutate_ops!(storage, add, sub, mul, div);
224storage_mutate_ops!(transient, add, sub, mul, div);
225
226#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
227pub fn slot_map_slot(k: &U, p: &U) -> U {
228 const_slot_map(k, p)
229}
230
231pub fn reentrancy_guard_entry(x: &U) {
232 assert!(x.len() <= 32, "too large");
233 assert!(transient_exchange_bool(x, true), "reentrancy alarm")
234}
235
236pub fn reentrancy_guard_exit(x: &U) {
237 assert!(x.len() <= 32, "too large");
238 transient_store(x, &U::ZERO);
239}
240
241pub fn reentrancy_guard<R>(k: &U, f: impl FnOnce() -> R) -> R {
242 reentrancy_guard_entry(k);
243 let v = f();
244 reentrancy_guard_exit(k);
245 v
246}
247
248pub fn reentrancy_guard_sel<R>(k: &[u8; 4], f: impl FnOnce() -> R) -> R {
249 reentrancy_guard::<R>(&U::from(k), f)
250}
251
252pub const fn const_slot_off_curve(b: &[u8]) -> U {
255 bobcat_maths::wrapping_sub(&const_keccak256(b), &U::ONE)
256}
257
258pub fn slot_off_curve(b: &[u8]) -> U {
259 bobcat_maths::checked_sub_opt(&keccak256(b), &U::ONE).unwrap()
262}
263
264#[cfg(all(target_family = "wasm", target_os = "unknown"))]
265pub fn keccak256(b: &[u8]) -> U {
266 let mut out = [0u8; 32];
267 unsafe {
268 native_keccak256(b.as_ptr(), b.len(), out.as_mut_ptr());
269 }
270 U(out)
271}
272
273pub const fn const_keccak256(b: &[u8]) -> U {
274 U(Keccak256::new().update(b).finalize())
275}
276
277#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
278pub fn keccak256(b: &[u8]) -> U {
279 const_keccak256(b)
280}
281
282pub fn reentrancy_guard_const_keccak<R>(k: &[u8], f: impl FnOnce() -> R) -> R {
283 reentrancy_guard(&const_keccak256(k), f)
284}
285
286pub fn reentrancy_guard_keccak<R>(k: &[u8], f: impl FnOnce() -> R) -> R {
287 reentrancy_guard(&keccak256(k), f)
288}
289
290pub const fn const_slot_map(k: &U, p: &U) -> U {
293 let a: [u8; 32 * 2] = concat_arrays!(k.0, p.0);
294 const_keccak256(&a)
295}
296
297#[cfg(all(target_family = "wasm", target_os = "unknown"))]
298pub fn slot_map(k: &U, p: &U) -> U {
299 let b: [u8; 32 * 2] = concat_arrays!(k.0, p.0);
300 keccak256(&b)
301}
302
303#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
304pub fn slot_map(k: &U, p: &U) -> U {
305 const_slot_map(k, p)
306}
307
308#[test]
309fn test_slot_edd25519_count() {
310 assert_eq!(
311 U::from(
312 const_hex::const_decode_to_array::<32>(
313 b"709318ac04e7c3155ef66c30be7220b3243d7e2378fa4153b5f14ebd3ea771ab"
314 )
315 .unwrap()
316 ),
317 const_slot_off_curve(b"superposition.passport.ed25519_count")
318 );
319}
320
321#[cfg(all(feature = "std", test))]
322mod test {
323 use super::*;
324
325 use proptest::prelude::*;
326
327 proptest! {
328 #[test]
329 fn test_reentrancy_guard(x in any::<[u8; 8]>()) {
330 reentrancy_guard(&U::from(x), || {
331 assert!(transient_load(&U::from(x)).is_true());
332 });
333 assert!(transient_load(&U::from(x)).is_zero());
334 }
335
336 #[test]
337 fn test_reentrancy_guard_bad(x in any::<[u8; 8]>()) {
338 let x = U::from(x);
339 transient_store(&x, &U::from(false));
340 assert!(transient_exchange_bool(&x, true));
341 assert!(!transient_exchange_bool(&x, true));
342 assert!(transient_load(&x).is_some());
343 }
344
345 #[test]
346 fn test_reentrancy_guard_sel(x in any::<[u8; 4]>()) {
347 reentrancy_guard_sel(&x, || {
348 assert!(transient_load(&U::from(x)).is_true());
349 });
350 assert!(transient_load(&U::from(x)).is_zero());
351 }
352 }
353}