Skip to main content

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}