Skip to content

Commit

Permalink
Introduce StorageString to sway-libs (#92)
Browse files Browse the repository at this point in the history
## Type of change

<!--Delete points that do not apply-->

- New feature

## Changes

The following changes have been made:

- Added `StorageString` type
- Added `from_raw_slice()` and `as_raw_slice()` to the `String` type
- Updated src README wording, links, and added `StorageString`
- Moved `String` into `sway-libs/libs/strings` folder with the
`StorageString` type
- Updated `String` README and SPECIFICATION to remove outdated info
- Updated `String` to use non-mutable types in `from_utf8()`

## Notes

- The `From<raw_slice>` implementation for the `String` type is
commented out until FuelLabs/sway#3637 is
resolved. The `from_raw_slice()` shall be removed when this is
reimplemented
- Tests for the `from_raw_slice()` and `as_raw_slice()` functions have
been commented out until FuelLabs/sway#4408 is
resolved
- Until FuelLabs/fuels-rs#940 is resolved,
developers must return any `StorageString`s from storage as a `Bytes`
type
- This should unblock NFT URIs, Fuel Name Service, token standards, and
more

## Related Issues

<!--Delete everything after the "#" symbol and replace it with a number.
No spaces between hash and number-->

Closes #40

---------

Co-authored-by: bitzoic <[email protected]>
  • Loading branch information
bitzoic and bitzoic authored Apr 20, 2023
1 parent f0274f7 commit ff949a7
Show file tree
Hide file tree
Showing 33 changed files with 663 additions and 11 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ These libraries contain helper functions, generalized standards, and other tools
- [Non-Fungible Token (NFT)](./libs/nft/) is a token library which provides unqiue collectibles, identified and differentiated by token IDs.
- [Ownership](./libs/ownership/) is used to apply restrictions on functions such that only a single user may call them.
- [Reentrancy](./libs/reentrancy) is used to detect and prevent reentrancy attacks.
- [String](./libs/string/) is an interface to implement dynamic length strings that are UTF-8 encoded.
- [Signed Integers](./libs/signed_integers/) is an interface to implement signed integers.
- [Unsigned Fixed Point Number](./libs/fixed_point/) is an interface to implement fixed-point numbers.
- [String](./libs/strings/string/) is an interface to implement dynamic length strings that are UTF-8 encoded.
- [StorageMapVec](./libs/storagemapvec/) is a temporary workaround for a StorageMap<K, StorageVec<V>> type.
- [StorageString](./libs/strings/storage_string/) is used to store dynamic length strings that are UTF-8 encoded in storage.
- [Fixed Point Number](./libs/fixed_point/) is an interface to implement fixed-point numbers.

### Standards

Expand Down
3 changes: 2 additions & 1 deletion libs/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ members = [
"reentrancy",
"signed_integers",
"storagemapvec",
"string",
"strings/storage_string",
"strings/string",
]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions libs/strings/storage_string/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "lib.sw"
license = "Apache-2.0"
name = "storage_string"

[dependencies]
string = { path = "../string" }
58 changes: 58 additions & 0 deletions libs/strings/storage_string/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset=".docs/storage-string-logo-dark-theme.png">
<img alt="SwayApps logo" width="400px" src=".docs/storage-string-logo-light-theme.png">
</picture>
</p>

# Overview

The StorageString library provides an interface to store UTF-8 encoded strings of dynamic length in Sway. The `StorageString` can be used in combination with the `String` type.

The `StorageString` stores the underlying data of the `String` type. This differs from Sway's built in `str` because the size cannot be known at compile time and the length is dynamic.

For more information please see the [specification](./SPECIFICATION.md).

## Known Issues

Until https://github.com/FuelLabs/fuels-rs/issues/940 is resolved, developers must use the `Bytes` type to return a `String` from a contract.

# Using the Library

## Getting Started

In order to use the `StorageString` library it must be added to the Forc.toml file and then imported into your Sway project. To add Sway-libs as a dependency to the Forc.toml in your project, please see the [README.md](../../../README.md).

```rust
use storage_string::StorageString;
```

Once imported, a `StorageString` must be defined in a storage block.

```rust
storage {
stored_string: StorageString = StorageString {},
}
```

## Basic Functionality

Storing a `String` type can be done by calling the `store` function.

```rust
// Create a new string
let mut my_string = String::new();
my_string.push(0u8);

// Store the string
storage.stored_string.store(my_string);
```

Retrieving a `String` from storage can be done with the `load` function.

```rust
// Get a string from storage
let my_string: String = storage.stored_string.load();
```

For more information please see the [specification](./SPECIFICATION.md).
29 changes: 29 additions & 0 deletions libs/strings/storage_string/SPECIFICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Overview

This document provides an overview of the StorageString library.

It outlines the use cases, i.e. specification, and describes how to implement the library.

## Use Cases

The StorageString library can be used anytime a string's length is unknown at compile time and must be saved in storage.

> **Note** To returned the `StorageString` from a contract you should use the `Bytes` type. For more information, please see the [known issues](./README.md#known-issues).
## Public Functions

### `store()`

Stores a `String` in storage.

### `load()`

Retrieves a `String` from storage.

### `len()`

Returns the length of the `String` stored in storage.

### `clear()`

Clears a stored `String` in storage.
143 changes: 143 additions & 0 deletions libs/strings/storage_string/src/lib.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
library;

use std::{bytes::Bytes, storage::{clear_slice, get, get_slice, StorableSlice, store_slice}};
use string::String;

pub struct StorageString {}

impl StorableSlice<String> for StorageString {
/// Takes a `String` type and saves the underlying data in storage.
///
/// ### Arguments
///
/// * `string` - The string which will be stored.
///
/// ### Number of Storage Accesses
///
/// * Writes: `2`
///
/// ### Examples
///
/// ```sway
/// storage {
/// stored_string: StorageString = StorageString {}
/// }
///
/// fn foo() {
/// let mut string = String::new();
/// string.push(5_u8);
/// string.push(7_u8);
/// string.push(9_u8);
///
/// storage.stored_string.store(string);
/// }
/// ```
#[storage(write)]
fn store(self, string: String) {
let key = __get_storage_key();
store_slice(key, string.as_raw_slice());
}

/// Constructs a `String` type from storage.
///
/// ### Number of Storage Accesses
///
/// * Reads: `2`
///
/// ### Examples
///
/// ```sway
/// storage {
/// stored_string: StorageString = StorageString {}
/// }
///
/// fn foo() {
/// let mut string = String::new();
/// string.push(5_u8);
/// string.push(7_u8);
/// string.push(9_u8);
///
/// assert(storage.stored_string.load(key).is_none());
/// storage.stored_string.store(string);
/// let retrieved_string = storage.stored_string.load(key).unwrap();
/// assert(string == retrieved_string);
/// }
/// ```
#[storage(read)]
fn load(self) -> Option<String> {
let key = __get_storage_key();
match get_slice(key) {
Option::Some(slice) => {
// Uncomment when https://github.com/FuelLabs/sway/issues/4408 is resolved
// Option::Some(String::from_raw_slice(slice))
Option::Some(String {
bytes: Bytes::from_raw_slice(slice),
})
},
Option::None => Option::None,
}
}

/// Clears a stored `String` in storage.
///
/// ### Number of Storage Accesses
///
/// * Reads: `1`
/// * Clears: `2`
///
/// ### Examples
///
/// ```sway
/// storage {
/// stored_string: StorageString = StorageString {}
/// }
///
/// fn foo() {
/// let mut string = String::new();
/// string.push(5_u8);
/// string.push(7_u8);
/// string.push(9_u8);
/// storage.stored_string.store(string);
///
/// assert(storage.stored_string.load(key).is_some());
/// let cleared = storage.stored_string.clear();
/// assert(cleared);
/// let retrieved_string = storage.stored_string.load(key);
/// assert(retrieved_string.is_none());
/// }
/// ```
#[storage(read, write)]
fn clear(self) -> bool {
let key = __get_storage_key();
clear_slice(key)
}

/// Returns the length of `String` in storage.
///
/// ### Number of Storage Accesses
///
/// * Reads: `1`
///
/// ### Examples
///
/// ```sway
/// storage {
/// stored_string: StorageString = StorageString {}
/// }
///
/// fn foo() {
/// let mut string = String::new();
/// string.push(5_u8);
/// string.push(7_u8);
/// string.push(9_u8);
///
/// assert(storage.stored_string.len() == 0)
/// storage.stored_string.store(string);
/// assert(storage.stored_string.len() == 3);
/// }
/// ```
#[storage(read)]
fn len(self) -> u64 {
get::<u64>(__get_storage_key()).unwrap_or(0)
}
}
File renamed without changes.
3 changes: 0 additions & 3 deletions libs/string/README.md → libs/strings/string/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ For more information please see the [specification](./SPECIFICATION.md).
## Known Issues

The `append()` function currently causes an internal compiler error when used. It has been commented out until https://github.com/FuelLabs/sway/issues/4158 is resolved.


Until https://github.com/FuelLabs/sway/issues/4158 is resolved, developers must directly access the underlying `Bytes` type to make modifications to the `String` type.

It is important to note that unlike Rust's `String`, this `String` library does **not** guarantee a valid UTF-8 string. The `String` currently behaves only as a `vec` and does not perform any validation. This intended to be supported in the future with the introduction of [`char`](https://github.com/FuelLabs/sway/issues/2937) to the Sway language.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ The String library can be used anytime a string's length is unknown at compile t

Joins two `String` instances into a single larger `String`.

**NOTE** This function is temporarily unavailable until https://github.com/FuelLabs/sway/issues/4158 is resolved.

### `as_vec()`

Convert the `String` struct to a `Vec` of `u8` bytes.
Expand Down
34 changes: 33 additions & 1 deletion libs/string/src/lib.sw → libs/strings/string/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ impl String {
/// # Arguments
///
/// * `bytes` - The vector of `u8` bytes which will be converted into a `String`.
pub fn from_utf8(mut bytes: Vec<u8>) -> Self {
pub fn from_utf8(bytes: Vec<u8>) -> Self {
let mut bytes = bytes;
Self {
bytes: Bytes::from_vec_u8(bytes),
}
Expand Down Expand Up @@ -123,6 +124,13 @@ impl String {
bytes: Bytes::with_capacity(capacity),
}
}

// Should be removed when https://github.com/FuelLabs/sway/issues/3637 is resovled
pub fn from_raw_slice(slice: raw_slice) -> Self {
let mut bytes = Bytes::with_capacity(slice.number_of_bytes());
bytes.buf.ptr = slice.ptr();
Self { bytes }
}
}

impl From<Bytes> for String {
Expand All @@ -137,6 +145,30 @@ impl From<Bytes> for String {
}
}

impl AsRawSlice for String {
/// Returns a raw slice to all of the elements in the string.
fn as_raw_slice(self) -> raw_slice {
asm(ptr: (self.bytes.buf.ptr(), self.bytes.len)) { ptr: raw_slice }
}
}



// Uncomment when https://github.com/FuelLabs/sway/issues/3637 is resolved.
// impl From<raw_slice> for String {
// fn from(slice: raw_slice) -> String {
// let mut bytes = Bytes::with_capacity(slice.number_of_bytes());
// bytes.buf.ptr = slice.ptr();
// Self {
// bytes
// }
// }

// fn into(self) -> raw_slice {
// asm(ptr: (self.bytes.buf.ptr(), self.bytes.len)) { ptr: raw_slice }
// }
// }

impl String {
/// Moves all elements of the `other` String into `self`, leaving `other` empty.
///
Expand Down
1 change: 1 addition & 0 deletions tests/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"./src/signed_integers/signed_i16_twos_complement",
"./src/signed_integers/signed_i32_twos_complement",
"./src/signed_integers/signed_i64_twos_complement",
"./src/storage_string",
"./src/storagemapvec",
"./src/string",
]
1 change: 1 addition & 0 deletions tests/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ mod nft;
mod ownership;
mod reentrancy;
mod signed_integers;
mod storage_string;
mod storagemapvec;
mod string;
2 changes: 2 additions & 0 deletions tests/src/storage_string/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
9 changes: 9 additions & 0 deletions tests/src/storage_string/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "storage_string_test"

[dependencies]
storage_string = { path = "../../../libs/strings/storage_string" }
string = { path = "../../../libs/strings/string" }
1 change: 1 addition & 0 deletions tests/src/storage_string/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod tests;
Loading

0 comments on commit ff949a7

Please sign in to comment.