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