Skip to main content

sails_idl_ast/
hash.rs

1use super::*;
2use alloc::{collections::btree_map::BTreeMap, format};
3use keccak_const::Keccak256;
4
5type Error = String;
6
7impl ServiceUnit {
8    /// Compute a deterministic interface identifier for this service.
9    ///
10    /// The hash incorporates:
11    /// - all functions (kind, name, params, output, optional throws),
12    /// - all events (their payload shape),
13    /// - all base services by their already-computed interface IDs.
14    ///
15    /// Types referenced by functions or events are expanded via the AST
16    /// definitions in `self.types`, including generic instantiation.
17    pub fn interface_id(&self) -> Result<InterfaceId, Error> {
18        let type_map: BTreeMap<_, _> = self.types.iter().map(|ty| (ty.name.as_str(), ty)).collect();
19
20        let mut hash = Keccak256::new();
21        for func in &self.funcs {
22            hash = hash.update(&hash_func(func, &type_map)?);
23        }
24
25        if !self.events.is_empty() {
26            let mut ev_hash = Keccak256::new();
27            for var in &self.events {
28                ev_hash = ev_hash.update(&hash_struct(&var.name, &var.def.fields, &type_map, None)?)
29            }
30            hash = hash.update(&ev_hash.finalize());
31        }
32
33        for s in &self.extends {
34            let interface_id = s
35                .interface_id
36                .ok_or_else(|| format!("service `{}` does not have an `interface_id`", s.name))?;
37            hash = hash.update(interface_id.as_bytes());
38        }
39
40        Ok(InterfaceId::from_bytes_32(hash.finalize()))
41    }
42}
43
44fn hash_func(func: &ServiceFunc, type_map: &BTreeMap<&str, &Type>) -> Result<[u8; 32], Error> {
45    let mut hash = Keccak256::new();
46    hash = match func.kind {
47        FunctionKind::Command => hash.update(b"command"),
48        FunctionKind::Query => hash.update(b"query"),
49    };
50    hash = hash.update(func.name.as_bytes());
51    for p in &func.params {
52        hash = hash.update(&hash_type_decl(&p.type_decl, type_map, None)?);
53    }
54    hash = hash.update(b"res");
55    hash = hash.update(&hash_type_decl(&func.output, type_map, None)?);
56    if let Some(th) = &func.throws {
57        hash = hash.update(b"throws");
58        hash = hash.update(&hash_type_decl(th, type_map, None)?);
59    }
60    Ok(hash.finalize())
61}
62
63fn hash_type(
64    ty: &Type,
65    type_map: &BTreeMap<&str, &Type>,
66    type_params: Option<&BTreeMap<String, TypeDecl>>,
67) -> Result<[u8; 32], Error> {
68    let bytes = match &ty.def {
69        TypeDef::Struct(StructDef { fields }) => {
70            hash_struct(&ty.name, fields, type_map, type_params)?
71        }
72        TypeDef::Enum(enum_def) => {
73            let mut hash = Keccak256::new();
74            for var in &enum_def.variants {
75                hash = hash.update(&hash_struct(
76                    &var.name,
77                    &var.def.fields,
78                    type_map,
79                    type_params,
80                )?)
81            }
82            hash.finalize()
83        }
84        TypeDef::Alias(alias_def) => hash_type_decl(&alias_def.target, type_map, type_params)?,
85    };
86    Ok(bytes)
87}
88
89fn hash_struct(
90    name: &str,
91    fields: &[StructField],
92    type_map: &BTreeMap<&str, &Type>,
93    type_params: Option<&BTreeMap<String, TypeDecl>>,
94) -> Result<[u8; 32], Error> {
95    let mut hash = Keccak256::new().update(name.as_bytes());
96    for f in fields {
97        hash = hash.update(hash_type_decl(&f.type_decl, type_map, type_params)?.as_slice())
98    }
99    Ok(hash.finalize())
100}
101
102fn hash_type_decl(
103    type_decl: &TypeDecl,
104    type_map: &BTreeMap<&str, &Type>,
105    type_params: Option<&BTreeMap<String, TypeDecl>>,
106) -> Result<[u8; 32], Error> {
107    let bytes = match type_decl {
108        // Encode slices as [T].
109        TypeDecl::Slice { item } => Keccak256::new()
110            .update(b"[")
111            .update(hash_type_decl(item, type_map, type_params)?.as_slice())
112            .update(b"]")
113            .finalize(),
114        // Arrays include the element type and the length.
115        TypeDecl::Array { item, len } => Keccak256::new()
116            .update(hash_type_decl(item, type_map, type_params)?.as_slice())
117            .update(format!("{len}").as_bytes())
118            .finalize(),
119        // Tuples hash their element types in order.
120        TypeDecl::Tuple { types } => {
121            let mut hash = Keccak256::new();
122            for ty in types {
123                hash = hash.update(&hash_type_decl(ty, type_map, type_params)?);
124            }
125            hash.finalize()
126        }
127        TypeDecl::Generic { name } => {
128            let Some(param_ty) = type_params.and_then(|map| map.get(name)) else {
129                return Err(format!("generic type parameter `{name}` must be resolved"));
130            };
131            return hash_type_decl(param_ty, type_map, type_params);
132        }
133        TypeDecl::Named { name, generics } => {
134            // Normalize well-known container types to stable markers.
135            if let Some(ty) = TypeDecl::option_type_decl(type_decl) {
136                Keccak256::new()
137                    .update(b"Option")
138                    .update(&hash_type_decl(&ty, type_map, type_params)?)
139                    .finalize()
140            } else if let Some((ok, err)) = TypeDecl::result_type_decl(type_decl) {
141                Keccak256::new()
142                    .update(b"Result")
143                    .update(&hash_type_decl(&ok, type_map, type_params)?)
144                    .update(&hash_type_decl(&err, type_map, type_params)?)
145                    .finalize()
146            // Expand named user-defined types from the map, with generics applied.
147            } else if let Some(ty) = type_map.get(name.as_str()) {
148                if generics.is_empty() {
149                    hash_type(ty, type_map, None)?
150                } else if ty.type_params.len() == generics.len() {
151                    let mut params = BTreeMap::new();
152                    for (param, arg) in ty.type_params.iter().zip(generics.iter()) {
153                        params.insert(param.name.clone(), arg.clone());
154                    }
155                    hash_type(ty, type_map, Some(&params))?
156                } else {
157                    return Err(format!("generic params type `{name}` must be resolved"));
158                }
159            } else {
160                return Err(format!("type `{name}` not supported"));
161            }
162        }
163        // Primitives are hashed by their canonical IDL spelling.
164        TypeDecl::Primitive(primitive_type) => Keccak256::new()
165            .update(primitive_type.as_str().as_bytes())
166            .finalize(),
167    };
168    Ok(bytes)
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use PrimitiveType::*;
175    use TypeDecl::*;
176    use alloc::{boxed::Box, vec};
177    use sails_reflect_hash::ReflectHash;
178
179    macro_rules! assert_type_decl {
180        ($ty: ty, $p: expr) => {
181            assert_eq!(
182                <$ty as ReflectHash>::HASH,
183                hash_type_decl(&$p, &BTreeMap::new(), None).unwrap()
184            );
185        };
186
187        ($ty: ty, $p: expr, $map: expr) => {
188            assert_eq!(
189                <$ty as ReflectHash>::HASH,
190                hash_type_decl(&$p, &$map, None).unwrap()
191            );
192        };
193    }
194
195    #[test]
196    fn hash_primitive() {
197        assert_type_decl!((), Primitive(Void));
198        assert_type_decl!(bool, Primitive(Bool));
199        assert_type_decl!(char, Primitive(Char));
200        assert_type_decl!(str, Primitive(String));
201
202        assert_type_decl!(u8, Primitive(U8));
203        assert_type_decl!(u16, Primitive(U16));
204        assert_type_decl!(u32, Primitive(U32));
205        assert_type_decl!(u64, Primitive(U64));
206        assert_type_decl!(u128, Primitive(U128));
207
208        assert_type_decl!(i8, Primitive(I8));
209        assert_type_decl!(i16, Primitive(I16));
210        assert_type_decl!(i32, Primitive(I32));
211        assert_type_decl!(i64, Primitive(I64));
212        assert_type_decl!(i128, Primitive(I128));
213
214        assert_type_decl!(gprimitives::ActorId, Primitive(ActorId));
215        assert_type_decl!(gprimitives::CodeId, Primitive(CodeId));
216        assert_type_decl!(gprimitives::MessageId, Primitive(MessageId));
217
218        assert_type_decl!(gprimitives::H160, Primitive(H160));
219        assert_type_decl!(gprimitives::H256, Primitive(H256));
220        assert_type_decl!(gprimitives::U256, Primitive(U256));
221    }
222
223    #[test]
224    fn hash_slice() {
225        assert_type_decl!(
226            [u8],
227            Slice {
228                item: Box::new(Primitive(U8))
229            }
230        );
231        assert_type_decl!(
232            Vec<u8>,
233            Slice {
234                item: Box::new(Primitive(U8))
235            }
236        );
237        assert_type_decl!(
238            Vec<(u8, &str)>,
239            Slice {
240                item: Box::new(Tuple {
241                    types: vec![Primitive(U8), Primitive(String)]
242                })
243            }
244        );
245    }
246
247    #[test]
248    fn hash_array() {
249        assert_type_decl!(
250            [u8; 32],
251            Array {
252                item: Box::new(Primitive(U8)),
253                len: 32
254            }
255        );
256        assert_type_decl!(
257            [(u8, &str); 4],
258            Array {
259                item: Box::new(Tuple {
260                    types: vec![Primitive(U8), Primitive(String)]
261                }),
262                len: 4
263            }
264        );
265    }
266
267    #[test]
268    fn hash_tuple() {
269        assert_type_decl!(
270            (u8, &str),
271            Tuple {
272                types: vec![Primitive(U8), Primitive(String)]
273            }
274        );
275        assert_type_decl!(
276            (u8, &str, [u8; 32]),
277            Tuple {
278                types: vec![
279                    Primitive(U8),
280                    Primitive(String),
281                    Array {
282                        item: Box::new(Primitive(U8)),
283                        len: 32
284                    }
285                ]
286            }
287        );
288    }
289
290    #[test]
291    fn hash_option() {
292        assert_type_decl!(
293            Option<u8>,
294            Named {
295                name: "Option".to_string(),
296                generics: vec![Primitive(U8)]
297            }
298        );
299        assert_type_decl!(
300            Option<(u8, &str, [u8; 32])>,
301            Named {
302                name: "Option".to_string(),
303                generics: vec![Tuple {
304                    types: vec![
305                        Primitive(U8),
306                        Primitive(String),
307                        Array {
308                            item: Box::new(Primitive(U8)),
309                            len: 32
310                        }
311                    ]
312                }]
313            }
314        );
315    }
316
317    #[test]
318    fn hash_result() {
319        assert_type_decl!(
320            Result<u8, &str>,
321            Named {
322                name: "Result".to_string(),
323                generics: vec![Primitive(U8), Primitive(String)]
324            }
325        );
326        assert_type_decl!(
327            Result<(u8, &str, [u8; 32]), ()>,
328            Named {
329                name: "Result".to_string(),
330                generics: vec![Tuple {
331                    types: vec![
332                        Primitive(U8),
333                        Primitive(String),
334                        Array {
335                            item: Box::new(Primitive(U8)),
336                            len: 32
337                        }
338                    ]
339                }, Primitive(Void)]
340            }
341        );
342    }
343
344    #[test]
345    fn hash_struct_unit() {
346        #[derive(ReflectHash)]
347        struct UnitStruct;
348
349        let mut map = BTreeMap::new();
350        let ty = Type {
351            name: "UnitStruct".to_string(),
352            type_params: vec![],
353            def: TypeDef::Struct(StructDef { fields: vec![] }),
354            docs: vec![],
355            annotations: vec![],
356        };
357        map.insert("UnitStruct", &ty);
358
359        assert_type_decl!(
360            UnitStruct,
361            Named {
362                name: "UnitStruct".to_string(),
363                generics: vec![]
364            },
365            map
366        );
367    }
368
369    #[test]
370    fn hash_struct_tuple() {
371        #[derive(ReflectHash)]
372        #[allow(unused)]
373        struct TupleStruct(u32);
374
375        let mut map = BTreeMap::new();
376        let ty = Type {
377            name: "TupleStruct".to_string(),
378            type_params: vec![],
379            def: TypeDef::Struct(StructDef {
380                fields: vec![StructField {
381                    name: None,
382                    type_decl: Primitive(U32),
383                    docs: vec![],
384                    annotations: vec![],
385                }],
386            }),
387            docs: vec![],
388            annotations: vec![],
389        };
390        map.insert("TupleStruct", &ty);
391
392        assert_type_decl!(
393            TupleStruct,
394            Named {
395                name: "TupleStruct".to_string(),
396                generics: vec![]
397            },
398            map
399        );
400    }
401
402    #[test]
403    fn hash_struct_named() {
404        #[derive(ReflectHash)]
405        #[allow(unused)]
406        struct NamedStruct {
407            f1: u32,
408            f2: Option<&'static str>,
409        }
410
411        let mut map = BTreeMap::new();
412        let ty = Type {
413            name: "NamedStruct".to_string(),
414            type_params: vec![],
415            def: TypeDef::Struct(StructDef {
416                fields: vec![
417                    StructField {
418                        name: Some("f1".to_string()),
419                        type_decl: Primitive(U32),
420                        docs: vec![],
421                        annotations: vec![],
422                    },
423                    StructField {
424                        name: Some("f2".to_string()),
425                        type_decl: Named {
426                            name: "Option".to_string(),
427                            generics: vec![Primitive(String)],
428                        },
429                        docs: vec![],
430                        annotations: vec![],
431                    },
432                ],
433            }),
434            docs: vec![],
435            annotations: vec![],
436        };
437        map.insert("NamedStruct", &ty);
438
439        assert_type_decl!(
440            NamedStruct,
441            Named {
442                name: "NamedStruct".to_string(),
443                generics: vec![]
444            },
445            map
446        );
447    }
448
449    #[test]
450    fn hash_struct_generics() {
451        #[derive(ReflectHash)]
452        #[allow(unused)]
453        struct GenericStruct<T1: ReflectHash, T2: ReflectHash> {
454            f1: T1,
455            f2: Option<T2>,
456        }
457
458        let mut map = BTreeMap::new();
459        let ty = Type {
460            name: "GenericStruct".to_string(),
461            type_params: vec![
462                TypeParameter {
463                    name: "T1".to_string(),
464                    ty: None,
465                },
466                TypeParameter {
467                    name: "T2".to_string(),
468                    ty: None,
469                },
470            ],
471            def: TypeDef::Struct(StructDef {
472                fields: vec![
473                    StructField {
474                        name: Some("f1".to_string()),
475                        type_decl: Generic {
476                            name: "T1".to_string(),
477                        },
478                        docs: vec![],
479                        annotations: vec![],
480                    },
481                    StructField {
482                        name: Some("f2".to_string()),
483                        type_decl: Named {
484                            name: "Option".to_string(),
485                            generics: vec![Generic {
486                                name: "T2".to_string(),
487                            }],
488                        },
489                        docs: vec![],
490                        annotations: vec![],
491                    },
492                ],
493            }),
494            docs: vec![],
495            annotations: vec![],
496        };
497        map.insert("GenericStruct", &ty);
498
499        let ty_u8_str = Named {
500            name: "GenericStruct".to_string(),
501            generics: vec![Primitive(U8), Primitive(String)],
502        };
503        let ty_str_u8 = Named {
504            name: "GenericStruct".to_string(),
505            generics: vec![Primitive(String), Primitive(U8)],
506        };
507
508        assert_ne!(
509            hash_type_decl(&ty_u8_str, &map, None),
510            hash_type_decl(&ty_str_u8, &map, None)
511        );
512
513        assert_type_decl!(
514            GenericStruct<u8, &str>,
515            Named {
516                name: "GenericStruct".to_string(),
517                generics: vec![Primitive(U8), Primitive(String)],
518            },
519            map
520        );
521
522        assert_type_decl!(
523            GenericStruct<&str, u8>,
524            Named {
525                name: "GenericStruct".to_string(),
526                generics: vec![Primitive(String), Primitive(U8)],
527            },
528            map
529        );
530    }
531
532    #[test]
533    fn hash_unresolved_generic_errors() {
534        let err = hash_type_decl(
535            &Generic {
536                name: "T".to_string(),
537            },
538            &BTreeMap::new(),
539            None,
540        )
541        .expect_err("unresolved Generic must not hash as a named type");
542
543        assert_eq!(err, "generic type parameter `T` must be resolved");
544    }
545
546    #[test]
547    fn hash_alias_is_same_as_target() {
548        let mut map = BTreeMap::new();
549        let target = TypeDecl::Primitive(PrimitiveType::U32);
550        let alias = Type {
551            name: "MyAlias".to_string(),
552            type_params: vec![],
553            def: TypeDef::Alias(AliasDef {
554                target: target.clone(),
555            }),
556            docs: vec![],
557            annotations: vec![],
558        };
559        map.insert("MyAlias", &alias);
560
561        let struct_hash = hash_type_decl(&target, &map, None).unwrap();
562        let alias_decl = TypeDecl::Named {
563            name: "MyAlias".to_string(),
564            generics: vec![],
565        };
566        let alias_hash = hash_type_decl(&alias_decl, &map, None).unwrap();
567
568        assert_eq!(struct_hash, alias_hash);
569    }
570}