ink_primitives/
key.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 xxhash_rust::const_xxh32::xxh32;
16
17/// The value 0 is a valid seed.
18const XXH32_SEED: u32 = 0;
19
20/// A key into the smart contract storage.
21///
22/// # Note
23///
24/// - The storage of an ink! smart contract can be viewed as a key-value store.
25/// - In order to manipulate its storage an ink! smart contract is required to indicate
26///   the respective cells using this primitive type.
27/// - The `Key` type can be compared to a raw pointer and also allows operations similar
28///   to pointer arithmetic.
29pub type Key = u32;
30
31/// Contains all rules related to storage key creation.
32pub struct KeyComposer;
33
34impl KeyComposer {
35    /// Concatenate two `Key` into one during compilation.
36    pub const fn concat(left: Key, right: Key) -> Key {
37        // If one of the keys is zero, then return another without hashing.
38        // If both keys are non-zero, return the hash of the XOR difference of both keys.
39        match (left, right) {
40            (0, 0) => 0,
41            (0, _) => right,
42            (_, 0) => left,
43            (left, right) => xxh32(&(left ^ right).to_be_bytes(), XXH32_SEED),
44        }
45    }
46
47    /// Return the storage key from the supplied `str`.
48    pub const fn from_str(str: &str) -> Key {
49        Self::from_bytes(str.as_bytes())
50    }
51
52    /// Returns the storage key from the supplied `bytes`.
53    pub const fn from_bytes(bytes: &[u8]) -> Key {
54        if bytes.is_empty() {
55            return 0
56        }
57
58        xxh32(bytes, XXH32_SEED)
59    }
60
61    /// Evaluates the storage key of the field in the structure, variant or union.
62    ///
63    /// 1. Compute the ASCII byte representation of `struct_name` and call it `S`.
64    /// 1. If `variant_name` is not empty then computes the ASCII byte representation and
65    ///    call it `V`. 1. Compute the ASCII byte representation of `field_name` and call
66    ///    it `F`. 1. Concatenate (`S` and `F`) or (`S`, `V` and `F`) using `::` as
67    ///    separator and call it `C`. 1. The `XXH32` hash of `C` is the storage key.
68    ///
69    /// # Note
70    ///
71    /// - `variant_name` is empty for structures and unions.
72    /// - if the field is unnamed then `field_name` is `"{}"` where `{}` is a number of
73    ///   the field.
74    pub fn compute_key(
75        struct_name: &str,
76        variant_name: &str,
77        field_name: &str,
78    ) -> Result<Key, Error> {
79        if struct_name.is_empty() {
80            return Err(Error::StructNameIsEmpty)
81        }
82        if field_name.is_empty() {
83            return Err(Error::FieldNameIsEmpty)
84        }
85
86        let separator = &b"::"[..];
87        let composed_key = if !variant_name.is_empty() {
88            [
89                struct_name.as_bytes(),
90                variant_name.as_bytes(),
91                field_name.as_bytes(),
92            ]
93            .join(separator)
94        } else {
95            [struct_name.as_bytes(), field_name.as_bytes()].join(separator)
96        };
97
98        Ok(Self::from_bytes(composed_key.as_slice()))
99    }
100}
101
102/// Possible errors during the computation of the storage key.
103#[derive(Debug, PartialEq, Eq)]
104pub enum Error {
105    StructNameIsEmpty,
106    FieldNameIsEmpty,
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn concat_works_correct() {
115        assert_eq!(KeyComposer::concat(0, 13), 13);
116        assert_eq!(KeyComposer::concat(31, 0), 31);
117        assert_eq!(KeyComposer::concat(31, 13), 0x9ab19a67);
118        assert_eq!(KeyComposer::concat(0, 0), 0);
119    }
120
121    #[test]
122    fn from_str_works_correct() {
123        assert_eq!(KeyComposer::from_str(""), 0);
124        assert_eq!(KeyComposer::from_str("123"), 0xb6855437);
125        assert_eq!(KeyComposer::from_str("Hello world"), 0x9705d437);
126    }
127
128    #[test]
129    fn from_bytes_works_correct() {
130        assert_eq!(KeyComposer::from_bytes(b""), 0);
131        assert_eq!(KeyComposer::from_bytes(b"123"), 0xb6855437);
132        assert_eq!(KeyComposer::from_bytes(b"Hello world"), 0x9705d437);
133    }
134
135    #[test]
136    fn compute_key_works_correct() {
137        assert_eq!(
138            KeyComposer::compute_key("Contract", "", "balances"),
139            Ok(0xf820ff02)
140        );
141        assert_eq!(
142            KeyComposer::compute_key("Enum", "Variant", "0"),
143            Ok(0x14786b51)
144        );
145        assert_eq!(
146            KeyComposer::compute_key("", "Variant", "0"),
147            Err(Error::StructNameIsEmpty)
148        );
149        assert_eq!(
150            KeyComposer::compute_key("Enum", "Variant", ""),
151            Err(Error::FieldNameIsEmpty)
152        );
153    }
154}