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 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 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
154pub 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 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
200pub 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}