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

Configurable and composable storage #40

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// This example shows how to apply the proposed API design guidelines
// on the `StorageEncodedBox`.
//
// For API design quidelines, see `api-design/storage_vec.sw`.
//
// For the complete `Storage` implementation of the `StorageEncodedBox` and its other aspects
// see `sway-libs/storage/storage_encoded_box.sw`.
//--

impl StorageEncodedBox<T> where T: AbiEncode + AbiDecode {
//--
// Reading can fail so we provide `try_read` and `read`.
//--
#[storage(read)]
fn try_read(&self) -> Option<T> {
let encoded_value = /* ...
Read the length of the encoded value from the `self_key`
and continue reading the `u8` content from the slots.
If any of the reads fail, the `try_read` fails.

The implementation uses `storage::internal::read(<key>)`
to actually read from the storage.
...
*/;

Some(abi_decode::<T>(encoded_value))
}

#[storage(read)]
fn read(&self) -> T {
self.try_read().unwrap()
}

//--
// Write cannot fail so we provide only `write`.
//
// Since the content can have a variable length, in case of writing,
// the default implementation will just overwrite the old value,
// potentially leaving parts of the old content in the storage
// (if it was longer then the new value).
//
// Thus, to enable clearing of the old content, we provide the
// `write_deep_clear` method.
//--
#[storage(write)]
fn write(&mut self, value: &T) {
let encoded_value = encode::<T>(value);

/* ...
Write the length of the `encoded_value` to the `self.self_key`
and continue writing its packed `u8` content to that and
the consecutive slots.

Packing also means packing 8 `u8`s into a single `u64`
and then those `u64`s into slots.

The implementation uses `storage::internal::write(<key>, <val>)`
to actually write to the storage.
...
*/
}

#[storage(write)]
fn write_deep_clear(&mut self, value: &T) {
let encoded_value = encode::<T>(value);

/* ...
Write the new content, and clear the remaining parts of the old
content, if any.
...
*/
}

//--
// Clear always means a semantic clear with a minimum storage manipulation. In the case
// of the `StorageEncodedBox`, it is sufficient to clear the slot at its self key.
//
// Note that we do not have the `clear_deep_clear` equivalent. We expect here to use
// the `deep_clear` method directly.
//--
#[storage(write)]
fn clear(&mut self) {
/* ...
Clear only the slot at the `self_key` by calling the `storage::internal::clear::<T>(<key>)`
where `T` is a type that guarantees a single slot gets cleared.

TODO-DISCUSSION: See the discussion on clearing API in the `internal.sw`.
...
*/
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// This example shows how to apply the proposed API design guidelines
// on the `StoragePair`.
//
// For API design quidelines, see `api-design/storage_vec.sw` and `api-design/storage_encoded_box.sw`.
//
// For the complete `Storage` implementation of the `StoragePair` and its other aspects
// see `sway-libs/storage/storage_pair.sw`.
//--

impl<A, B> StoragePair<A, B> where A: Storage, B: Storage {
const fn get_first_element_self_key(self_key: &StorageKey) -> StorageKey {
//--
// Returns the self key of the first element of the pair.
// `self_key` is the self key of the `StoragePair`.
// For the sample implementation, see `sway-libs/user-defined-libs/storage_vec.sw`.
//--
}

/// Returns the self key of the second element of the pair.
/// `self_key` is the self key of the [StoragePair].
const fn get_snd_element_self_key(self_key: &StorageKey) -> StorageKey {
//--
// Returns the self key of the second element of the pair.
// `self_key` is the self key of the `StoragePair`.
// For the sample implementation, see `sway-libs/user-defined-libs/storage_vec.sw`.
//--
}

//--
// The below methods consistently apply the API guidelines
// exaplained in detail in `api-design/storage_vec.sw` and `api-design/storage_encoded_box.sw`.
ironcev marked this conversation as resolved.
Show resolved Hide resolved
//--

#[storage(read)]
pub const fn first(&self) -> A {
A::new(Self::get_first_element_self_key(&self.self_key))
}

#[storage(read)]
pub const fn snd(&self) -> B {
B::new(Self::get_snd_element_self_key(&self.self_key))
}

#[storage(write)]
pub fn set_first(&mut self, value: &A::Value) -> A {
let first_element_self_key = Self::get_first_element_self_key(&self.self_key);
A::init(&first_element_self_key, value)
}

#[storage(write)]
pub fn set_first_deep_clear(&mut self, value: &A::Value) -> A where A: DeepClearStorage {
let first_element_self_key = Self::get_first_element_self_key(&self.self_key);
self.set_element_deep_clear::<A>(&first_element_self_key, value)
}

#[storage(write)]
pub fn set_snd(&mut self, value: &B::Value) -> B {
let snd_element_self_key = Self::get_snd_element_self_key(&self.self_key);
A::init(&snd_element_self_key, value)
}

#[storage(write)]
pub fn set_snd_deep_clear(&mut self, value: &B::Value) -> B where B: DeepClearStorage {
let snd_element_self_key = Self::get_snd_element_self_key(&self.self_key);
self.set_element_deep_clear::<B>(&first_element_self_key, value)
}

#[storage(write)]
fn set_element_deep_clear<TElement>(element_self_key: &StorageKey, value: &TElement::Value) -> TElement where TElement: DeepClearStorage {
match TElement::internal_layout() {
// If the size of the element is fixed, we will just overwrite the current content, if any.
ContinuousOfKnownSize(_) => { },
// Otherwise, we must first deep clear the content.
_ => TElement::new(element_self_key).deep_clear(),
}

TElement::init(element_self_key, value)
}

#[storage(write)]
pub fn clear(&mut self) {
/* ...
Clear only the slot at the `self_key` by calling the `storage::internal::clear::<T>(<key>)`
where `T` is a type that guarantees a single slot gets cleared.

TODO-DISCUSSION: See the discussion on clearing API in the `internal.sw`.
...
*/
}

/* ... Other `StoragePair` methods that follow the same API design guidelines. ... */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//--
// This example discuss details of the proposed API design guidelines
// in respect to:
// - handling uninitialized `Storage`.
// - handling semantic errors in operations.
// - handling special options like, e.g., skipping checks for performance.
//
// For the complete `Storage` implementation of the `StorageVec` and its other aspects
// see `sway-libs/storage/storage_vec.sw`.
//--

impl<T> StorageVec<T> where T: Storage {
const fn get_element_self_key(self_key: &StorageKey, element_index: u64) -> StorageKey {
//--
// Calcualte the self key of the element stored at `element_index`.
// `self_key` is the self key of the [StorageVec].
// For the sample implementation, see `sway-libs/storage/storage_vec.sw`.
//--
}

//--
// Getting length can fail if the `StorageVec` is not initialized.
// Therefore, we provide the `try` method.
// `try` methods return `Option`.
//--
#[storage(read)]
pub fn try_len(&self) -> Option<u64> {
storage::internal::read::<u64>(self.self_key)
}

//--
// For every `try` method, we provide its "non-try" equivalent,
// which just unwraps the result of the `try` methods.
// Therefore, it reverts if the `StorageVec` is uninitialized.
//--
#[storage(read)]
pub fn len(&self) -> u64 {
self.try_len().unwrap()
}

//--
// For each `Storage`, we give an uninitialize storage a semantic
// meaning. In the case of the `StorageVec`, we treat the uninitialized
// `StorageVec` as an empty `StorageVec`.
//
// This means that the `push` cannot fail, and therefore, we provide only
// the `push` method, an not a `try_push`.
//--
#[storage(write)]
pub fn push(&mut self, value: &T::Value) {
let len = self.try_len().unwrap_or(0);

// Store the value.
let element_self_key = Self::get_element_self_key(&self.self_key, len);
T::init(element_self_key, value);

// Store the new length.
storage::internal::write(self.self_key, len + 1);
}

//--
// `pop` is a much more difficult example.
// Same as with `push` and other methods, we treat an uninitialized
// `StorageVec` as an empty vector.
//
// The additional challange here is providing the possibility to deep clear
// the popped element. This surely requires an separate method because of
// an additional trait constraint `T: DeepClearStorage`.
//
// In this case, the guideline is to provide a `<name>_<special_behavior>()`
// equivalent, `pop_deep_clear()`.
//
// So we will have a `pop` and a `pop_deep_clear` methods.
//
// Note that this approach, `<name>_<special_behavior>()`, does not scale
// well if we want to combine several "special behaviors" in one call.
// However, in the storage API design, we do not expect such cases, or at
// least not a considerable number of them.
//
// And in the case they do appear, the guideline is to have a single distinguished
// method with a "special behavior" that accepts additional parameters.
// E.g., let's say that we want to `<operation>_deep_clear` with certains checks
// being optional. A possible method would look like:
//
// fn <operation>_deep_clear(&mut self, checked: bool)
//--
#[storage(write)]
pub fn pop(&mut self) -> bool {
let len = self.try_len().unwrap_or(0);

if len <= 0 {
return false;
}

//--
// Just store the new length, without deep clearing the value.
//--
storage::internal::write(self.self_key, len - 1);
}

#[storage(write)]
pub fn pop_deep_clear(&mut self) -> bool where T: DeepClearStorage {
let len = self.try_len().unwrap_or(0);

if len <= 0 {
return false;
}

let element_self_key = Self::get_element_self_key(&self.self_key, len - 1);
T::new(element_self_key).deep_clear();

storage::internal::write(self.self_key, len - 1);
}

//--
// Same as above, `get` treats the uninitialized `StorageVec` as empty.
//
// The important consequence of that decision, to treat an initialized vector
// as empty is, that we do not distinguish between "technical" errors, those
// coming from reading uninitialized storge, and "semantic" errors, those coming
// from, e.g., reading out of bounds.
//
// This means that we will, following the guideline given above, have a
// `try_get` and a `get` method, where actually the `try_get` corresponds to
// the `get` method in the current implementation of the `StorageVec`, that
// returns `Option`.
//--
#[storage(read)]
pub fn try_get(&self, element_index) -> Option<T> {
if self.try_len().unwrap_or(0) <= index {
return None;
}

let element_self_key = Self::get_element_self_key(&self.self_key, element_index);
Some(T::new(element_self_key))
}

#[storage(read)]
pub fn get(&self, element_index) -> T {
self.try_get(element_index).unwrap()
}

//--
// Another important aspect of `get` is a possibility to have a special behavior
// that is not the default one. Concretely, providing the possibility to skip
// the costly bound check. According to the `<name>_<special_behavior>` guideline
// given above, we will have the `get_unchecked` method.
//
// Note that in this case, there is no `try_get_unchecked` because ignoring the
// boundary check also removes the possibility that the `get` fails.
//
// In a general case, we should also provide the `try_<name>_<special_behavior>` method.
//--
#[storage(read)]
pub fn get_unchecked(&self, element_index) -> T {
let element_self_key = Self::get_element_self_key(&self.self_key, element_index);
T::new(element_self_key)
}

//--
// Clear always means a semantic clear with a minimum storage manipulation. In the case
// of the `StorageVec`, it is sufficient to set its length to zero, or to clear
// the slot at its self key.
//
// Note that we do not have the `clear_deep_clear` equivalent. We expect here to use
// the `deep_clear` method directly.
//--
pub fn clear(&mut self) {
storage::internal::write(self.self_key, 0);
}

/* ... Other `StorageVec` methods that follow the same API design guidelines. ... */
}
Loading
Loading