Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage domains #6466

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4266,6 +4266,14 @@ impl<'eng> FnCompiler<'eng> {
base_type: &Type,
span_md_idx: Option<MetadataIndex>,
) -> Result<TerminatorValue, CompileError> {
// Use the `struct_field_names` to get a field id that is unique even for zero-sized values that live in the same slot.
// We calculate the `unique_field_id` early, here, before the `storage_filed_names` get consumed by `get_storage_key` below.
let unique_field_id = get_storage_field_id(
&storage_field_names,
&struct_field_names,
context.experimental,
);

// Get the actual storage key as a `Bytes32` as well as the offset, in words,
// within the slot. The offset depends on what field of the top level storage
// variable is being accessed.
Expand Down Expand Up @@ -4299,7 +4307,7 @@ impl<'eng> FnCompiler<'eng> {
// particular slot is the remaining offset, in words.
(
add_to_b256(
get_storage_key(storage_field_names.clone(), key.clone()),
get_storage_key(storage_field_names, key, context.experimental),
offset_in_slots,
),
offset_remaining,
Expand Down Expand Up @@ -4356,7 +4364,6 @@ impl<'eng> FnCompiler<'eng> {
.add_metadatum(context, span_md_idx);

// Store the field identifier as the third field in the `StorageKey` struct
let unique_field_id = get_storage_field_id(storage_field_names, struct_field_names); // use the struct_field_names to get a field id that is unique even for zero-sized values that live in the same slot
let field_id = convert_literal_to_value(context, &Literal::B256(unique_field_id.into()))
.add_metadatum(context, span_md_idx);
let gep_2_val =
Expand Down
73 changes: 53 additions & 20 deletions sway-core/src/ir_generation/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::fuel_prelude::{
fuel_tx::StorageSlot,
fuel_types::{Bytes32, Bytes8},
};
use sway_features::ExperimentalFeatures;
use sway_ir::{
constant::{Constant, ConstantValue},
context::Context,
Expand All @@ -20,28 +21,31 @@ enum InByte8Padding {
}

/// Hands out storage keys using storage field names or an existing key.
/// Basically returns sha256("storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>")
/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>"))
/// or key if defined.
pub(super) fn get_storage_key(storage_field_names: Vec<String>, key: Option<U256>) -> Bytes32 {
if let Some(key) = key {
return key.to_be_bytes().into();
pub(super) fn get_storage_key(
storage_field_names: Vec<String>,
key: Option<U256>,
experimental: ExperimentalFeatures,
) -> Bytes32 {
match key {
Some(key) => key.to_be_bytes().into(),
None => hash_storage_key_string(get_storage_key_string(&storage_field_names), experimental),
}

Hasher::hash(get_storage_key_string(storage_field_names))
}

pub fn get_storage_key_string(storage_field_names: Vec<String>) -> String {
pub fn get_storage_key_string(storage_field_names: &[String]) -> String {
if storage_field_names.len() == 1 {
format!(
"{}{}{}",
sway_utils::constants::STORAGE_DOMAIN,
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_FIELD_SEPARATOR,
storage_field_names.last().unwrap(),
)
} else {
format!(
"{}{}{}{}{}",
sway_utils::constants::STORAGE_DOMAIN,
sway_utils::constants::STORAGE_TOP_LEVEL_NAMESPACE,
sway_utils::constants::STORAGE_NAMESPACE_SEPARATOR,
storage_field_names
.iter()
Expand All @@ -56,10 +60,11 @@ pub fn get_storage_key_string(storage_field_names: Vec<String>) -> String {
}

/// Hands out unique storage field ids using storage field names and struct field names.
/// Basically returns sha256("storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>.<struct_field_name1>.<struct_field_name2>")
/// Basically returns sha256((0u8, "storage::<storage_namespace_name1>::<storage_namespace_name2>.<storage_field_name>.<struct_field_name1>.<struct_field_name2>")).
pub(super) fn get_storage_field_id(
storage_field_names: Vec<String>,
struct_field_names: Vec<String>,
storage_field_names: &[String],
struct_field_names: &[String],
experimental: ExperimentalFeatures,
) -> Bytes32 {
let data = format!(
"{}{}",
Expand All @@ -74,7 +79,32 @@ pub(super) fn get_storage_field_id(
)
}
);
Hasher::hash(data)

hash_storage_key_string(data, experimental)
}

fn hash_storage_key_string(
storage_key_string: String,
experimental: ExperimentalFeatures,
) -> Bytes32 {
let mut hasher = Hasher::default();
// Certain storage types, like, e.g., `StorageMap` allow
// storage slots of their contained elements to be defined
// based on developer's input. E.g., the `key` in a `StorageMap`
// used to calculate the storage slot is a developer input.
//
// To ensure that pre-images of such storage slots can never
// be the same as a pre-image of compiler generated key of storage
// field, we prefix the pre-images with a single byte that denotes
// the domain. Storage types like `StorageMap` must have a different
// domain prefix than the `STORAGE_DOMAIN` which is 0u8.
//
// For detailed elaboration see: https://github.com/FuelLabs/sway/issues/6317
if experimental.storage_domains {
hasher.input(sway_utils::constants::STORAGE_DOMAIN);
}
hasher.input(storage_key_string);
hasher.finalize()
}

use uint::construct_uint;
Expand Down Expand Up @@ -109,17 +139,18 @@ pub fn serialize_to_storage_slots(
key: Option<U256>,
ty: &Type,
) -> Vec<StorageSlot> {
let experimental = context.experimental;
match &constant.value {
ConstantValue::Undef => vec![],
// If not being a part of an aggregate, single byte values like `bool`, `u8`, and unit
// are stored as a byte at the beginning of the storage slot.
ConstantValue::Unit if ty.is_unit(context) => vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
Bytes32::new([0; 32]),
)],
ConstantValue::Bool(b) if ty.is_bool(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
Bytes32::new([
if *b { 1 } else { 0 },
0,
Expand Down Expand Up @@ -158,7 +189,7 @@ pub fn serialize_to_storage_slots(
}
ConstantValue::Uint(b) if ty.is_uint8(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
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,
Expand All @@ -168,7 +199,7 @@ pub fn serialize_to_storage_slots(
// Similarly, other uint values are stored at the beginning of the storage slot.
ConstantValue::Uint(n) if ty.is_uint(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
Bytes32::new(
n.to_be_bytes()
.iter()
Expand All @@ -182,13 +213,13 @@ pub fn serialize_to_storage_slots(
}
ConstantValue::U256(b) if ty.is_uint_of(context, 256) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
Bytes32::new(b.to_be_bytes()),
)]
}
ConstantValue::B256(b) if ty.is_b256(context) => {
vec![StorageSlot::new(
get_storage_key(storage_field_names, key),
get_storage_key(storage_field_names, key, experimental),
Bytes32::new(b.to_be_bytes()),
)]
}
Expand Down Expand Up @@ -224,8 +255,10 @@ pub fn serialize_to_storage_slots(
type_size_in_bytes,
ty.as_string(context)
);

let storage_key = get_storage_key(storage_field_names, key, experimental);
(0..(type_size_in_bytes + 31) / 32)
.map(|i| add_to_b256(get_storage_key(storage_field_names.clone(), key.clone()), i))
.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]))
Expand Down
20 changes: 19 additions & 1 deletion sway-core/src/language/ty/declaration/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use sway_types::{Ident, Named, Span, Spanned};

use crate::{
engine_threading::*,
language::{parsed::StorageDeclaration, ty::*},
ir_generation::storage::get_storage_key_string,
language::parsed::StorageDeclaration,
transform::{self},
ty::*,
type_system::*,
Namespace,
};
Expand Down Expand Up @@ -252,6 +254,22 @@ pub struct TyStorageField {
pub attributes: transform::AttributesMap,
}

impl TyStorageField {
/// Returns the full name of the [TyStorageField], consisting
/// of its name preceded by its full namespace path.
/// E.g., "storage::ns1::ns1.name".
pub fn full_name(&self) -> String {
get_storage_key_string(
&self
.namespace_names
.iter()
.map(|i| i.as_str().to_string())
.chain(vec![self.name.as_str().to_string()])
.collect::<Vec<_>>(),
)
}
}

impl EqWithEngines for TyStorageField {}
impl PartialEqWithEngines for TyStorageField {
fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,22 +441,32 @@ impl TyDecl {
let initializer =
ty::TyExpression::type_check(handler, ctx.by_ref(), &initializer)?;

let mut key_ty_expression = None;
if let Some(key_expression) = key_expression {
let mut key_ctx =
ctx.with_type_annotation(engines.te().id_of_b256());

key_ty_expression = Some(ty::TyExpression::type_check(
handler,
key_ctx.by_ref(),
&key_expression,
)?);
}
let key_expression = match key_expression {
Some(key_expression) => {
let key_ctx = ctx
.with_type_annotation(engines.te().id_of_b256())
.with_help_text("Storage keys must have type \"b256\".");

// TODO: Remove the `handler.scope` once https://github.com/FuelLabs/sway/issues/5606 gets solved.
// We need it here so that we can short-circuit in case of a `TypeMismatch` error which is
// not treated as an error in the `type_check()`'s result.
let typed_expr = handler.scope(|handler| {
ty::TyExpression::type_check(
handler,
key_ctx,
&key_expression,
)
})?;

Some(typed_expr)
}
None => None,
};

fields_buf.push(ty::TyStorageField {
name,
namespace_names: namespace_names.clone(),
key_expression: key_ty_expression,
key_expression,
type_argument,
initializer,
span: field_span,
Expand Down
42 changes: 18 additions & 24 deletions sway-core/src/semantic_analysis/ast_node/declaration/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use crate::{
decl_engine::parsed_id::ParsedDeclId,
fuel_prelude::fuel_tx::StorageSlot,
ir_generation::{
const_eval::compile_constant_expression_to_constant,
storage::{get_storage_key_string, serialize_to_storage_slots},
const_eval::compile_constant_expression_to_constant, storage::serialize_to_storage_slots,
},
language::{
parsed::StorageDeclaration,
Expand Down Expand Up @@ -54,32 +53,27 @@ impl ty::TyStorageDecl {
let slots = f.get_initialized_storage_slots(engines, context, md_mgr, module);

// Check if slot with same key was already used and throw warning.
if let Ok(slots) = slots.clone() {
for s in slots.into_iter() {
if let Ok(slots) = &slots {
for s in slots.iter() {
if let Some(old_field) = slot_fields.insert(*s.key(), f.clone()) {
handler.emit_warn(CompileWarning {
span: f.span(),
warning_content:
sway_error::warning::Warning::DuplicatedStorageKey {
key: format!("{:X} ", s.key()),
field1: get_storage_key_string(
old_field
.namespace_names
.iter()
.map(|i| i.as_str().to_string())
.chain(vec![old_field
.name
.as_str()
.to_string()])
.collect::<Vec<_>>(),
),
field2: get_storage_key_string(
f.namespace_names
.iter()
.map(|i| i.as_str().to_string())
.chain(vec![f.name.as_str().to_string()])
.collect::<Vec<_>>(),
),
first_field: (&old_field.name).into(),
first_field_full_name: old_field.full_name(),
first_field_key_is_compiler_generated: old_field
.key_expression
.is_none(),
second_field: (&f.name).into(),
second_field_full_name: f.full_name(),
second_field_key_is_compiler_generated: f
.key_expression
.is_none(),
key: format!("0x{:x}", s.key()),
experimental_storage_domains: context
.experimental
.storage_domains,
},
})
}
Expand Down Expand Up @@ -151,7 +145,7 @@ impl ty::TyStorageField {
Ok(Some(key))
} else {
Err(CompileError::Internal(
"Expected B256 key",
"Storage keys must have type \"b256\".",
key_expression.span.clone(),
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ pub(crate) fn instantiate_enum(
.with_help_text(help_text)
.with_type_annotation(enum_variant_type_id);

// TODO-IG: Remove the `handler.scope` once https://github.com/FuelLabs/sway/issues/5606 gets solved.
// We need it here so that we can short-circuit in case of a `TypeMismatch` error which is
// not treated as an error in the `type_check()`'s result.
// TODO: Remove the `handler.scope` once https://github.com/FuelLabs/sway/issues/5606 gets solved.
// We need it here so that we can short-circuit in case of a `TypeMismatch` error which is
// not treated as an error in the `type_check()`'s result.
let typed_expr = handler
.scope(|handler| ty::TyExpression::type_check(handler, enum_ctx, single_expr))?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ fn type_check_field_arguments(
.with_type_annotation(struct_field.type_argument.type_id)
.with_unify_generic(true);

// TODO-IG: Remove the `handler.scope` once https://github.com/FuelLabs/sway/issues/5606 gets solved.
// We need it here so that we can short-circuit in case of a `TypeMismatch` error which is
// not treated as an error in the `type_check()`'s result.
// TODO: Remove the `handler.scope` once https://github.com/FuelLabs/sway/issues/5606 gets solved.
// We need it here so that we can short-circuit in case of a `TypeMismatch` error which is
// not treated as an error in the `type_check()`'s result.
let typed_expr = handler
.scope(|handler| ty::TyExpression::type_check(handler, ctx, &field.value));

Expand Down
Loading
Loading