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