ink_metadata/layout/
mod.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[cfg(test)]
16mod tests;
17mod validate;
18
19use core::fmt::Display;
20pub use validate::ValidateLayout;
21
22use crate::{
23    serde_hex,
24    utils::{
25        deserialize_from_byte_str,
26        serialize_as_byte_str,
27    },
28};
29use derive_more::From;
30use ink_prelude::collections::btree_map::BTreeMap;
31use ink_primitives::Key;
32use scale::{
33    Decode,
34    Encode,
35};
36use scale_info::{
37    IntoPortable,
38    Registry,
39    TypeInfo,
40    form::{
41        Form,
42        MetaForm,
43        PortableForm,
44    },
45    meta_type,
46};
47use schemars::JsonSchema;
48use serde::{
49    Deserialize,
50    Serialize,
51    de::{
52        DeserializeOwned,
53        Error,
54    },
55};
56
57/// Represents the static storage layout of an ink! smart contract.
58#[derive(
59    Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
60)]
61#[serde(bound(
62    serialize = "F::Type: Serialize, F::String: Serialize",
63    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
64))]
65#[serde(rename_all = "camelCase")]
66pub enum Layout<F: Form = MetaForm> {
67    /// An encoded cell.
68    ///
69    /// This is the only leaf node within the layout graph.
70    /// All layout nodes have this node type as their leafs.
71    Leaf(LeafLayout<F>),
72    /// The root cell defines the storage key for all sub-trees.
73    Root(RootLayout<F>),
74    /// A layout that hashes values into the entire storage key space.
75    ///
76    /// This is commonly used by ink! hashmaps and similar data structures.
77    Hash(HashLayout<F>),
78    /// An array of type associated with storage cell.
79    Array(ArrayLayout<F>),
80    /// A struct layout with fields of different types.
81    Struct(StructLayout<F>),
82    /// An enum layout with a discriminant telling which variant is laid out.
83    Enum(EnumLayout<F>),
84}
85
86/// A pointer into some storage region.
87#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, From, JsonSchema)]
88pub struct LayoutKey {
89    key: Key,
90}
91
92impl serde::Serialize for LayoutKey {
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: serde::Serializer,
96    {
97        serde_hex::serialize(&self.key.encode(), serializer)
98    }
99}
100
101impl<'de> serde::Deserialize<'de> for LayoutKey {
102    fn deserialize<D>(d: D) -> Result<Self, D::Error>
103    where
104        D: serde::Deserializer<'de>,
105    {
106        let mut arr = [0; 4];
107        serde_hex::deserialize_check_len(d, serde_hex::ExpectedLen::Exact(&mut arr[..]))?;
108        let key = Key::decode(&mut &arr[..])
109            .map_err(|err| Error::custom(format!("Error decoding layout key: {err}")))?;
110        Ok(key.into())
111    }
112}
113
114impl<'a> From<&'a Key> for LayoutKey {
115    fn from(key: &'a Key) -> Self {
116        Self { key: *key }
117    }
118}
119
120impl LayoutKey {
121    /// Construct a custom layout key.
122    pub fn new<T>(key: T) -> Self
123    where
124        T: Into<u32>,
125    {
126        Self { key: key.into() }
127    }
128
129    /// Returns the key of the layout key.
130    pub fn key(&self) -> &Key {
131        &self.key
132    }
133}
134
135/// Sub-tree root.
136#[derive(
137    Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
138)]
139#[serde(bound(
140    serialize = "F::Type: Serialize, F::String: Serialize",
141    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
142))]
143pub struct RootLayout<F: Form = MetaForm> {
144    /// The root key of the sub-tree.
145    #[schemars(with = "String")]
146    root_key: LayoutKey,
147    /// The storage layout of the unbounded layout elements.
148    layout: Box<Layout<F>>,
149    /// The type of the encoded entity.
150    ty: <F as Form>::Type,
151}
152
153impl IntoPortable for RootLayout {
154    type Output = RootLayout<PortableForm>;
155
156    fn into_portable(self, registry: &mut Registry) -> Self::Output {
157        RootLayout {
158            root_key: self.root_key,
159            layout: Box::new(self.layout.into_portable(registry)),
160            ty: registry.register_type(&self.ty),
161        }
162    }
163}
164
165impl RootLayout<MetaForm> {
166    /// Creates a new root layout with empty root type.
167    pub fn new_empty<L>(root_key: LayoutKey, layout: L) -> Self
168    where
169        L: Into<Layout<MetaForm>>,
170    {
171        Self::new::<L>(root_key, layout, meta_type::<()>())
172    }
173}
174
175impl<F> RootLayout<F>
176where
177    F: Form,
178{
179    /// Create a new root layout
180    pub fn new<L>(root_key: LayoutKey, layout: L, ty: <F as Form>::Type) -> Self
181    where
182        L: Into<Layout<F>>,
183    {
184        Self {
185            root_key,
186            layout: Box::new(layout.into()),
187            ty,
188        }
189    }
190
191    /// Returns the root key of the sub-tree.
192    pub fn root_key(&self) -> &LayoutKey {
193        &self.root_key
194    }
195
196    /// Returns the storage layout of the unbounded layout elements.
197    pub fn layout(&self) -> &Layout<F> {
198        &self.layout
199    }
200
201    /// Returns the type of the encoded entity.
202    pub fn ty(&self) -> &F::Type {
203        &self.ty
204    }
205}
206
207/// A SCALE encoded cell.
208#[derive(
209    Debug, PartialEq, Eq, PartialOrd, Ord, From, Serialize, Deserialize, JsonSchema,
210)]
211#[serde(bound(
212    serialize = "F::Type: Serialize, F::String: Serialize",
213    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
214))]
215pub struct LeafLayout<F: Form = MetaForm> {
216    /// The offset key into the storage.
217    #[schemars(with = "String")]
218    key: LayoutKey,
219    /// The type of the encoded entity.
220    ty: <F as Form>::Type,
221}
222
223impl LeafLayout {
224    /// Creates a new cell layout.
225    pub fn from_key<T>(key: LayoutKey) -> Self
226    where
227        T: TypeInfo + 'static,
228    {
229        Self {
230            key,
231            ty: meta_type::<T>(),
232        }
233    }
234}
235
236impl IntoPortable for LeafLayout {
237    type Output = LeafLayout<PortableForm>;
238
239    fn into_portable(self, registry: &mut Registry) -> Self::Output {
240        LeafLayout {
241            key: self.key,
242            ty: registry.register_type(&self.ty),
243        }
244    }
245}
246
247impl IntoPortable for Layout {
248    type Output = Layout<PortableForm>;
249
250    fn into_portable(self, registry: &mut Registry) -> Self::Output {
251        match self {
252            Layout::Leaf(encoded_cell) => {
253                Layout::Leaf(encoded_cell.into_portable(registry))
254            }
255            Layout::Root(encoded_cell) => {
256                Layout::Root(encoded_cell.into_portable(registry))
257            }
258            Layout::Hash(hash_layout) => {
259                Layout::Hash(hash_layout.into_portable(registry))
260            }
261            Layout::Array(array_layout) => {
262                Layout::Array(array_layout.into_portable(registry))
263            }
264            Layout::Struct(struct_layout) => {
265                Layout::Struct(struct_layout.into_portable(registry))
266            }
267            Layout::Enum(enum_layout) => {
268                Layout::Enum(enum_layout.into_portable(registry))
269            }
270        }
271    }
272}
273
274impl<F> LeafLayout<F>
275where
276    F: Form,
277{
278    /// Returns the offset key into the storage.
279    pub fn key(&self) -> &LayoutKey {
280        &self.key
281    }
282
283    /// Returns the type of the encoded entity.
284    pub fn ty(&self) -> &F::Type {
285        &self.ty
286    }
287
288    pub fn new(key: LayoutKey, ty: <F as Form>::Type) -> Self {
289        Self { key, ty }
290    }
291}
292
293/// A hashing layout potentially hitting all cells of the storage.
294///
295/// Every hashing layout has an offset and a strategy to compute its keys.
296#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
297#[serde(bound(
298    serialize = "F::Type: Serialize, F::String: Serialize",
299    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
300))]
301pub struct HashLayout<F: Form = MetaForm> {
302    /// The key offset used by the strategy.
303    #[schemars(with = "String")]
304    offset: LayoutKey,
305    /// The hashing strategy to layout the underlying elements.
306    strategy: HashingStrategy,
307    /// The storage layout of the unbounded layout elements.
308    layout: Box<Layout<F>>,
309}
310
311impl IntoPortable for HashLayout {
312    type Output = HashLayout<PortableForm>;
313
314    fn into_portable(self, registry: &mut Registry) -> Self::Output {
315        HashLayout {
316            offset: self.offset,
317            strategy: self.strategy,
318            layout: Box::new(self.layout.into_portable(registry)),
319        }
320    }
321}
322
323impl HashLayout {
324    /// Creates a new unbounded layout.
325    pub fn new<K, L>(offset: K, strategy: HashingStrategy, layout: L) -> Self
326    where
327        K: Into<LayoutKey>,
328        L: Into<Layout>,
329    {
330        Self {
331            offset: offset.into(),
332            strategy,
333            layout: Box::new(layout.into()),
334        }
335    }
336}
337
338impl<F> HashLayout<F>
339where
340    F: Form,
341{
342    /// Returns the key offset used by the strategy.
343    pub fn offset(&self) -> &LayoutKey {
344        &self.offset
345    }
346
347    /// Returns the hashing strategy to layout the underlying elements.
348    pub fn strategy(&self) -> &HashingStrategy {
349        &self.strategy
350    }
351
352    /// Returns the storage layout of the unbounded layout elements.
353    pub fn layout(&self) -> &Layout<F> {
354        &self.layout
355    }
356}
357
358/// The unbounded hashing strategy.
359///
360/// The offset key is used as another postfix for the computation.
361/// So the actual formula is: `hasher(prefix + encoded(key) + offset + postfix)`
362/// Where `+` in this contexts means append of the byte slices.
363#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
364pub struct HashingStrategy {
365    /// One of the supported crypto hashers.
366    hasher: CryptoHasher,
367    /// An optional prefix to the computed hash.
368    #[serde(
369        serialize_with = "serialize_as_byte_str",
370        deserialize_with = "deserialize_from_byte_str"
371    )]
372    prefix: Vec<u8>,
373    /// An optional postfix to the computed hash.
374    #[serde(
375        serialize_with = "serialize_as_byte_str",
376        deserialize_with = "deserialize_from_byte_str"
377    )]
378    postfix: Vec<u8>,
379}
380
381impl HashingStrategy {
382    /// Creates a new unbounded hashing strategy.
383    pub fn new(hasher: CryptoHasher, prefix: Vec<u8>, postfix: Vec<u8>) -> Self {
384        Self {
385            hasher,
386            prefix,
387            postfix,
388        }
389    }
390
391    /// Returns the supported crypto hasher.
392    pub fn hasher(&self) -> &CryptoHasher {
393        &self.hasher
394    }
395
396    /// Returns the optional prefix to the computed hash.
397    pub fn prefix(&self) -> &[u8] {
398        &self.prefix
399    }
400
401    /// Returns the optional postfix to the computed hash.
402    pub fn postfix(&self) -> &[u8] {
403        &self.postfix
404    }
405}
406
407/// One of the supported crypto hashers.
408#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
409pub enum CryptoHasher {
410    /// The BLAKE-2 crypto hasher with an output of 256 bits.
411    Blake2x256,
412    /// The SHA-2 crypto hasher with an output of 256 bits.
413    Sha2x256,
414    /// The KECCAK crypto hasher with an output of 256 bits.
415    Keccak256,
416}
417
418/// A layout for an array of associated cells with the same encoding.
419#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
420#[serde(bound(
421    serialize = "F::Type: Serialize, F::String: Serialize",
422    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
423))]
424#[serde(rename_all = "camelCase")]
425pub struct ArrayLayout<F: Form = MetaForm> {
426    /// The offset key of the array layout.
427    ///
428    /// This is the same key as the element at index 0 of the array layout.
429    #[schemars(with = "String")]
430    offset: LayoutKey,
431    /// The number of elements in the array layout.
432    len: u32,
433    /// The layout of the elements stored in the array layout.
434    layout: Box<Layout<F>>,
435}
436
437impl ArrayLayout {
438    /// Creates an array layout with the given length.
439    pub fn new<K, L>(at: K, len: u32, layout: L) -> Self
440    where
441        K: Into<LayoutKey>,
442        L: Into<Layout>,
443    {
444        Self {
445            offset: at.into(),
446            len,
447            layout: Box::new(layout.into()),
448        }
449    }
450}
451
452#[allow(clippy::len_without_is_empty)]
453impl<F> ArrayLayout<F>
454where
455    F: Form,
456{
457    /// Returns the offset key of the array layout.
458    ///
459    /// This is the same key as the element at index 0 of the array layout.
460    pub fn offset(&self) -> &LayoutKey {
461        &self.offset
462    }
463
464    /// Returns the number of elements in the array layout.
465    pub fn len(&self) -> u32 {
466        self.len
467    }
468
469    /// Returns the layout of the elements stored in the array layout.
470    pub fn layout(&self) -> &Layout<F> {
471        &self.layout
472    }
473}
474
475impl IntoPortable for ArrayLayout {
476    type Output = ArrayLayout<PortableForm>;
477
478    fn into_portable(self, registry: &mut Registry) -> Self::Output {
479        ArrayLayout {
480            offset: self.offset,
481            len: self.len,
482            layout: Box::new(self.layout.into_portable(registry)),
483        }
484    }
485}
486
487/// A struct layout with consecutive fields of different layout.
488#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
489#[serde(bound(
490    serialize = "F::Type: Serialize, F::String: Serialize",
491    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
492))]
493pub struct StructLayout<F: Form = MetaForm> {
494    /// The name of the struct.
495    name: F::String,
496    /// The fields of the struct layout.
497    fields: Vec<FieldLayout<F>>,
498}
499
500impl<F> StructLayout<F>
501where
502    F: Form,
503{
504    /// Creates a new struct layout.
505    pub fn new<N, T>(name: N, fields: T) -> Self
506    where
507        N: Into<F::String>,
508        T: IntoIterator<Item = FieldLayout<F>>,
509    {
510        Self {
511            name: name.into(),
512            fields: fields.into_iter().collect(),
513        }
514    }
515
516    /// Returns the name of the struct.
517    pub fn name(&self) -> &F::String {
518        &self.name
519    }
520    /// Returns the fields of the struct layout.
521    pub fn fields(&self) -> &[FieldLayout<F>] {
522        &self.fields
523    }
524}
525
526impl IntoPortable for StructLayout {
527    type Output = StructLayout<PortableForm>;
528
529    fn into_portable(self, registry: &mut Registry) -> Self::Output {
530        StructLayout {
531            name: self.name.to_string(),
532            fields: self
533                .fields
534                .into_iter()
535                .map(|field| field.into_portable(registry))
536                .collect::<Vec<_>>(),
537        }
538    }
539}
540
541/// The layout for a particular field of a struct layout.
542#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
543#[serde(bound(
544    serialize = "F::Type: Serialize, F::String: Serialize",
545    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
546))]
547pub struct FieldLayout<F: Form = MetaForm> {
548    /// The name of the field.
549    name: F::String,
550    /// The kind of the field.
551    ///
552    /// This is either a direct layout bound
553    /// or another recursive layout sub-struct.
554    layout: Layout<F>,
555}
556
557impl<F> FieldLayout<F>
558where
559    F: Form,
560{
561    /// Creates a new custom field layout.
562    pub fn new<N, L>(name: N, layout: L) -> Self
563    where
564        N: Into<F::String>,
565        L: Into<Layout<F>>,
566    {
567        Self {
568            name: name.into(),
569            layout: layout.into(),
570        }
571    }
572
573    /// Returns the name of the field.
574    pub fn name(&self) -> &F::String {
575        &self.name
576    }
577
578    /// Returns the kind of the field.
579    ///
580    /// This is either a direct layout bound
581    /// or another recursive layout sub-struct.
582    pub fn layout(&self) -> &Layout<F> {
583        &self.layout
584    }
585}
586
587impl IntoPortable for FieldLayout {
588    type Output = FieldLayout<PortableForm>;
589
590    fn into_portable(self, registry: &mut Registry) -> Self::Output {
591        FieldLayout {
592            name: self.name.to_string(),
593            layout: self.layout.into_portable(registry),
594        }
595    }
596}
597
598/// The discriminant of an enum variant.
599#[derive(
600    Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
601)]
602pub struct Discriminant(usize);
603
604impl From<usize> for Discriminant {
605    fn from(value: usize) -> Self {
606        Self(value)
607    }
608}
609
610impl Discriminant {
611    /// Returns the value of the discriminant
612    pub fn value(&self) -> usize {
613        self.0
614    }
615}
616
617/// An enum storage layout.
618#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
619#[serde(bound(
620    serialize = "F::Type: Serialize, F::String: Serialize",
621    deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned"
622))]
623#[serde(rename_all = "camelCase")]
624pub struct EnumLayout<F: Form = MetaForm> {
625    /// The name of the Enum.
626    name: F::String,
627    /// The key where the discriminant is stored to dispatch the variants.
628    #[schemars(with = "String")]
629    dispatch_key: LayoutKey,
630    /// The variants of the enum.
631    variants: BTreeMap<Discriminant, StructLayout<F>>,
632}
633
634impl EnumLayout {
635    /// Creates a new enum layout.
636    pub fn new<N, K, V>(name: N, dispatch_key: K, variants: V) -> Self
637    where
638        N: Into<<MetaForm as Form>::String>,
639        K: Into<LayoutKey>,
640        V: IntoIterator<Item = (Discriminant, StructLayout)>,
641    {
642        Self {
643            name: name.into(),
644            dispatch_key: dispatch_key.into(),
645            variants: variants.into_iter().collect(),
646        }
647    }
648}
649
650impl<F> EnumLayout<F>
651where
652    F: Form,
653{
654    /// Returns the name of the field.
655    pub fn name(&self) -> &F::String {
656        &self.name
657    }
658
659    /// Returns the key where the discriminant is stored to dispatch the variants.
660    pub fn dispatch_key(&self) -> &LayoutKey {
661        &self.dispatch_key
662    }
663
664    /// Returns the variants of the enum.
665    pub fn variants(&self) -> &BTreeMap<Discriminant, StructLayout<F>> {
666        &self.variants
667    }
668}
669
670impl IntoPortable for EnumLayout {
671    type Output = EnumLayout<PortableForm>;
672
673    fn into_portable(self, registry: &mut Registry) -> Self::Output {
674        EnumLayout {
675            name: self.name.to_string(),
676            dispatch_key: self.dispatch_key,
677            variants: self
678                .variants
679                .into_iter()
680                .map(|(discriminant, layout)| {
681                    (discriminant, layout.into_portable(registry))
682                })
683                .collect(),
684        }
685    }
686}
687
688/// An error that can occur during ink! metadata generation.
689#[derive(Debug, Clone, PartialEq, Eq)]
690pub enum MetadataError {
691    /// Storage keys of two types intersect
692    Collision(String, String),
693}
694
695impl Display for MetadataError {
696    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
697        match self {
698            Self::Collision(prev_path, curr_path) => {
699                write!(
700                    f,
701                    "storage key collision occurred for `{}`. \
702                    The same storage key is occupied by the `{}`.",
703                    curr_path,
704                    if prev_path.is_empty() {
705                        "contract storage"
706                    } else {
707                        prev_path
708                    }
709                )
710            }
711        }
712    }
713}
714
715#[test]
716fn valid_error_message() {
717    assert_eq!(
718        MetadataError::Collision("".to_string(), "Contract.c:".to_string()).to_string(),
719        "storage key collision occurred for `Contract.c:`. \
720        The same storage key is occupied by the `contract storage`."
721    );
722    assert_eq!(
723        MetadataError::Collision("Contract.a:".to_string(), "Contract.c:".to_string())
724            .to_string(),
725        "storage key collision occurred for `Contract.c:`. \
726        The same storage key is occupied by the `Contract.a:`."
727    )
728}