sway_core/ir_generation/
storage.rs

1use crate::fuel_prelude::{
2    fuel_crypto::Hasher,
3    fuel_tx::StorageSlot,
4    fuel_types::{Bytes32, Bytes8},
5};
6use sway_ir::{
7    constant::{ConstantContent, ConstantValue},
8    context::Context,
9    irtype::Type,
10    Constant,
11};
12use sway_types::u256::U256;
13
14/// Determines how values that are less then a word in length
15/// has to be padded to word boundary when in structs or enums.
16#[derive(Default)]
17enum InByte8Padding {
18    #[default]
19    Right,
20    Left,
21}
22
23/// Hands out storage keys using storage field names or an existing key.
24/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>"))
25/// or key if defined.
26pub(super) fn get_storage_key(storage_field_names: Vec<String>, key: Option<U256>) -> Bytes32 {
27    match key {
28        Some(key) => key.to_be_bytes().into(),
29        None => hash_storage_key_string(&get_storage_key_string(&storage_field_names)),
30    }
31}
32
33pub fn get_storage_key_string(storage_field_names: &[String]) -> String {
34    if storage_field_names.len() == 1 {
35        format!(
36            "{}{}{}",
37            sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
38            sway_utils::constants::STORAGE_FIELD_SEPARATOR,
39            storage_field_names.last().unwrap(),
40        )
41    } else {
42        format!(
43            "{}{}{}{}{}",
44            sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
45            sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR,
46            storage_field_names
47                .iter()
48                .take(storage_field_names.len() - 1)
49                .cloned()
50                .collect::<Vec<_>>()
51                .join(sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR),
52            sway_utils::constants::STORAGE_FIELD_SEPARATOR,
53            storage_field_names.last().unwrap(),
54        )
55    }
56}
57
58/// Hands out unique storage field ids using storage field names and struct field names.
59/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>.<struct_field_name1>.<struct_field_name2>")).
60pub(super) fn get_storage_field_path_and_field_id(
61    storage_field_names: &[String],
62    struct_field_names: &[String],
63) -> (String, Bytes32) {
64    let path = format!(
65        "{}{}",
66        get_storage_key_string(storage_field_names),
67        if struct_field_names.is_empty() {
68            "".to_string()
69        } else {
70            format!(
71                "{}{}",
72                sway_utils::constants::STRUCT_FIELD_SEPARATOR,
73                struct_field_names.join(sway_utils::constants::STRUCT_FIELD_SEPARATOR),
74            )
75        }
76    );
77
78    let id = hash_storage_key_string(&path);
79    (path, id)
80}
81
82fn hash_storage_key_string(storage_key_string: &str) -> Bytes32 {
83    let mut hasher = Hasher::default();
84    // Certain storage types, like, e.g., `StorageMap` allow
85    // storage slots of their contained elements to be defined
86    // based on developer's input. E.g., the `key` in a `StorageMap`
87    // used to calculate the storage slot is a developer input.
88    //
89    // To ensure that pre-images of such storage slots can never
90    // be the same as a pre-image of compiler generated key of storage
91    // field, we prefix the pre-images with a single byte that denotes
92    // the domain. Storage types like `StorageMap` must have a different
93    // domain prefix than the `STORAGE_DOMAIN` which is 0u8.
94    //
95    // For detailed elaboration see: https://github.com/FuelLabs/sway/issues/6317
96    hasher.input(sway_utils::constants::STORAGE_DOMAIN);
97    hasher.input(storage_key_string);
98    hasher.finalize()
99}
100
101use uint::construct_uint;
102
103#[allow(
104// These warnings are generated by the `construct_uint!()` macro below.
105    clippy::assign_op_pattern,
106    clippy::ptr_offset_with_cast,
107    clippy::manual_div_ceil
108)]
109pub(super) fn add_to_b256(x: Bytes32, y: u64) -> Bytes32 {
110    construct_uint! {
111        struct U256(4);
112    }
113    let x = U256::from(*x);
114    let y = U256::from(y);
115    let res: [u8; 32] = (x + y).into();
116    Bytes32::from(res)
117}
118
119/// Given a constant value `constant`, a type `ty`, a state index, and a vector of subfield
120/// indices, serialize the constant into a vector of storage slots. The keys (slots) are
121/// generated using the state index and the subfield indices which are recursively built. The
122/// values are generated such that each subfield gets its own storage slot except for enums and
123/// strings which are spread over successive storage slots (use `serialize_to_words` in this case).
124///
125/// This behavior matches the behavior of how storage slots are assigned for storage reads and
126/// writes (i.e. how `state_read_*` and `state_write_*` instructions are generated).
127pub fn serialize_to_storage_slots(
128    constant: &Constant,
129    context: &Context,
130    storage_field_names: Vec<String>,
131    key: Option<U256>,
132    ty: &Type,
133) -> Vec<StorageSlot> {
134    match &constant.get_content(context).value {
135        ConstantValue::Undef => vec![],
136        // If not being a part of an aggregate, single byte values like `bool`, `u8`, and unit
137        // are stored as a byte at the beginning of the storage slot.
138        ConstantValue::Unit if ty.is_unit(context) => vec![StorageSlot::new(
139            get_storage_key(storage_field_names, key),
140            Bytes32::new([0; 32]),
141        )],
142        ConstantValue::Bool(b) if ty.is_bool(context) => {
143            vec![StorageSlot::new(
144                get_storage_key(storage_field_names, key),
145                Bytes32::new([
146                    if *b { 1 } else { 0 },
147                    0,
148                    0,
149                    0,
150                    0,
151                    0,
152                    0,
153                    0,
154                    0,
155                    0,
156                    0,
157                    0,
158                    0,
159                    0,
160                    0,
161                    0,
162                    0,
163                    0,
164                    0,
165                    0,
166                    0,
167                    0,
168                    0,
169                    0,
170                    0,
171                    0,
172                    0,
173                    0,
174                    0,
175                    0,
176                    0,
177                    0,
178                ]),
179            )]
180        }
181        ConstantValue::Uint(b) if ty.is_uint8(context) => {
182            vec![StorageSlot::new(
183                get_storage_key(storage_field_names, key),
184                Bytes32::new([
185                    *b as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
186                    0, 0, 0, 0, 0, 0, 0, 0,
187                ]),
188            )]
189        }
190        // Similarly, other uint values are stored at the beginning of the storage slot.
191        ConstantValue::Uint(n) if ty.is_uint(context) => {
192            vec![StorageSlot::new(
193                get_storage_key(storage_field_names, key),
194                Bytes32::new(
195                    n.to_be_bytes()
196                        .iter()
197                        .cloned()
198                        .chain([0; 24].iter().cloned())
199                        .collect::<Vec<u8>>()
200                        .try_into()
201                        .unwrap(),
202                ),
203            )]
204        }
205        ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
206            vec![StorageSlot::new(
207                get_storage_key(storage_field_names, key),
208                Bytes32::new(b.to_be_bytes()),
209            )]
210        }
211        ConstantValue::B256(b) if ty.is_b256(context) => {
212            vec![StorageSlot::new(
213                get_storage_key(storage_field_names, key),
214                Bytes32::new(b.to_be_bytes()),
215            )]
216        }
217        ConstantValue::Array(_a) if ty.is_array(context) => {
218            unimplemented!("Arrays in storage have not been implemented yet.")
219        }
220        _ if ty.is_string_array(context) || ty.is_struct(context) || ty.is_union(context) => {
221            // Serialize the constant data in words and add zero words until the number of words
222            // is a multiple of 4. This is useful because each storage slot is 4 words.
223            // Regarding padding, the top level type in the call is either a string array, struct, or
224            // a union. They will properly set the initial padding for the further recursive calls.
225            let mut packed = serialize_to_words(
226                constant.get_content(context),
227                context,
228                ty,
229                InByte8Padding::default(),
230            );
231            packed.extend(vec![
232                Bytes8::new([0; 8]);
233                packed.len().div_ceil(4) * 4 - packed.len()
234            ]);
235
236            assert!(packed.len().is_multiple_of(4));
237
238            // Return a list of `StorageSlot`s
239            // First get the keys then get the values
240            // TODO-MEMLAY: Warning! Here we make an assumption about the memory layout of
241            //       string arrays, structs, and enum.
242            //       The assumption is that they are rounded to word boundaries
243            //       which will very likely always be the case.
244            //       We will not refactor the Storage API at the moment to remove this
245            //       assumption. It is a questionable effort because we anyhow
246            //       want to improve and refactor Storage API in the future.
247            let type_size_in_bytes = ty.size(context).in_bytes();
248            assert!(
249                type_size_in_bytes.is_multiple_of(8),
250                "Expected string arrays, structs, and enums to be aligned to word boundary. The type size in bytes was {} and the type was {}.",
251                type_size_in_bytes,
252                ty.as_string(context)
253            );
254
255            let storage_key = get_storage_key(storage_field_names, key);
256            (0..type_size_in_bytes.div_ceil(32))
257                .map(|i| add_to_b256(storage_key, i))
258                .zip((0..packed.len() / 4).map(|i| {
259                    Bytes32::new(
260                        Vec::from_iter((0..4).flat_map(|j| *packed[4 * i + j]))
261                            .try_into()
262                            .unwrap(),
263                    )
264                }))
265                .map(|(k, r)| StorageSlot::new(k, r))
266                .collect()
267        }
268        _ => vec![],
269    }
270}
271
272/// Given a constant value `constant` and a type `ty`, serialize the constant into a vector of
273/// words and apply the requested padding if needed.
274fn serialize_to_words(
275    constant: &ConstantContent,
276    context: &Context,
277    ty: &Type,
278    padding: InByte8Padding,
279) -> Vec<Bytes8> {
280    match &constant.value {
281        ConstantValue::Undef => vec![],
282        ConstantValue::Unit if ty.is_unit(context) => vec![Bytes8::new([0; 8])],
283        ConstantValue::Bool(b) if ty.is_bool(context) => match padding {
284            InByte8Padding::Right => {
285                vec![Bytes8::new([if *b { 1 } else { 0 }, 0, 0, 0, 0, 0, 0, 0])]
286            }
287            InByte8Padding::Left => {
288                vec![Bytes8::new([0, 0, 0, 0, 0, 0, 0, if *b { 1 } else { 0 }])]
289            }
290        },
291        ConstantValue::Uint(n) if ty.is_uint8(context) => match padding {
292            InByte8Padding::Right => vec![Bytes8::new([*n as u8, 0, 0, 0, 0, 0, 0, 0])],
293            InByte8Padding::Left => vec![Bytes8::new([0, 0, 0, 0, 0, 0, 0, *n as u8])],
294        },
295        ConstantValue::Uint(n) if ty.is_uint(context) => {
296            vec![Bytes8::new(n.to_be_bytes())]
297        }
298        ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
299            let b = b.to_be_bytes();
300            Vec::from_iter((0..4).map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap())))
301        }
302        ConstantValue::B256(b) if ty.is_b256(context) => {
303            let b = b.to_be_bytes();
304            Vec::from_iter((0..4).map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap())))
305        }
306        ConstantValue::String(s) if ty.is_string_array(context) => {
307            // Turn the bytes into serialized words (Bytes8) and right pad it to the word boundary.
308            let mut s = s.clone();
309            s.extend(vec![0; s.len().div_ceil(8) * 8 - s.len()]);
310
311            assert!(s.len() % 8 == 0);
312
313            // Group into words.
314            Vec::from_iter((0..s.len() / 8).map(|i| {
315                Bytes8::new(
316                    Vec::from_iter((0..8).map(|j| s[8 * i + j]))
317                        .try_into()
318                        .unwrap(),
319                )
320            }))
321        }
322        ConstantValue::Array(_) if ty.is_array(context) => {
323            unimplemented!("Arrays in storage have not been implemented yet.")
324        }
325        ConstantValue::Struct(vec) if ty.is_struct(context) => {
326            let field_tys = ty.get_field_types(context);
327            vec.iter()
328                .zip(field_tys.iter())
329                // TODO-MEMLAY: Warning! Again, making an assumption about the memory layout
330                //       of struct fields.
331                .flat_map(|(f, ty)| serialize_to_words(f, context, ty, InByte8Padding::Right))
332                .collect()
333        }
334        _ if ty.is_union(context) => {
335            let value_size_in_words = ty.size(context).in_words();
336            let constant_size_in_words = constant.ty.size(context).in_words();
337            assert!(value_size_in_words >= constant_size_in_words);
338
339            // Add enough left padding to satisfy the actual size of the union
340            // TODO-MEMLAY: Warning! Here we make an assumption about the memory layout of enums,
341            //       that they are left padded.
342            //       The memory layout of enums can be changed in the future.
343            //       We will not refactor the Storage API at the moment to remove this
344            //       assumption. It is a questionable effort because we anyhow
345            //       want to improve and refactor Storage API in the future.
346            let padding_size_in_words = value_size_in_words - constant_size_in_words;
347            vec![Bytes8::new([0; 8]); padding_size_in_words as usize]
348                .iter()
349                .cloned()
350                .chain(
351                    serialize_to_words(constant, context, &constant.ty, InByte8Padding::Left)
352                        .iter()
353                        .cloned(),
354                )
355                .collect()
356        }
357        _ => vec![],
358    }
359}