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