use crate::fuel_prelude::{
fuel_crypto::Hasher,
fuel_tx::StorageSlot,
fuel_types::{Bytes32, Bytes8},
};
use sway_ir::{
constant::{ConstantContent, ConstantValue},
context::Context,
irtype::Type,
Constant,
};
use sway_types::u256::U256;
#[derive(Default)]
enum InByte8Padding {
#[default]
Right,
Left,
}
pub(super) fn get_storage_key(storage_field_names: Vec<String>, key: Option<U256>) -> Bytes32 {
match key {
Some(key) => key.to_be_bytes().into(),
None => hash_storage_key_string(&get_storage_key_string(&storage_field_names)),
}
}
pub fn get_storage_key_string(storage_field_names: &[String]) -> String {
if storage_field_names.len() == 1 {
format!(
"{}{}{}",
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_FIELD_SEPARATOR,
storage_field_names.last().unwrap(),
)
} else {
format!(
"{}{}{}{}{}",
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR,
storage_field_names
.iter()
.take(storage_field_names.len() - 1)
.cloned()
.collect::<Vec<_>>()
.join(sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR),
sway_utils::constants::STORAGE_FIELD_SEPARATOR,
storage_field_names.last().unwrap(),
)
}
}
pub(super) fn get_storage_field_path_and_field_id(
storage_field_names: &[String],
struct_field_names: &[String],
) -> (String, Bytes32) {
let path = format!(
"{}{}",
get_storage_key_string(storage_field_names),
if struct_field_names.is_empty() {
"".to_string()
} else {
format!(
"{}{}",
sway_utils::constants::STRUCT_FIELD_SEPARATOR,
struct_field_names.join(sway_utils::constants::STRUCT_FIELD_SEPARATOR),
)
}
);
let id = hash_storage_key_string(&path);
(path, id)
}
fn hash_storage_key_string(storage_key_string: &str) -> Bytes32 {
let mut hasher = Hasher::default();
hasher.input(sway_utils::constants::STORAGE_DOMAIN);
hasher.input(storage_key_string);
hasher.finalize()
}
use uint::construct_uint;
#[allow(
// These warnings are generated by the `construct_uint!()` macro below.
clippy::assign_op_pattern,
clippy::ptr_offset_with_cast,
clippy::manual_div_ceil
)]
pub(super) fn add_to_b256(x: Bytes32, y: u64) -> Bytes32 {
construct_uint! {
struct U256(4);
}
let x = U256::from(*x);
let y = U256::from(y);
let res: [u8; 32] = (x + y).into();
Bytes32::from(res)
}
pub fn serialize_to_storage_slots(
constant: &Constant,
context: &Context,
storage_field_names: Vec<String>,
key: Option<U256>,
ty: &Type,
) -> Vec<StorageSlot> {
match &constant.get_content(context).value {
ConstantValue::Undef => vec![],
ConstantValue::Unit if ty.is_unit(context) => vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new([0; 32]),
)],
ConstantValue::Bool(b) if ty.is_bool(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new([
if *b { 1 } else { 0 },
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]),
)]
}
ConstantValue::Uint(b) if ty.is_uint8(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new([
*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,
0, 0, 0, 0, 0, 0, 0, 0,
]),
)]
}
ConstantValue::Uint(n) if ty.is_uint(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new(
n.to_be_bytes()
.iter()
.cloned()
.chain([0; 24].iter().cloned())
.collect::<Vec<u8>>()
.try_into()
.unwrap(),
),
)]
}
ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new(b.to_be_bytes()),
)]
}
ConstantValue::B256(b) if ty.is_b256(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
Bytes32::new(b.to_be_bytes()),
)]
}
ConstantValue::Array(_a) if ty.is_array(context) => {
unimplemented!("Arrays in storage have not been implemented yet.")
}
_ if ty.is_string_array(context) || ty.is_struct(context) || ty.is_union(context) => {
let mut packed = serialize_to_words(
constant.get_content(context),
context,
ty,
InByte8Padding::default(),
);
packed.extend(vec![
Bytes8::new([0; 8]);
packed.len().div_ceil(4) * 4 - packed.len()
]);
assert!(packed.len().is_multiple_of(4));
let type_size_in_bytes = ty.size(context).in_bytes();
if !context.experimental.str_array_no_padding {
assert!(
type_size_in_bytes.is_multiple_of(8),
"Expected string arrays, structs, and enums to be aligned to word boundary. The type size in bytes was {} and the type was {}.",
type_size_in_bytes,
ty.as_string(context)
);
}
let storage_key = get_storage_key(storage_field_names, key);
(0..type_size_in_bytes.div_ceil(32))
.map(|i| add_to_b256(storage_key, i))
.zip((0..packed.len() / 4).map(|i| {
Bytes32::new(
Vec::from_iter((0..4).flat_map(|j| *packed[4 * i + j]))
.try_into()
.unwrap(),
)
}))
.map(|(k, r)| StorageSlot::new(k, r))
.collect()
}
_ => vec![],
}
}
fn serialize_to_words(
constant: &ConstantContent,
context: &Context,
ty: &Type,
padding: InByte8Padding,
) -> Vec<Bytes8> {
match &constant.value {
ConstantValue::Undef => vec![],
ConstantValue::Unit if ty.is_unit(context) => vec![Bytes8::new([0; 8])],
ConstantValue::Bool(b) if ty.is_bool(context) => match padding {
InByte8Padding::Right => {
vec![Bytes8::new([if *b { 1 } else { 0 }, 0, 0, 0, 0, 0, 0, 0])]
}
InByte8Padding::Left => {
vec![Bytes8::new([0, 0, 0, 0, 0, 0, 0, if *b { 1 } else { 0 }])]
}
},
ConstantValue::Uint(n) if ty.is_uint8(context) => match padding {
InByte8Padding::Right => vec![Bytes8::new([*n as u8, 0, 0, 0, 0, 0, 0, 0])],
InByte8Padding::Left => vec![Bytes8::new([0, 0, 0, 0, 0, 0, 0, *n as u8])],
},
ConstantValue::Uint(n) if ty.is_uint(context) => {
vec![Bytes8::new(n.to_be_bytes())]
}
ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
let b = b.to_be_bytes();
Vec::from_iter((0..4).map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap())))
}
ConstantValue::B256(b) if ty.is_b256(context) => {
let b = b.to_be_bytes();
Vec::from_iter((0..4).map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap())))
}
ConstantValue::String(s) if ty.is_string_array(context) => {
let mut s = s.clone();
s.extend(vec![0; s.len().div_ceil(8) * 8 - s.len()]);
assert!(s.len() % 8 == 0);
Vec::from_iter((0..s.len() / 8).map(|i| {
Bytes8::new(
Vec::from_iter((0..8).map(|j| s[8 * i + j]))
.try_into()
.unwrap(),
)
}))
}
ConstantValue::Array(_) if ty.is_array(context) => {
unimplemented!("Arrays in storage have not been implemented yet.")
}
ConstantValue::Struct(vec) if ty.is_struct(context) => {
let field_tys = ty.get_field_types(context);
vec.iter()
.zip(field_tys.iter())
.flat_map(|(f, ty)| serialize_to_words(f, context, ty, InByte8Padding::Right))
.collect()
}
_ if ty.is_union(context) => {
let value_size_in_words = ty.size(context).in_words();
let constant_size_in_words = constant.ty.size(context).in_words();
assert!(value_size_in_words >= constant_size_in_words);
let padding_size_in_words = value_size_in_words - constant_size_in_words;
vec![Bytes8::new([0; 8]); padding_size_in_words as usize]
.iter()
.cloned()
.chain(
serialize_to_words(constant, context, &constant.ty, InByte8Padding::Left)
.iter()
.cloned(),
)
.collect()
}
_ => vec![],
}
}