Skip to content

Commit

Permalink
List example (#124)
Browse files Browse the repository at this point in the history
* added list exmaples and tests

* list.md updated and formatted the code

* documentation updated and replaced #external(v0) with #abi(embed_v0)

* updated: list.md

* updated: SUMMARY.md

* list example moved to ch02 and defined interface

* removed submodule: using_lists

* added using_lists

* git submodule removed

* apply fmt

---------

Co-authored-by: julio4 <jules.doumeche@gmail.com>
  • Loading branch information
Mohiiit and julio4 authored Nov 27, 2023
1 parent 35ec815 commit 5cd4ad3
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 0 deletions.
8 changes: 8 additions & 0 deletions listings/ch00-getting-started/cairo_cheatsheet/Scarb.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "alexandria_storage"
version = "0.2.0"
source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f"

[[package]]
name = "cairo_cheatsheet"
version = "0.1.0"
dependencies = [
"alexandria_storage",
]
1 change: 1 addition & 0 deletions listings/ch00-getting-started/cairo_cheatsheet/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ version = "0.1.0"

[dependencies]
starknet = ">=2.3.0"
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="ae1d514"}

[[target.starknet-contract]]
1 change: 1 addition & 0 deletions listings/ch02-advanced-concepts/using_lists/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
14 changes: 14 additions & 0 deletions listings/ch02-advanced-concepts/using_lists/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "alexandria_storage"
version = "0.2.0"
source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f"

[[package]]
name = "using_lists"
version = "0.1.0"
dependencies = [
"alexandria_storage",
]
9 changes: 9 additions & 0 deletions listings/ch02-advanced-concepts/using_lists/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "using_lists"
version = "0.1.0"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
starknet = ">=2.3.0"
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="ae1d514"}
72 changes: 72 additions & 0 deletions listings/ch02-advanced-concepts/using_lists/src/contract.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#[starknet::interface]
trait IListExample<TContractState> {
fn add_in_amount(ref self: TContractState, number: u128);
fn add_in_task(ref self: TContractState, description: felt252, status: felt252);
fn is_empty_list(self: @TContractState) -> bool;
fn list_length(self: @TContractState) -> u32;
fn get_from_index(self: @TContractState, index: u32) -> u128;
fn set_from_index(ref self: TContractState, index: u32, number: u128);
fn pop_front_list(ref self: TContractState);
fn array_conversion(self: @TContractState) -> Array<u128>;
}

#[starknet::contract]
mod ListExample {
use alexandria_storage::list::{List, ListTrait};

#[storage]
struct Storage {
amount: List<u128>,
tasks: List<Task>
}

#[derive(Copy, Drop, Serde, starknet::Store)]
struct Task {
description: felt252,
status: felt252
}


#[abi(embed_v0)]
impl ListExample of super::IListExample<ContractState> {
fn add_in_amount(ref self: ContractState, number: u128) {
let mut current_amount_list = self.amount.read();
current_amount_list.append(number);
}

fn add_in_task(ref self: ContractState, description: felt252, status: felt252) {
let new_task = Task { description: description, status: status };
let mut current_tasks_list = self.tasks.read();
current_tasks_list.append(new_task);
}

fn is_empty_list(self: @ContractState) -> bool {
let mut current_amount_list = self.amount.read();
current_amount_list.is_empty()
}

fn list_length(self: @ContractState) -> u32 {
let mut current_amount_list = self.amount.read();
current_amount_list.len()
}

fn get_from_index(self: @ContractState, index: u32) -> u128 {
self.amount.read()[index]
}

fn set_from_index(ref self: ContractState, index: u32, number: u128) {
let mut current_amount_list = self.amount.read();
current_amount_list.set(index, number);
}

fn pop_front_list(ref self: ContractState) {
let mut current_amount_list = self.amount.read();
current_amount_list.pop_front();
}

fn array_conversion(self: @ContractState) -> Array<u128> {
let mut current_amount_list = self.amount.read();
current_amount_list.array()
}
}
}
4 changes: 4 additions & 0 deletions listings/ch02-advanced-concepts/using_lists/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod contract;

#[cfg(test)]
mod tests;
102 changes: 102 additions & 0 deletions listings/ch02-advanced-concepts/using_lists/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use using_lists::contract::IListExample;
use core::array::ArrayTrait;
use using_lists::contract::ListExample;
use using_lists::contract::ListExample::{
Task, amountContractMemberStateTrait, tasksContractMemberStateTrait
};

fn STATE() -> ListExample::ContractState {
ListExample::contract_state_for_testing()
}

