Skip to content

Commit

Permalink
feat: Strings and simple ByteArray Store example (#173)
Browse files Browse the repository at this point in the history
  • Loading branch information
julio4 committed Feb 16, 2024
1 parent 89037ca commit d569d57
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ name = "alexandria_storage"
version = "0.3.0"
source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=800f5ad#800f5ad217847b5ded63c0302a444161766ee9d6"

[[package]]
name = "bytearray"
version = "0.1.0"

[[package]]
name = "cairo_cheatsheet"
version = "0.1.0"
Expand Down
1 change: 1 addition & 0 deletions listings/getting-started/bytearray/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
6 changes: 6 additions & 0 deletions listings/getting-started/bytearray/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "constructor"
version.workspace = true
12 changes: 12 additions & 0 deletions listings/getting-started/bytearray/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "bytearray"
version.workspace = true
edition = '2023_11'

[dependencies]
starknet.workspace = true

[scripts]
test.workspace = true

[[target.starknet-contract]]
54 changes: 54 additions & 0 deletions listings/getting-started/bytearray/src/bytearray.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// ANCHOR: contract
#[starknet::interface]
pub trait IMessage<TContractState> {
fn append(ref self: TContractState, str: ByteArray);
fn prepend(ref self: TContractState, str: ByteArray);
}

#[starknet::contract]
pub mod MessageContract {
#[storage]
struct Storage {
message: ByteArray
}

#[constructor]
fn constructor(ref self: ContractState) {
self.message.write("World!");
}

#[abi(embed_v0)]
impl MessageContract of super::IMessage<ContractState> {
fn append(ref self: ContractState, str: ByteArray) {
self.message.write(self.message.read() + str);
}

fn prepend(ref self: ContractState, str: ByteArray) {
self.message.write(str + self.message.read());
}
}
}
// ANCHOR_END: contract

#[cfg(test)]
mod tests {
use bytearray::bytearray::{
MessageContract::messageContractMemberStateTrait, MessageContract, IMessage
};

#[test]
#[available_gas(2000000000)]
fn message_contract_tests() {
let mut state = MessageContract::unsafe_new_contract_state();
state.message.write("World!");

let message = state.message.read();
assert(message == "World!", 'wrong message');

state.append(" Good day, sir!");
assert(state.message.read() == "World! Good day, sir!", 'wrong message (append)');

state.prepend("Hello, ");
assert(state.message.read() == "Hello, World! Good day, sir!", 'wrong message (prepend)');
}
}
1 change: 1 addition & 0 deletions listings/getting-started/bytearray/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod bytearray;
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Summary
- [Errors](./ch00/basics/errors.md)
- [Events](./ch00/basics/events.md)
- [Syscalls](./ch00/basics/syscalls.md)
- [Strings and ByteArrays](./ch00/basics/bytearrays-strings.md)
- [Storing Custom Types](./ch00/basics/storing-custom-types.md)
- [Custom types in entrypoints](./ch00/basics/custom-types-in-entrypoints.md)
- [Documentation](./ch00/basics/documentation.md)
Expand Down
43 changes: 43 additions & 0 deletions src/ch00/basics/bytearrays-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Strings and ByteArrays

In Cairo, there's no native type for strings. Instead, you can use a single `felt252` to store a `short string` or a `ByteArray` for strings of arbitrary length.

## Short strings

Each character is encoded on 8 bits following the ASCII standard, so it's possible to store up to 31 characters in a single `felt252`.

Short strings are declared with single quotes, like this: `'Hello, World!'`.
See the [Felt](../cairo_cheatsheet/felt.md) section for more information about short strings with the `felt252` type.

> Notice that any short string only use up to 31 bytes, so it's possible to represent any short string with the `bytes31`.
## ByteArray (Long strings)

The `ByteArray` struct is used to store strings of arbitrary length. It contain a field `data` of type `Array<bytes31>` to store a sequence of short strings.

ByteArrays are declared with double quotes, like this: `"Hello, World!"`.

They can be stored in the contract's storage and passed as arguments to entrypoints.

```rust
{{#rustdoc_include ../../../listings/getting-started/bytearray/src/bytearray.cairo:contract}}
```

### Operations

ByteArrays also provide a set of operations that facilitate the manipulation of strings.
Here are the available operations on an instance of `ByteArray`:

- `append(mut other: @ByteArray)` - Append another ByteArray to the current one.
- `append_word(word: felt252, len: usize)` - Append a short string to the ByteArray. You **need to ensure** that `len` is at most 31 and that `word` can be converted to a `bytes31` with maximum `len` bytes/characters.
- `append_byte(byte: felt252)` - Append a single byte/character to the end of the ByteArray.
- `len() -> usize` - Get the length of the ByteArray.
- `at(index: usize) -> Option<u8>` - Access the character at the given index.
- `rev() -> ByteArray` - Return a new ByteArray with the characters of the original one in reverse order.
- `append_word_rev(word: felt252, len: usize)` - Append a short string to the ByteArray in reverse order. You **need to ensure** again that `len` is at most 31 and that `word` can be converted to a `bytes31` with maximum `len` bytes/characters.

Additionally, there are some operations that can be called as static functions:

- `concat(left: @ByteArray, right: @ByteArray)` - Concatenate two ByteArrays.

Concatenation of `ByteArray` (`append(mut other: @ByteArray)`) can also be done with the `+` and `+=` operators directly, and access to a specific index can be done with the `[]` operator (with the maximum index being `len() - 1`).
2 changes: 1 addition & 1 deletion src/ch00/cairo_cheatsheet/felt.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Felt252

Felt252 is a fundamental data type in Cairo from which all other data types are derived.
Felt252 can also be used to store short-string representations with a maximum length of 31 characters.
Felt252 can also be used to store [short string representations](../basics/bytearrays-strings.md#short-strings) with a maximum length of 31 characters.

For example:

Expand Down
2 changes: 2 additions & 0 deletions src/ch02/storing_arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

On Starknet, complex values (e.g., tuples or structs), are stored in a continuous segment starting from the address of the storage variable. There is a 256 field elements limitation to the maximal size of a complex storage value, meaning that to store arrays of more than 255 elements in storage, we would need to split it into segments of size `n <= 255` and store these segments in multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you will need to write your own implementation of the `Store` trait for the type of array you wish to store.

However, the `ByteArray` struct can be used to store `Array<bytes31>` in storage without additional implementation. Before implementing your own `Store` trait, consider wether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../ch00/basics/bytearrays-strings.md#bytearray-long-strings) section for more information.

> Note: While storing arrays in storage is possible, it is not always recommended, as the read and write operations can get very costly. For example, reading an array of size `n` requires `n` storage reads, and writing to an array of size `n` requires `n` storage writes. If you only need to access a single element of the array at a time, it is recommended to use a `LegacyMap` and store the length in another variable instead.
The following example demonstrates how to write a simple implementation of the `StorageAccess` trait for the `Array<felt252>` type, allowing us to store arrays of up to 255 `felt252` elements.
Expand Down
2 changes: 1 addition & 1 deletion src/starknet-by-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ For more resources, check [Awesome Starknet](https://github.com/keep-starknet-st

The current version of this book use:
```
cairo 2.4.0
cairo 2.5.4
edition = '2023_11'
{{#include ../.tool-versions}}
```

0 comments on commit d569d57

Please sign in to comment.