ink_metadata/layout/
validate.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
15use crate::layout::{
16    Layout,
17    MetadataError,
18    StructLayout,
19};
20use ink_prelude::collections::HashMap;
21use ink_primitives::Key;
22use scale_info::form::MetaForm;
23
24/// It validates that the storage layout doesn't have conflicting storage keys.
25/// Otherwise an error with a description of the conflict is returned.
26pub struct ValidateLayout {
27    first_entry: HashMap<Key, String>,
28    name_stack: Vec<String>,
29}
30
31impl ValidateLayout {
32    /// Validates the storage layout.
33    pub fn validate(layout: &Layout<MetaForm>) -> Result<(), MetadataError> {
34        let mut validator = Self {
35            first_entry: Default::default(),
36            name_stack: Default::default(),
37        };
38        validator.recursive_validate(layout)
39    }
40
41    fn recursive_validate(
42        &mut self,
43        layout: &Layout<MetaForm>,
44    ) -> Result<(), MetadataError> {
45        match layout {
46            Layout::Root(root) => {
47                self.check_key(root.root_key.key())?;
48                self.recursive_validate(root.layout())
49            }
50            Layout::Hash(hash) => self.recursive_validate(hash.layout()),
51            Layout::Array(array) => self.recursive_validate(array.layout()),
52            Layout::Struct(st) => self.check_struct_layout(st),
53            Layout::Enum(en) => {
54                // After `Enum::` we will have the struct -> `Enum::Struct`
55                self.name_stack.push(format!("{}::", en.name()));
56                for variant in en.variants().values() {
57                    self.check_struct_layout(variant)?;
58                }
59                self.name_stack.pop().expect("stack is not empty; qed");
60                Ok(())
61            }
62            _ => Ok(()),
63        }
64    }
65
66    fn check_struct_layout(&mut self, st: &StructLayout) -> Result<(), MetadataError> {
67        self.name_stack.push(st.name().to_string());
68        for layout in st.fields() {
69            let name = layout.name();
70            // After `Struct` we always have fields -> `Struct.field`
71            // After field we have `Struct` or `Enum` -> `Struct.field:Struct`
72            self.name_stack.push(format!(".{name}:"));
73
74            self.recursive_validate(layout.layout())?;
75
76            self.name_stack.pop().unwrap();
77        }
78        self.name_stack.pop().unwrap();
79        Ok(())
80    }
81
82    fn check_key(&mut self, key: &Key) -> Result<(), MetadataError> {
83        let path = self.name_stack.join("");
84        if let Some(prev_path) = self.first_entry.get(key) {
85            Err(MetadataError::Collision(prev_path.clone(), path))
86        } else {
87            self.first_entry.insert(*key, path);
88            Ok(())
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use crate::layout::{
96        EnumLayout,
97        FieldLayout,
98        Layout,
99        LeafLayout,
100        MetadataError,
101        RootLayout,
102        StructLayout,
103        ValidateLayout,
104    };
105    use ink_primitives::Key;
106    use std::collections::BTreeSet;
107
108    #[test]
109    fn valid_layout_tree_only_roots() {
110        // Root(0) -> Root(1) -> Root(2) -> u32
111        let layout = RootLayout::new_empty(
112            0.into(),
113            RootLayout::new_empty(
114                1.into(),
115                RootLayout::new_empty(2.into(), LeafLayout::from_key::<u32>(2.into())),
116            ),
117        );
118
119        assert!(ValidateLayout::validate(&Layout::Root(layout)).is_ok())
120    }
121
122    // If any of the root key are equal it should cause an error
123    fn valid_big_layout_tree(
124        key_for_root_0: Key,
125        key_for_root_1: Key,
126        key_for_root_2: Key,
127        key_for_root_3: Key,
128        key_for_root_4: Key,
129    ) -> Result<(), MetadataError> {
130        let root_0 = key_for_root_0.into();
131        let root_1 = key_for_root_1.into();
132        let root_2 = key_for_root_2.into();
133        let root_3 = key_for_root_3.into();
134        let root_4 = key_for_root_4.into();
135        // Below the description of the layout tree. Inside `(...)` the expected storage
136        // key.              Root(0)
137        //                |
138        //            Contract(0)
139        //          /     |     \
140        //  a:Root(1)  b:u32(0)  c:Struct0(0)
141        //         |               /       \
142        //  Vec<u8>(1)     d:u128(0)     f:Root(2)
143        //                                   |
144        //                                Enum(2)
145        //                               /   |   \
146        //                           First Second Third
147        //                       0.Struct1 0.u8(2) 0.Root(3)
148        //                             |            |
149        //                           Root(4)      String
150        //                             |
151        //                      g:BTreeSet<u64>(4)
152        let layout = RootLayout::new_empty(
153            root_0,
154            StructLayout::new(
155                "Contract",
156                vec![
157                    FieldLayout::new(
158                        "a",
159                        StructLayout::new(
160                            "Struct0",
161                            vec![
162                                FieldLayout::new("d", LeafLayout::from_key::<u128>(root_0)),
163                                FieldLayout::new(
164                                    "f",
165                                    RootLayout::new_empty(
166                                        root_2,
167                                        EnumLayout::new(
168                                            "Enum",
169                                            root_2,
170                                            vec![
171                                                (
172                                                    0.into(),
173                                                    StructLayout::new(
174                                                        "First",
175                                                        vec![FieldLayout::new(
176                                                            "0",
177                                                            StructLayout::new(
178                                                                "Struct1",
179                                                                vec![FieldLayout::new(
180                                                                    "g",
181                                                                    RootLayout::new_empty(
182                                                                        root_4,
183                                                                        LeafLayout::from_key::<
184                                                                            BTreeSet<u64>,
185                                                                        >(
186                                                                            root_4
187                                                                        ),
188                                                                    ),
189                                                                )],
190                                                            ),
191                                                        )],
192                                                    ),
193                                                ),
194                                                (
195                                                    1.into(),
196                                                    StructLayout::new(
197                                                        "Second",
198                                                        vec![FieldLayout::new(
199                                                            "0",
200                                                            LeafLayout::from_key::<u8>(root_2),
201                                                        )],
202                                                    ),
203                                                ),
204                                                (
205                                                    2.into(),
206                                                    StructLayout::new(
207                                                        "Third",
208                                                        vec![FieldLayout::new(
209                                                            "0",
210                                                            RootLayout::new_empty(
211                                                                root_3,
212                                                                LeafLayout::from_key::<String>(
213                                                                    root_3,
214                                                                ),
215                                                            ),
216                                                        )],
217                                                    ),
218                                                ),
219                                            ],
220                                        ),
221                                    ),
222                                ),
223                            ],
224                        ),
225                    ),
226                    FieldLayout::new("b", LeafLayout::from_key::<u32>(root_0)),
227                    FieldLayout::new(
228                        "c",
229                        RootLayout::new_empty(root_1, LeafLayout::from_key::<Vec<u8>>(root_1)),
230                    ),
231                ],
232            ),
233        );
234
235        ValidateLayout::validate(&Layout::Root(layout))
236    }
237
238    #[test]
239    fn tree_is_valid() {
240        assert_eq!(Ok(()), valid_big_layout_tree(0, 1, 2, 3, 4));
241        assert_eq!(Ok(()), valid_big_layout_tree(4, 3, 2, 1, 0));
242    }
243
244    #[test]
245    fn conflict_0_and_1() {
246        assert_eq!(
247            Err(MetadataError::Collision(
248                "".to_string(),
249                "Contract.c:".to_string()
250            )),
251            valid_big_layout_tree(0, 0, 2, 3, 4)
252        )
253    }
254
255    #[test]
256    fn conflict_0_and_2() {
257        assert_eq!(
258            Err(MetadataError::Collision(
259                "".to_string(),
260                "Contract.a:Struct0.f:".to_string()
261            )),
262            valid_big_layout_tree(0, 1, 0, 3, 4)
263        )
264    }
265
266    #[test]
267    fn conflict_0_and_3() {
268        assert_eq!(
269            Err(MetadataError::Collision(
270                "".to_string(),
271                "Contract.a:Struct0.f:Enum::Third.0:".to_string()
272            )),
273            valid_big_layout_tree(0, 1, 2, 0, 4)
274        )
275    }
276
277    #[test]
278    fn conflict_0_and_4() {
279        assert_eq!(
280            Err(MetadataError::Collision(
281                "".to_string(),
282                "Contract.a:Struct0.f:Enum::First.0:Struct1.g:".to_string()
283            )),
284            valid_big_layout_tree(0, 1, 2, 3, 0)
285        )
286    }
287
288    #[test]
289    fn conflict_3_and_4() {
290        assert_eq!(
291            Err(MetadataError::Collision(
292                "Contract.a:Struct0.f:Enum::First.0:Struct1.g:".to_string(),
293                "Contract.a:Struct0.f:Enum::Third.0:".to_string()
294            )),
295            valid_big_layout_tree(0, 1, 2, 3, 3)
296        )
297    }
298}