#[test]
#[available_gas(2000000)]
fn test_add_in_amount() {
let mut state = STATE();
state.add_in_amount(200);
assert(state.amount.read()[0] == 200, 'should be 200');
}

#[test]
#[available_gas(2000000)]
fn test_add_in_task() {
let mut state = STATE();
state.add_in_task('test_description', 'test_status');
let current_task: Task = state.tasks.read()[0];
assert(current_task.description == 'test_description', 'should be test_description');
assert(current_task.status == 'test_status', 'should be test_status');
}

#[test]
#[available_gas(2000000)]
fn test_is_empty_list() {
let mut state = STATE();

let pre_addition = state.is_empty_list();
assert(pre_addition == true, 'should be true');

state.add_in_amount(200);
let post_addition = state.is_empty_list();
assert(post_addition == false, 'should be false');
}

#[test]
#[available_gas(2000000)]
fn test_list_length() {
let mut state = STATE();

let pre_addition = state.list_length();
assert(pre_addition == 0, 'should be zero');

state.add_in_amount(200);
let post_addition = state.list_length();
assert(post_addition == 1, 'should be 1');
}

#[test]
#[available_gas(2000000)]
fn test_get_from_index() {
let mut state = STATE();
state.add_in_amount(200);
let output = state.get_from_index(0);
assert(output == 200, 'should be 200');
}

#[test]
#[available_gas(2000000)]
fn test_set_from_index() {
let mut state = STATE();
state.add_in_amount(200);
state.set_from_index(0, 400);
assert(state.amount.read()[0] == 400, 'should be 400');
}

#[test]
#[available_gas(2000000)]
fn test_pop_front_list() {
let mut state = STATE();

state.add_in_amount(200);
let pre_pop_front = state.list_length();
assert(pre_pop_front == 1, 'should be 1');

state.pop_front_list();
let post_pop_front = state.list_length();
assert(post_pop_front == 0, 'should be zero');
}

#[test]
#[available_gas(2000000)]
fn test_array_conversion() {
let mut ideal_array = ArrayTrait::<u128>::new();
ideal_array.append(200);
ideal_array.append(400);

let mut state = STATE();

state.add_in_amount(200);
state.add_in_amount(400);
let output: Array<u128> = state.array_conversion();

assert(output == ideal_array, 'should be equal');
}
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ Summary
- [Hash Solidity Compatible](./ch02/hash-solidity-compatible.md)
- [Optimisations](./ch02/optimisations/optimisations.md)
- [Storage Optimisations](./ch02/optimisations/store_using_packing.md)
- [List](./ch02/list.md)
67 changes: 67 additions & 0 deletions src/ch02/list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# List

By default, there is no list type supported in Cairo, but you can use Alexandria. You can refer to the [Alexandria documentation](https://github.com/keep-starknet-strange/alexandria/tree/main/src/storage) for more details.

## What is `List`?

An ordered sequence of values that can be used in Starknet storage:

```rust
#[storage]
stuct Storage {
amounts: List<u128>
}
```

### Interface

```rust
trait ListTrait<T> {
fn len(self: @List<T>) -> u32;
fn is_empty(self: @List<T>) -> bool;
fn append(ref self: List<T>, value: T) -> u32;
fn get(self: @List<T>, index: u32) -> Option<T>;
fn set(ref self: List<T>, index: u32, value: T);
fn pop_front(ref self: List<T>) -> Option<T>;
fn array(self: @List<T>) -> Array<T>;
}
```

`List` also implements `IndexView` so you can use the familiar bracket notation to access its members:

```rust
let second = self.amounts.read()[1];
```

Note that unlike `get`, using this bracket notation panics when accessing an out of bounds index.

### Support for custom types

`List` supports most of the corelib types out of the box. If you want to store a your own custom type in a `List`, it has to implement the `Store` trait. You can have the compiler derive it for you using the `#[derive(starknet::Store)]` attribute.

### Caveats

There are two idiosyncacies you should be aware of when using `List`

1. The `append` operation costs 2 storage writes - one for the value itself and another one for updating the List's length
2. Due to a compiler limitation, it is not possible to use mutating operations with a single inline statement. For example, `self.amounts.read().append(42);` will not work. You have to do it in 2 steps:

```rust
let mut amounts = self.amounts.read();
amounts.append(42);
```

### Dependencies

Update your project dependencies by in the `Scarb.toml` file:
```rust
[dependencies]
(...)
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" }
```

For example, let's use `List` to create a contract that tracks a list of amounts and tasks:

```rust
{{#include ../../../listings/ch02-advanced-concepts/using-lists/src/contract.cairo}}
```

0 comments on commit 5cd4ad3

Please sign in to comment.