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}