ab_contracts_common/metadata.rs
1mod compact;
2pub mod decode;
3#[cfg(test)]
4mod tests;
5
6use crate::metadata::compact::compact_metadata;
7use ab_io_type::metadata::MAX_METADATA_CAPACITY;
8
9/// Maximum nesting depth of I/O type metadata in contracts
10pub const MAX_METADATA_DEPTH: usize = 32;
11
12/// Metadata for smart contact methods.
13///
14/// Metadata encoding consists of this enum variant treated as `u8` followed by optional metadata
15/// encoding rules specific to metadata type variant (see variant's description).
16///
17/// This metadata is enough to fully reconstruct the hierarchy of the type to generate language
18/// bindings, auto-generate UI forms, etc.
19#[derive(Debug, Copy, Clone, Eq, PartialEq)]
20#[repr(u8)]
21pub enum ContractMetadataKind {
22 /// Main contract metadata.
23 ///
24 /// Contracts are encoded as follows:
25 /// * Encoding of the state type as described in [`IoTypeMetadataKind`]
26 /// * Encoding of the `#[slot]` type as described in [`IoTypeMetadataKind`]
27 /// * Encoding of the `#[tmp]` type as described in [`IoTypeMetadataKind`]
28 /// * Number of methods (u8)
29 /// * Metadata of methods, each as defined in one of:
30 /// * [`Self::Init`]
31 /// * [`Self::UpdateStateless`]
32 /// * [`Self::UpdateStatefulRo`]
33 /// * [`Self::UpdateStatefulRw`]
34 /// * [`Self::ViewStateless`]
35 /// * [`Self::ViewStateful`]
36 ///
37 /// [`IoTypeMetadataKind`]: ab_io_type::metadata::IoTypeMetadataKind
38 Contract,
39 /// Trait metadata.
40 ///
41 /// Traits are encoded as follows:
42 /// * Length of trait name in bytes (u8)
43 /// * Trait name as UTF-8 bytes
44 /// * Number of methods (u8)
45 /// * Metadata of methods, each as defined in one of:
46 /// * [`Self::UpdateStateless`]
47 /// * [`Self::ViewStateless`]
48 ///
49 /// Methods of traits have no `#[tmp]` or `#[slot]` arguments, since their types are defined by
50 /// a contract.
51 Trait,
52
53 /// `#[init]` method.
54 ///
55 /// Initializers are encoded as follows:
56 /// * Length of method name in bytes (u8)
57 /// * Method name as UTF-8 bytes
58 /// * Number of named arguments (u8, excluding state argument `&self` or `&mut self`, but
59 /// including return type that is not `()` or `Result<(), ContactError>`)
60 ///
61 /// Each argument is encoded as follows:
62 /// * Argument type as u8, one of:
63 /// * [`Self::EnvRo`]
64 /// * [`Self::EnvRw`]
65 /// * [`Self::TmpRo`]
66 /// * [`Self::TmpRw`]
67 /// * [`Self::SlotRo`]
68 /// * [`Self::SlotRw`]
69 /// * [`Self::Input`]
70 /// * [`Self::Output`]
71 /// * [`Self::Return`] (yes, this is considered to be an argument)
72 /// * Length of the argument name in bytes (u8, except for [`Self::EnvRo`] and [`Self::EnvRw`])
73 /// * Argument name as UTF-8 bytes (except for [`Self::EnvRo`] and [`Self::EnvRw`])
74 /// * Only for [`Self::Input`], [`Self::Output`] and [`Self::Return`] recursive metadata of
75 /// argument's type as described in [`IoTypeMetadataKind`] with the following exception:
76 /// * For the last argument of [`Self::Init`] this is skipped since it is statically known to
77 /// be the state: [`Self::Return`] (`Self`) or, without a return value, the last
78 /// [`Self::Output`] (`MaybeData<Self>`), it is present otherwise
79 ///
80 /// Arguments are in the order that `#[contract]` generates them in: `#[env]`
81 /// ([`Self::EnvRo`] or [`Self::EnvRw`]), `#[tmp]` ([`Self::TmpRo`] or [`Self::TmpRw`]),
82 /// `#[slot]`s ([`Self::SlotRo`] and [`Self::SlotRw`]), `#[input]`s, `#[output]`s and the
83 /// return value. Each of `#[env]`, `#[tmp]` and the return value appears at most once.
84 ///
85 /// The number of arguments, together with `self` in methods that have it, is at most
86 /// [`MAX_TOTAL_METHOD_ARGS`].
87 ///
88 /// [`IoTypeMetadataKind`]: ab_io_type::metadata::IoTypeMetadataKind
89 /// [`MAX_TOTAL_METHOD_ARGS`]: crate::MAX_TOTAL_METHOD_ARGS
90 ///
91 /// NOTE: [`Self::Return`] is encoded as a separate argument and counts towards the number of
92 /// arguments. At the same time, `self` doesn't count towards the number of arguments as it
93 /// is implicitly defined by the variant of this struct.
94 Init,
95 /// Stateless `#[update]` method (doesn't have `self` in its arguments).
96 ///
97 /// Encoding is the same as [`Self::Init`].
98 UpdateStateless,
99 /// Stateful read-only `#[update]` method (has `&self` in its arguments).
100 ///
101 /// Encoding is the same as [`Self::Init`].
102 UpdateStatefulRo,
103 /// Stateful read-write `#[update]` method (has `&mut self` in its arguments).
104 ///
105 /// Encoding is the same as [`Self::Init`].
106 UpdateStatefulRw,
107 /// Stateless `#[view]` method (doesn't have `self` in its arguments).
108 ///
109 /// Encoding is the same as [`Self::Init`].
110 ViewStateless,
111 /// Stateful read-only `#[view]` method (has `&self` in its arguments).
112 ///
113 /// Encoding is the same as [`Self::Init`].
114 ViewStateful,
115
116 // TODO: `#[env] can be made implicit assuming the name is of the struct is always the same
117 /// Read-only `#[env]` argument.
118 ///
119 /// Example: `#[env] env: &Env,`
120 EnvRo,
121 /// Read-write `#[env]` argument.
122 ///
123 /// Example: `#[env] env: &mut Env<'_>,`
124 EnvRw,
125 /// Read-only `#[tmp]` argument.
126 ///
127 /// Example: `#[tmp] tmp: &MaybeData<Tmp>,`
128 TmpRo,
129 /// Read-write `#[tmp]` argument.
130 ///
131 /// Example: `#[tmp] tmp: &mut MaybeData<Tmp>,`
132 TmpRw,
133 // TODO: What if address is mandatory for slots? Then it would be possible to make `#[slot]`
134 // implicit
135 /// Read-only `#[slot]` argument with an address.
136 ///
137 /// Example: `#[slot] (from_address, from): (&Address, &MaybeData<Slot>),`
138 SlotRo,
139 /// Read-write `#[slot]` argument with an address.
140 ///
141 /// Example: `#[slot] (from_address, from): (&Address, &mut MaybeData<Slot>),`
142 SlotRw,
143 /// `#[input]` argument.
144 ///
145 /// Example: `#[input] balance: &Balance,`
146 Input,
147 /// Explicit `#[output]` argument.
148 ///
149 /// Example: `#[output] out: &mut VariableBytes<1024>,`
150 Output,
151 /// `T` of [`Result<T, ContractError>`] return type or simply return type if it is not fallible.
152 ///
153 /// NOTE: Skipped if return type's `T` is `()`.
154 ///
155 /// Example: `-> Balance`
156 Return,
157}
158
159const impl TryFrom<u8> for ContractMetadataKind {
160 type Error = ();
161
162 #[inline(always)]
163 fn try_from(byte: u8) -> Result<Self, Self::Error> {
164 Ok(match byte {
165 0 => Self::Contract,
166 1 => Self::Trait,
167 2 => Self::Init,
168 3 => Self::UpdateStateless,
169 4 => Self::UpdateStatefulRo,
170 5 => Self::UpdateStatefulRw,
171 6 => Self::ViewStateless,
172 7 => Self::ViewStateful,
173 8 => Self::EnvRo,
174 9 => Self::EnvRw,
175 10 => Self::TmpRo,
176 11 => Self::TmpRw,
177 12 => Self::SlotRo,
178 13 => Self::SlotRw,
179 14 => Self::Input,
180 15 => Self::Output,
181 16 => Self::Return,
182 _ => {
183 return Err(());
184 }
185 })
186 }
187}
188
189impl ContractMetadataKind {
190 // TODO: Create wrapper type for metadata bytes and move this method there
191 /// Produce compact metadata.
192 ///
193 /// Compact metadata retains the shape but throws some details. Specifically, the following
194 /// transformations are applied to metadata (crucially, method names are retained!):
195 /// * Struct, field, trait, enum, and enum variant names removed (replaced with 0 bytes names),
196 /// so structs with named fields are the same as tuple structs
197 /// * Structs with a single field replaced with the type of the field and structs without fields
198 /// with `()` (see [`IoTypeMetadataKind::compact()`]), so newtypes are the same as the types
199 /// they wrap
200 /// * Method argument names removed
201 ///
202 /// This means that two methods with different argument names, struct field names or newtypes
203 /// (like `BlockNumber` instead of `u64`), but the same shape otherwise are considered
204 /// identical, allowing for limited future refactoring opportunities without changing compact
205 /// metadata shape, which is important for [`MethodFingerprint`] (though
206 /// [`Self::compact_external_args()`] is used by it internally).
207 ///
208 /// [`IoTypeMetadataKind::compact()`]: ab_io_type::metadata::IoTypeMetadataKind::compact
209 /// [`MethodFingerprint`]: crate::method::MethodFingerprint
210 ///
211 /// Returns `None` if the input is invalid, too long or nested deeper than
212 /// [`MAX_METADATA_DEPTH`].
213 pub const fn compact(metadata: &[u8]) -> Option<([u8; MAX_METADATA_CAPACITY], usize)> {
214 compact_metadata(metadata, false)
215 }
216
217 // TODO: Create wrapper type for metadata bytes and move this method there
218 /// Produce compact metadata for `ExternalArgs`.
219 ///
220 /// Similar to [`Self::compact()`], but arguments that are not reflected in `ExternalArgs` will
221 /// be skipped since they don't impact `ExternalArgs` API. This is used for
222 /// [`MethodFingerprint`] derivation.
223 ///
224 /// [`MethodFingerprint`]: crate::method::MethodFingerprint
225 ///
226 /// Returns `None` if the input is invalid, too long or nested deeper than
227 /// [`MAX_METADATA_DEPTH`].
228 pub const fn compact_external_args(
229 metadata: &[u8],
230 ) -> Option<([u8; MAX_METADATA_CAPACITY], usize)> {
231 compact_metadata(metadata, true)
232 }
233}