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