thruput_sdk/storage/node.rs
1use super::keypath::Keypath;
2use super::sealed::Sealed;
3use core::ops::{Deref, DerefMut};
4
5/// Marker trait for types that can appear as elements in Map, Vector and
6/// Array.
7/// Sealed — only SDK storage types implement this.
8#[allow(private_bounds)]
9// No list of element types here: the trait is sealed with explicit impls, so
10// rustc enumerates them itself. This adds only the part it cannot infer --
11// that the fix is to wrap the value rather than find a different type.
12#[diagnostic::on_unimplemented(
13 message = "`{Self}` cannot be stored in a `Map` or `Vector`",
14 note = "element types must be storage handles: wrap plain data in `Value<{Self}>`; a nested `#[storage]` struct goes in `Node<_>`"
15)]
16pub trait StorageType: Sealed + From<Keypath> {}
17
18/// Build a storage field handle from its keypath.
19///
20/// The `#[storage]` derive calls this rather than `.into()` so that a field
21/// whose type is not a storage handle fails here, at the field. Stated as a
22/// where-clause bound on a concrete type it is *trivially* unsatisfiable,
23/// which rustc reports at the derive's span suggesting
24/// `#![feature(trivial_bounds)]` — never the fix, and it does not say which
25/// field is wrong.
26#[doc(hidden)]
27pub fn __field_from_keypath<T: From<Keypath>>(keypath: Keypath) -> T {
28 T::from(keypath)
29}
30
31/// Wrapper marking a compound storage field group.
32///
33/// Derefs to `T` for field access. The node does not retain its own keypath:
34/// each field handle constructed by `T::from` already carries the path it
35/// needs, so keeping the parent path would add an unused buffer and require an
36/// otherwise-unnecessary clone during construction.
37#[derive(Debug)]
38pub struct Node<T> {
39 fields: T,
40}
41
42impl<T: From<Keypath>> From<Keypath> for Node<T> {
43 fn from(keypath: Keypath) -> Self {
44 Self {
45 fields: T::from(keypath),
46 }
47 }
48}
49
50impl<T> Deref for Node<T> {
51 type Target = T;
52 fn deref(&self) -> &T {
53 &self.fields
54 }
55}
56
57impl<T> DerefMut for Node<T> {
58 fn deref_mut(&mut self) -> &mut T {
59 &mut self.fields
60 }
61}
62
63impl<T> Sealed for Node<T> {}
64impl<T: From<Keypath>> StorageType for Node<T> {}
65
66impl<T: super::deletable::Deletable> super::deletable::Deletable for Node<T> {
67 fn delete_internal(&mut self, exclusive: bool) {
68 self.fields.delete_internal(exclusive);
69 }
70}
71
72/// Marker for types usable as a contract's root state by `#[contract]`.
73///
74/// Automatically implemented for every type that implements
75/// `From<Keypath>` — i.e. anything annotated with `#[storage]`, including
76/// unit structs like `struct Token;`. Contract methods taking
77/// `&self`/`&mut self` require the state type to implement this trait;
78/// contracts with only associated functions never construct state and do
79/// not need it.
80#[diagnostic::on_unimplemented(
81 message = "`{Self}` cannot be used as contract state",
82 note = "methods taking `&self`/`&mut self` require the state type to be a storage struct: add `#[storage]` to the definition of `{Self}`",
83 note = "contracts with only associated functions (no `self` methods) do not need `#[storage]`"
84)]
85pub trait ContractState: From<Keypath> {}
86
87impl<T: From<Keypath>> ContractState for T {}
88
89/// Carries a storage struct's root domain separator.
90///
91/// Derived by `#[storage]` / `#[derive(Storage)]`. `DOMAIN` defaults to the
92/// struct's bare identifier and is overridable with `#[storage(domain = "…")]`.
93/// Only consulted when the type is used as a contract-state root
94/// (see [`contract_state_root`]); nested usage ignores it.
95pub trait StorageDomain {
96 /// The type's storage domain, pre-framed at compile time (see
97 /// [`frame_domain`](crate::storage::frame_domain)); consumed by
98 /// [`contract_state_root`]. Emitted by `derive(Storage)`.
99 const DOMAIN: crate::storage::FramedDomain;
100}
101
102/// Construct the root state node for a contract entrypoint.
103///
104/// Seeds the keypath with the type's [`StorageDomain::DOMAIN`] so distinct
105/// state roots linked into one artifact occupy disjoint subtrees.
106/// Called by `#[contract]`-generated code; not intended for direct use.
107// Still measurably worth keeping out of line even after `StaticKeyBuf`
108// dropped its zero-fill: the body is small enough that LLVM inlines it into
109// every entrypoint, duplicating the CXS-framing/copy work at each call site.
110// Measured on erc20 (10 entrypoints, optimized release build, code size):
111// inline(never) = 14583 bytes, inlined = 16118 bytes (+1535 bytes). Keep out
112// of line.
113#[doc(hidden)]
114#[inline(never)]
115pub fn contract_state_root<T: ContractState + StorageDomain>() -> Node<T> {
116 Node::from(Keypath::root(T::DOMAIN))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[derive(crate::Storage)]
124 struct UnitState;
125
126 crate::contract_test!(unit_struct_contract_state_root_constructs {
127 let _node: Node<UnitState> = contract_state_root::<UnitState>();
128 });
129
130 #[derive(crate::Storage)]
131 struct LeafState {
132 value: crate::storage::Value<u64>,
133 }
134
135 crate::contract_test!(contract_state_root_seeds_domain {
136 let node: Node<LeafState> = contract_state_root::<LeafState>();
137 // Observe the domain through a real field path: the Node itself does
138 // not retain a redundant copy of the parent path.
139 let expected = Keypath::default().child("LeafState").child(&0u32);
140 assert_eq!(*node.value.key(), expected.generate_key());
141 });
142}