Skip to content

Commit

Permalink
Enable storing of a Vec with the StorageVec type (#5123)
Browse files Browse the repository at this point in the history
## Description

Currently, `StorageVec` and `Vec` do not have any relation with one
another. This PR takes advantage of the recent optimization and
refactoring done to the Storage API `read()` and `write()` functions in
#4795 and enables the storing of a
`Vec` using the `StorageVec` type.

To do this, the `StorageVec` now stores things sequentially rather than
a different key for every element. Due to the optimizations done to the
Storage API, this has become feasible as we can now load a single
element of the sequential `StorageVec`.

The storing of elements sequentially mimics the existing `Vec`, allowing
us to store it as a `raw_slice` with a ***single*** read/write to
storage as opposed to looping over the `Vec` and having ***n***
read/writes.

It should be noted that due to
#409, the storing of a `Vec` is
written in the `StorageVec` file. This is the resulting syntax:
```sway
let my_vec = Vec::new();
storage.storage_vec.store_vec(my_vec); // Store the Vec
let other_vec = storage.storage_vec.load_vec(); // Read the Vec
```
When this issue is resolved, this should be changed to a `From`
implementation changing the syntax to:
```sway
let my_vec = Vec::new();
storage.storage_vec = my_vec.into(); // Store the Vec
let other_vec = Vec::from(storage.storage_vec); // Read the Vec
```

Closes #2439

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [ ] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: bitzoic <[email protected]>
  • Loading branch information
bitzoic and bitzoic authored Sep 26, 2023
1 parent 8b3ed78 commit 5392f8b
Show file tree
Hide file tree
Showing 7 changed files with 793 additions and 260 deletions.
236 changes: 193 additions & 43 deletions sway-lib-std/src/storage/storage_vec.sw
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
library;

use ::alloc::alloc;
use ::alloc::{alloc_bytes, realloc_bytes};
use ::assert::assert;
use ::hash::*;
use ::option::Option::{self, *};
use ::storage::storage_api::*;
use ::storage::storage_key::*;
use ::vec::Vec;

/// A persistant vector struct.
pub struct StorageVec<V> {}
Expand Down Expand Up @@ -42,8 +43,9 @@ impl<V> StorageKey<StorageVec<V>> {
let len = read::<u64>(self.field_id, 0).unwrap_or(0);

// Storing the value at the current length index (if this is the first item, starts off at 0)
let key = sha256((len, self.field_id));
write::<V>(key, 0, value);
let key = sha256(self.field_id);
let offset = offset_calculator::<V>(len);
write::<V>(key, offset, value);

// Incrementing the length
write(self.field_id, 0, len + 1);
Expand Down Expand Up @@ -90,8 +92,9 @@ impl<V> StorageKey<StorageVec<V>> {
// reduces len by 1, effectively removing the last item in the vec
write(self.field_id, 0, len - 1);

let key = sha256((len - 1, self.field_id));
read::<V>(key, 0)
let key = sha256(self.field_id);
let offset = offset_calculator::<V>(len - 1);
read::<V>(key, offset)
}

/// Gets the value in the given index, `None` if index is out of bounds.
Expand Down Expand Up @@ -134,10 +137,15 @@ impl<V> StorageKey<StorageVec<V>> {
return None;
}

let key = sha256(self.field_id);
let offset = offset_calculator::<V>(index);
// This StorageKey can be read by the standard storage api.
// Field Id must be unique such that nested storage vecs work as they have a
// __size_of() zero and will there forefore always have an offset of zero.
Some(StorageKey::<V>::new(
sha256((index, self.field_id)),
0,
sha256((index, self.field_id))
key,
offset,
sha256((index, key))
))
}

Expand Down Expand Up @@ -191,16 +199,19 @@ impl<V> StorageKey<StorageVec<V>> {
assert(index < len);

// gets the element before removing it, so it can be returned
let removed_element = read::<V>(sha256((index, self.field_id)), 0).unwrap();
let key = sha256(self.field_id);
let removed_offset = offset_calculator::<V>(index);
let removed_element = read::<V>(key, removed_offset).unwrap();

// for every element in the vec with an index greater than the input index,
// shifts the index for that element down one
let mut count = index + 1;
while count < len {
// gets the storage location for the previous index
let key = sha256((count - 1, self.field_id));
// gets the storage location for the previous index and
// moves the element of the current index into the previous index
write::<V>(key, 0, read::<V>(sha256((count, self.field_id)), 0).unwrap());
let write_offset = offset_calculator::<V>(count - 1);
let read_offset = offset_calculator::<V>(count);
write::<V>(key, write_offset, read::<V>(key, read_offset).unwrap());

count += 1;
}
Expand Down Expand Up @@ -257,12 +268,15 @@ impl<V> StorageKey<StorageVec<V>> {
// if the index is larger or equal to len, there is no item to remove
assert(index < len);

let hash_of_to_be_removed = sha256((index, self.field_id));
let key = sha256(self.field_id);
// gets the element before removing it, so it can be returned
let element_to_be_removed = read::<V>(hash_of_to_be_removed, 0).unwrap();
let element_offset = offset_calculator::<V>(index);
let element_to_be_removed = read::<V>(key, element_offset).unwrap();

let last_element = read::<V>(sha256((len - 1, self.field_id)), 0).unwrap();
write::<V>(hash_of_to_be_removed, 0, last_element);
let last_offset = offset_calculator::<V>(len - 1);
let last_element = read::<V>(key, last_offset).unwrap();

write::<V>(key, element_offset, last_element);

// decrements len by 1
write(self.field_id, 0, len - 1);
Expand Down Expand Up @@ -312,8 +326,9 @@ impl<V> StorageKey<StorageVec<V>> {
// if the index is higher than or equal len, there is no element to set
assert(index < len);

let key = sha256((index, self.field_id));
write::<V>(key, 0, value);
let key = sha256(self.field_id);
let offset = offset_calculator::<V>(index);
write::<V>(key, offset, value);
}

/// Inserts the value at the given index, moving the current index's value
Expand Down Expand Up @@ -365,9 +380,10 @@ impl<V> StorageKey<StorageVec<V>> {
assert(index <= len);

// if len is 0, index must also be 0 due to above check
let key = sha256(self.field_id);
if len == index {
let key = sha256((index, self.field_id));
write::<V>(key, 0, value);
let offset = offset_calculator::<V>(index);
write::<V>(key, offset, value);

// increments len by 1
write(self.field_id, 0, len + 1);
Expand All @@ -380,17 +396,18 @@ impl<V> StorageKey<StorageVec<V>> {
// performed in reverse to prevent data overwriting
let mut count = len - 1;
while count >= index {
let key = sha256((count + 1, self.field_id));
// shifts all the values up one index
write::<V>(key, 0, read::<V>(sha256((count, self.field_id)), 0).unwrap());
let write_offset = offset_calculator::<V>(count + 1);
let read_offset = offset_calculator::<V>(count);
write::<V>(key, write_offset, read::<V>(key, read_offset).unwrap());

if count == 0 { break; }
count -= 1;
}

// inserts the value into the now unused index
let key = sha256((index, self.field_id));
write::<V>(key, 0, value);
let offset = offset_calculator::<V>(index);
write::<V>(key, offset, value);

// increments len by 1
write(self.field_id, 0, len + 1);
Expand Down Expand Up @@ -537,12 +554,14 @@ impl<V> StorageKey<StorageVec<V>> {
return;
}

let element1_key = sha256((element1_index, self.field_id));
let element2_key = sha256((element2_index, self.field_id));
let key = sha256(self.field_id);
let element1_offset = offset_calculator::<V>(element1_index);
let element2_offset = offset_calculator::<V>(element2_index);

let element1_value = read::<V>(key, element1_offset).unwrap();

let element1_value = read::<V>(element1_key, 0).unwrap();
write::<V>(element1_key, 0, read::<V>(element2_key, 0).unwrap());
write::<V>(element2_key, 0, element1_value);
write::<V>(key, element1_offset, read::<V>(key, element2_offset).unwrap());
write::<V>(key, element2_offset, element1_value);
}

/// Returns the first element of the vector, or `None` if it is empty.
Expand Down Expand Up @@ -573,12 +592,13 @@ impl<V> StorageKey<StorageVec<V>> {
/// ```
#[storage(read)]
pub fn first(self) -> Option<StorageKey<V>> {
let key = sha256(self.field_id);
match read::<u64>(self.field_id, 0).unwrap_or(0) {
0 => None,
_ => Some(StorageKey::<V>::new(
sha256((0, self.field_id)),
key,
0,
sha256((0, self.field_id))
sha256((0, key))
)),
}
}
Expand Down Expand Up @@ -612,13 +632,17 @@ impl<V> StorageKey<StorageVec<V>> {
/// ```
#[storage(read)]
pub fn last(self) -> Option<StorageKey<V>> {
let key = sha256(self.field_id);
match read::<u64>(self.field_id, 0).unwrap_or(0) {
0 => None,
len => Some(StorageKey::<V>::new(
sha256((len - 1, self.field_id)),
0,
sha256((0, self.field_id))
)),
len => {
let offset = offset_calculator::<V>(len - 1);
Some(StorageKey::<V>::new(
key,
offset,
sha256((len - 1, key))
))
},
}
}

Expand Down Expand Up @@ -655,15 +679,17 @@ impl<V> StorageKey<StorageVec<V>> {
return;
}

let key = sha256(self.field_id);
let mid = len / 2;
let mut i = 0;
while i < mid {
let element1_key = sha256((i, self.field_id));
let element2_key = sha256((len - i - 1, self.field_id));
let i_offset = offset_calculator::<V>(i);
let other_offset = offset_calculator::<V>(len - i - 1);

let element1_value = read::<V>(element1_key, 0).unwrap();
write::<V>(element1_key, 0, read::<V>(element2_key, 0).unwrap());
write::<V>(element2_key, 0, element1_value);
let element1_value = read::<V>(key, i_offset).unwrap();

write::<V>(key, i_offset, read::<V>(key, other_offset).unwrap());
write::<V>(key, other_offset, element1_value);

i += 1;
}
Expand Down Expand Up @@ -702,9 +728,11 @@ impl<V> StorageKey<StorageVec<V>> {
pub fn fill(self, value: V) {
let len = read::<u64>(self.field_id, 0).unwrap_or(0);

let key = sha256(self.field_id);
let mut i = 0;
while i < len {
write::<V>(sha256((i, self.field_id)), 0, value);
let offset = offset_calculator::<V>(i);
write::<V>(key, offset, value);
i += 1;
}
}
Expand Down Expand Up @@ -755,10 +783,132 @@ impl<V> StorageKey<StorageVec<V>> {
#[storage(read, write)]
pub fn resize(self, new_len: u64, value: V) {
let mut len = read::<u64>(self.field_id, 0).unwrap_or(0);
let key = sha256(self.field_id);
while len < new_len {
write::<V>(sha256((len, self.field_id)), 0, value);
let offset = offset_calculator::<V>(len);
write::<V>(key, offset, value);
len += 1;
}
write::<u64>(self.field_id, 0, new_len);
}

// TODO: This should be moved into the vec.sw file and `From<StorageKey<StorageVec>> for Vec`
// implemented instead of this when https://github.com/FuelLabs/sway/issues/409 is resolved.
// Implementation will change from this:
// ```sway
// let my_vec = Vec::new();
// storage.storage_vec.store_vec(my_vec);
// let other_vec = storage.storage_vec.load_vec();
// ```
// To this:
// ```sway
// let my_vec = Vec::new();
// storage.storage_vec = my_vec.into();
// let other_vec = Vec::from(storage.storage_vec);
// ```
/// Stores a `Vec` as a `StorageVec`.
///
/// # Additional Information
///
/// This will overwrite any existing values in the `StorageVec`.
///
/// # Arguments
///
/// * `vec`: [Vec<V>] - The vector to store in storage.
///
/// # Number of Storage Accesses
///
/// * Writes - `2`
///
/// # Examples
///
/// ```sway
/// storage {
/// vec: StorageVec<u64> = StorageVec {},
/// }
///
/// fn foo() {
/// let mut vec = Vec::<u64>::new();
/// vec.push(5);
/// vec.push(10);
/// vec.push(15);
///
/// storage.vec.store_vec(vec);
///
/// assert(5 == storage.vec.get(0).unwrap());
/// assert(10 == storage.vec.get(1).unwrap());
/// assert(15 == storage.vec.get(2).unwrap());
/// }
/// ```
#[storage(write)]
pub fn store_vec(self, vec: Vec<V>) {
let slice = vec.as_raw_slice();
// Get the number of storage slots needed based on the size of bytes.
let number_of_bytes = slice.number_of_bytes();
let number_of_slots = (number_of_bytes + 31) >> 5;
let mut ptr = slice.ptr();

// The capacity needs to be a multiple of 32 bytes so we can
// make the 'quad' storage instruction store without accessing unallocated heap memory.
ptr = realloc_bytes(ptr, number_of_bytes, number_of_slots * 32);

// Store `number_of_slots * 32` bytes starting at storage slot `key`.
let _ = __state_store_quad(sha256(self.field_id), ptr, number_of_slots);

// Store the length, NOT the bytes.
// This differs from the existing `write_slice()` function to be compatible with `StorageVec`.
write::<u64>(self.field_id, 0, number_of_bytes / __size_of::<V>());
}

/// Load a `Vec` from the `StorageVec`.
///
/// # Returns
///
/// * [Option<Vec<V>>] - The vector constructed from storage or `None`.
///
/// # Number of Storage Accesses
///
/// * Reads - `2`
///
/// # Examples
///
/// ```sway
/// storage {
/// vec: StorageVec<u64> = StorageVec {},
/// }
///
/// fn foo() {
/// let mut vec = Vec::<u64>::new();
/// vec.push(5);
/// vec.push(10);
/// vec.push(15);
///
/// storage.vec.store_vec(vec);
/// let returned_vec = storage.vec.load_vec();
///
/// assert(5 == returned_vec.get(0).unwrap());
/// assert(10 == returned_vec.get(1).unwrap());
/// assert(15 == returned_vec.get(2).unwrap());
/// }
/// ```
#[storage(read)]
pub fn load_vec(self) -> Vec<V> {
// Get the length of the slice that is stored.
match read::<u64>(self.field_id, 0).unwrap_or(0) {
0 => Vec::new(),
len => {
// Get the number of storage slots needed based on the size.
let bytes = len * __size_of::<V>();
let number_of_slots = (bytes + 31) >> 5;
let ptr = alloc_bytes(number_of_slots * 32);
// Load the stored slice into the pointer.
let _ = __state_load_quad(sha256(self.field_id), ptr, number_of_slots);
Vec::from(asm(ptr: (ptr, bytes)) { ptr: raw_slice })
}
}
}
}

fn offset_calculator<T>(offset: u64) -> u64 {
(offset * __size_of::<T>()) / 8
}
Loading

0 comments on commit 5392f8b

Please sign in to comment.