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 name based slots and namespaces. #6064

Merged
merged 21 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion docs/book/src/forc/plugins/forc_client/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Another alternative is the `--target` option, which provides useful aliases to a
forc-deploy --target beta-3
```

Since deploying and running projects on the testnet cost gas, you will need coins to pay for them. You can get some using the [testnet faucet](https://faucet-beta-4.fuel.network/).
Since deploying and running projects on the testnet cost gas, you will need coins to pay for them. You can get some using the [testnet faucet](https://faucet-testnet.fuel.network/).

## Deployment Artifacts

Expand Down
18 changes: 11 additions & 7 deletions docs/reference/src/code/language/annotations/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
contract;

// ANCHOR: storage_namespace
#[namespace(my_storage_namespace)]
storage {
// ANCHOR_END: storage_namespace
var: u64 = 0,
my_storage_namespace {
// ANCHOR_END: storage_namespace
var: u64 = 0,
}
}

// ANCHOR: read
#[storage(read)]
// ANCHOR_END: read
fn read() {
let variable = storage.var.read();
// ANCHOR: storage_namespace_access
let variable = storage::my_storage_namespace.var.read();
// ANCHOR_END: storage_namespace_access

}

// ANCHOR: write
#[storage(write)]
// ANCHOR_END: write
fn write() {
storage.var.write(storage.var.read() + 1);
storage::my_storage_namespace.var.write(storage::my_storage_namespace.var.read() + 1);
}

// ANCHOR: read_write
#[storage(read, write)]
// ANCHOR_END: read_write
fn read_write() {
let var = storage.var.read();
storage.var.write(var + 1);
let var = storage::my_storage_namespace.var.read();
storage::my_storage_namespace.var.write(var + 1);
}

fn example() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[package]]
name = 'core'
source = 'path+from-root-815C32BB386C1563'
dependencies = []

[[package]]
name = 'std'
source = 'git+https://github.com/fuellabs/sway?tag=v0.25.2#dfa6224932a97c514b707dcfc300715b2a0895dc'
dependencies = ['core']

[[package]]
name = 'storage_init'
source = 'root'
dependencies = ['std']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "storage_in_keyword"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
contract;

// ANCHOR: data_structures
struct Owner {
maximum_owners: u64,
role: Role,
}

impl Owner {
// a constructor that can be evaluated to a constant `Owner` during compilation
fn default() -> Self {
Self {
maximum_owners: 10,
role: Role::FullAccess,
}
}
}

enum Role {
FullAccess: (),
PartialAccess: (),
NoAccess: (),
}

const HASH_KEY: b256 = 0x7616e5793ef977b22465f0c843bcad56155c4369245f347bcc8a61edb08b7645;

// ANCHOR_END: data_structures
// ANCHOR: initialization
storage {
// ANCHOR: in_keyword
current_owners in HASH_KEY: u64 = 0,
// ANCHOR_END: in_keyword
explicit_declaration: Owner = Owner {
maximum_owners: 10,
role: Role::FullAccess,
},
encapsulated_declaration: Owner = Owner::default(),
}
// ANCHOR_END: initialization

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

# Storage `in` keyword

The `in` keyword can be used within a storage variable to override the position where the value is stored.

## Example

The `in` keyword and position value are used as follows:

```sway
{{#include ../../../../code/operations/storage/storage_in_keyword/src/main.sw:in_keyword}}
```

The code above will force the storage of the variable into the position `HASH_KEY` which is set to `0x7616e5793ef977b22465f0c843bcad56155c4369245f347bcc8a61edb08b7645` instead of the position that would be calculated from the variable name `sha256("storage.current_owners")` which would be `0x84f905e3f560d70fbfab9ffcd92198998ce6f936e3d45f8fcb16b00f6a6a8d7e`
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ In the following sections we'll take a look at how Sway handles `storage` throug
- [Storage Initialization](init.md): How to declare a `storage` block
- [Reading & Writing](read-write.md): How to read from and write to storage
- [Libraries](libraries/index.md): Additional functionality provided by the [storage library](https://github.com/FuelLabs/sway/blob/master/sway-lib-std/src/storage.sw)
- [Namespaces](namespace.md): How to use `storage` namespaces
- [In keyword](in-keyword.md): How to override storage variable position
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

# Namespace

Namespaces can be used on a `storage` block and variables placed inside the namespaces. A single `storage` block may contain multiple namespaces placed sequentially and or nested.

The hash calculations determining the position of variables in a block with namespace `my_namespace` that contains the variable `foobar` are calculated from `sha256("storage::my_namespace.foobar")`.

## Example

A namespace can be declared as follows:

```sway
{{#include ../../../../code/language/annotations/src/main.sw:storage_namespace}}
```

A variable inside a namespace can be accessed as follows:

```sway
{{#include ../../../../code/language/annotations/src/main.sw:storage_namespace_access}}
```
27 changes: 25 additions & 2 deletions sway-ast/src/item/item_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,41 @@ use crate::priv_prelude::*;
#[derive(Clone, Debug, Serialize)]
pub struct ItemStorage {
pub storage_token: StorageToken,
pub fields: Braces<Punctuated<Annotated<StorageField>, CommaToken>>,
pub entries: Braces<Punctuated<Annotated<StorageEntry>, CommaToken>>,
}

impl Spanned for ItemStorage {
fn span(&self) -> Span {
Span::join(self.storage_token.span(), &self.fields.span())
Span::join(self.storage_token.span(), &self.entries.span())
}
}

#[derive(Clone, Debug, Serialize)]

pub struct StorageEntry {
pub name: Ident,
pub namespace: Option<Braces<Punctuated<Annotated<Box<StorageEntry>>, CommaToken>>>,
pub field: Option<StorageField>,
}

impl Spanned for StorageEntry {
fn span(&self) -> Span {
if let Some(namespace) = &self.namespace {
Span::join(self.name.span(), &namespace.span())
} else if let Some(field) = &self.field {
Span::join(self.name.span(), &field.span())
} else {
self.name.span()
}
}
}

#[derive(Clone, Debug, Serialize)]

pub struct StorageField {
pub name: Ident,
pub in_token: Option<InToken>,
pub key_expr: Option<Expr>,
pub colon_token: ColonToken,
pub ty: Ty,
pub eq_token: EqToken,
Expand Down
2 changes: 1 addition & 1 deletion sway-ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub use crate::{
item_enum::ItemEnum,
item_fn::ItemFn,
item_impl::{ItemImpl, ItemImplItem},
item_storage::{ItemStorage, StorageField},
item_storage::{ItemStorage, StorageEntry, StorageField},
item_struct::ItemStruct,
item_trait::{ItemTrait, ItemTraitItem, Traits},
item_type_alias::ItemTypeAlias,
Expand Down
55 changes: 42 additions & 13 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{
convert::*,
lexical_map::LexicalMap,
storage::{add_to_b256, get_storage_key},
storage::{add_to_b256, get_storage_field_id, get_storage_key},
types::*,
CompiledFunctionCache,
};
Expand All @@ -11,7 +11,10 @@ use crate::{
compile_constant_expression, compile_constant_expression_to_constant,
},
language::{
ty::{self, ProjectionKind, TyConfigurableDecl, TyConstantDecl, TyExpressionVariant},
ty::{
self, ProjectionKind, TyConfigurableDecl, TyConstantDecl, TyExpressionVariant,
TyStorageField,
},
*,
},
metadata::MetadataManager,
Expand All @@ -28,7 +31,6 @@ use sway_types::{
ident::Ident,
integer_bits::IntegerBits,
span::{Span, Spanned},
state::StateIndex,
u256::U256,
Named,
};
Expand Down Expand Up @@ -595,9 +597,22 @@ impl<'eng> FnCompiler<'eng> {
Ok(TerminatorValue::new(val, context))
}
ty::TyExpressionVariant::StorageAccess(access) => {
let span_md_idx = md_mgr.span_to_md(context, &access.span());
let ns = access.namespace.as_ref().map(|ns| ns.as_str());
self.compile_storage_access(context, ns, &access.ix, &access.fields, span_md_idx)
let span_md_idx: Option<MetadataIndex> = md_mgr.span_to_md(context, &access.span());
let key = TyStorageField::get_key_expression_const(
&access.key_expression.clone().map(|v| *v),
self.engines,
context,
md_mgr,
self.module,
)?;
self.compile_storage_access(
context,
access.storage_field_names.clone(),
access.struct_field_names.clone(),
key,
&access.fields,
span_md_idx,
)
}
ty::TyExpressionVariant::IntrinsicFunction(kind) => {
self.compile_intrinsic_function(context, md_mgr, kind, ast_expr.span.clone())
Expand Down Expand Up @@ -3677,8 +3692,9 @@ impl<'eng> FnCompiler<'eng> {
fn compile_storage_access(
&mut self,
context: &mut Context,
ns: Option<&str>,
ix: &StateIndex,
storage_field_names: Vec<String>,
struct_field_names: Vec<String>,
key: Option<U256>,
fields: &[ty::TyStorageAccessDescriptor],
span_md_idx: Option<MetadataIndex>,
) -> Result<TerminatorValue, CompileError> {
Expand All @@ -3703,7 +3719,15 @@ impl<'eng> FnCompiler<'eng> {

// Do the actual work. This is a recursive function because we want to drill down
// to load each primitive type in the storage field in its own storage slot.
self.compile_storage_read(context, ns, ix, &field_idcs, &base_type, span_md_idx)
self.compile_storage_read(
context,
storage_field_names,
struct_field_names,
key,
&field_idcs,
&base_type,
span_md_idx,
)
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -3805,11 +3829,13 @@ impl<'eng> FnCompiler<'eng> {
Ok(TerminatorValue::new(val, context))
}

#[allow(clippy::too_many_arguments)]
fn compile_storage_read(
&mut self,
context: &mut Context,
ns: Option<&str>,
ix: &StateIndex,
storage_field_names: Vec<String>,
struct_field_names: Vec<String>,
key: Option<U256>,
indices: &[u64],
base_type: &Type,
span_md_idx: Option<MetadataIndex>,
Expand Down Expand Up @@ -3846,7 +3872,10 @@ impl<'eng> FnCompiler<'eng> {
// plus the offset, in number of slots, computed above. The offset within this
// particular slot is the remaining offset, in words.
(
add_to_b256(get_storage_key::<u64>(ns, ix, &[]), offset_in_slots),
add_to_b256(
get_storage_key(storage_field_names.clone(), key.clone()),
offset_in_slots,
),
offset_remaining,
)
};
Expand Down Expand Up @@ -3901,7 +3930,7 @@ 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_key(ns, ix, indices); // use the indices to get a field id that is unique even for zero-sized values that live in the same slot
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
Loading
Loading