From 5cd4ad3c516b23c46891247fa6998c857417e266 Mon Sep 17 00:00:00 2001 From: Mohiiit <48082542+Mohiiit@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:15:41 +0530 Subject: [PATCH] List example (#124) * 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 --- .../cairo_cheatsheet/Scarb.lock | 8 ++ .../cairo_cheatsheet/Scarb.toml | 1 + .../using_lists/.gitignore | 1 + .../using_lists/Scarb.lock | 14 +++ .../using_lists/Scarb.toml | 9 ++ .../using_lists/src/contract.cairo | 72 +++++++++++++ .../using_lists/src/lib.cairo | 4 + .../using_lists/src/tests.cairo | 102 ++++++++++++++++++ src/SUMMARY.md | 1 + src/ch02/list.md | 67 ++++++++++++ 10 files changed, 279 insertions(+) create mode 100644 listings/ch02-advanced-concepts/using_lists/.gitignore create mode 100644 listings/ch02-advanced-concepts/using_lists/Scarb.lock create mode 100644 listings/ch02-advanced-concepts/using_lists/Scarb.toml create mode 100644 listings/ch02-advanced-concepts/using_lists/src/contract.cairo create mode 100644 listings/ch02-advanced-concepts/using_lists/src/lib.cairo create mode 100644 listings/ch02-advanced-concepts/using_lists/src/tests.cairo create mode 100644 src/ch02/list.md diff --git a/listings/ch00-getting-started/cairo_cheatsheet/Scarb.lock b/listings/ch00-getting-started/cairo_cheatsheet/Scarb.lock index 87f8b0ef..b5eeb960 100644 --- a/listings/ch00-getting-started/cairo_cheatsheet/Scarb.lock +++ b/listings/ch00-getting-started/cairo_cheatsheet/Scarb.lock @@ -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", +] diff --git a/listings/ch00-getting-started/cairo_cheatsheet/Scarb.toml b/listings/ch00-getting-started/cairo_cheatsheet/Scarb.toml index 1c3c4055..0ca087ea 100644 --- a/listings/ch00-getting-started/cairo_cheatsheet/Scarb.toml +++ b/listings/ch00-getting-started/cairo_cheatsheet/Scarb.toml @@ -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]] diff --git a/listings/ch02-advanced-concepts/using_lists/.gitignore b/listings/ch02-advanced-concepts/using_lists/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/ch02-advanced-concepts/using_lists/Scarb.lock b/listings/ch02-advanced-concepts/using_lists/Scarb.lock new file mode 100644 index 00000000..12d040a4 --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/Scarb.lock @@ -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", +] diff --git a/listings/ch02-advanced-concepts/using_lists/Scarb.toml b/listings/ch02-advanced-concepts/using_lists/Scarb.toml new file mode 100644 index 00000000..37b58ba6 --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/Scarb.toml @@ -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"} diff --git a/listings/ch02-advanced-concepts/using_lists/src/contract.cairo b/listings/ch02-advanced-concepts/using_lists/src/contract.cairo new file mode 100644 index 00000000..53999608 --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/src/contract.cairo @@ -0,0 +1,72 @@ +#[starknet::interface] +trait IListExample { + 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; +} + +#[starknet::contract] +mod ListExample { + use alexandria_storage::list::{List, ListTrait}; + + #[storage] + struct Storage { + amount: List, + tasks: List + } + + #[derive(Copy, Drop, Serde, starknet::Store)] + struct Task { + description: felt252, + status: felt252 + } + + + #[abi(embed_v0)] + impl ListExample of super::IListExample { + 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 { + let mut current_amount_list = self.amount.read(); + current_amount_list.array() + } + } +} diff --git a/listings/ch02-advanced-concepts/using_lists/src/lib.cairo b/listings/ch02-advanced-concepts/using_lists/src/lib.cairo new file mode 100644 index 00000000..11ada17a --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/src/lib.cairo @@ -0,0 +1,4 @@ +mod contract; + +#[cfg(test)] +mod tests; diff --git a/listings/ch02-advanced-concepts/using_lists/src/tests.cairo b/listings/ch02-advanced-concepts/using_lists/src/tests.cairo new file mode 100644 index 00000000..3b75a5ba --- /dev/null +++ b/listings/ch02-advanced-concepts/using_lists/src/tests.cairo @@ -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::::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 = state.array_conversion(); + + assert(output == ideal_array, 'should be equal'); +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8de27a3c..edf3ff5a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/ch02/list.md b/src/ch02/list.md new file mode 100644 index 00000000..4613dbe9 --- /dev/null +++ b/src/ch02/list.md @@ -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 +} +``` + +### Interface + +```rust +trait ListTrait { + fn len(self: @List) -> u32; + fn is_empty(self: @List) -> bool; + fn append(ref self: List, value: T) -> u32; + fn get(self: @List, index: u32) -> Option; + fn set(ref self: List, index: u32, value: T); + fn pop_front(ref self: List) -> Option; + fn array(self: @List) -> Array; +} +``` + +`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}} +``` \ No newline at end of file