Skip to content

Commit

Permalink
feat: store using packing optimisation (#72)
Browse files Browse the repository at this point in the history
* feat: store using packing optimisation

* feat: add packing contract

* chore: update to v2.2.0
  • Loading branch information
julio4 authored Sep 19, 2023
1 parent 2e36053 commit f17fe80
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions listings/ch03-optimisations/store_using_packing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
8 changes: 8 additions & 0 deletions listings/ch03-optimisations/store_using_packing/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "store_using_packing"
version = "0.1.0"

[dependencies]
starknet = ">=2.2.0"

[[target.starknet-contract]]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod packing;
92 changes: 92 additions & 0 deletions listings/ch03-optimisations/store_using_packing/src/packing.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// ANCHOR: Packing
#[starknet::interface]
trait ITime<TContractState> {
fn set(ref self: TContractState, value: TimeContract::Time);
fn get(self: @TContractState) -> TimeContract::Time;
}

#[starknet::contract]
mod TimeContract {
use starknet::storage_access::StorePacking;
use integer::{
U8IntoFelt252, Felt252TryIntoU16, U16DivRem, u16_as_non_zero, U16IntoFelt252,
Felt252TryIntoU8
};
use traits::{Into, TryInto, DivRem};
use option::OptionTrait;
use serde::Serde;

#[storage]
struct Storage {
time: Time
}

#[derive(Copy, Serde, Drop)]
struct Time {
hour: u8,
minute: u8
}

impl TimePackable of StorePacking<Time, felt252> {
fn pack(value: Time) -> felt252 {
let msb: felt252 = 256 * value.hour.into();
let lsb: felt252 = value.minute.into();
return msb + lsb;
}
fn unpack(value: felt252) -> Time {
let value: u16 = value.try_into().unwrap();
let (q, r) = U16DivRem::div_rem(value, u16_as_non_zero(256));
let hour: u8 = Into::<u16, felt252>::into(q).try_into().unwrap();
let minute: u8 = Into::<u16, felt252>::into(r).try_into().unwrap();
return Time { hour, minute };
}
}

#[external(v0)]
impl TimeContract of super::ITime<ContractState> {
fn set(ref self: ContractState, value: Time) {
// This will call the pack method of the TimePackable trait
// and store the resulting felt252
self.time.write(value);
}
fn get(self: @ContractState) -> Time {
// This will read the felt252 value from storage
// and return the result of the unpack method of the TimePackable trait
return self.time.read();
}
}
}
// ANCHOR_END: Packing

#[cfg(test)]
mod tests {
use super::TimeContract;
use super::TimeContract::Time;
use super::{ITimeDispatcher, ITimeDispatcherTrait};

use starknet::deploy_syscall;
use starknet::class_hash::Felt252TryIntoClassHash;

use debug::PrintTrait;

#[test]
#[available_gas(20000000)]
fn test_packing() {
// Set up.
let mut calldata: Array<felt252> = ArrayTrait::new();
let (address0, _) = deploy_syscall(
TimeContract::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false
)
.unwrap();
let mut contract = ITimeDispatcher { contract_address: address0 };

// Store a Time struct.
let time = Time { hour: 1, minute: 2 };
contract.set(time);

// Read the stored struct.
let read_time: Time = contract.get();
assert(read_time.hour == time.hour, 'Time.hour mismatch');
assert(read_time.minute == time.minute, 'Time.minute mismatch');
}
}
4 changes: 4 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ Summary

- [Upgradeable Contract](./ch02-01-upgradeable_contract.md)
- [Defi Vault](./ch02-02-simple_vault.md)

- [Optimisations](./ch03-00-optimisations.md)

- [Storage Optimisations](./ch03-01-store_using_packing.md)
3 changes: 3 additions & 0 deletions src/ch03-00-optimisations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Optimisations

A collection of optimisation patterns to save gas and steps.
32 changes: 32 additions & 0 deletions src/ch03-01-store_using_packing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Storage optimisation

A smart contract has a limited amount of **storage slots**. Each slot can store a single `felt252` value.
Writing to a storage slot has a cost, so we want to use as few storage slots as possible.

In Cairo, every type is derived from the `felt252` type, which uses 252 bits to store a value.
This design is quite simple, but it does have a drawback: it is not storage efficient. For example, if we want to store a `u8` value, we need to use an entire slot, even though we only need 8 bits.

## Packing

When storing multiple values, we can use a technique called **packing**. Packing is a technique that allows us to store multiple values in a single felt value. This is done by using the bits of the felt value to store multiple values.

For example, if we want to store two `u8` values, we can use the first 8 bits of the felt value to store the first `u8` value, and the last 8 bits to store the second `u8` value. This way, we can store two `u8` values in a single felt value.

Cairo provides a built-in store using packing that you can use with the `StorePacking` trait.

```rust
trait StorePacking<T, PackedT> {
fn pack(value: T) -> PackedT;
fn unpack(value: PackedT) -> T;
}
```

This allows to store the type `T` by first packing it into the type `PackedT` with the `pack` function, and then storing the `PackedT` value with it's `Store` implementation. When reading the value, we first retrieve the `PackedT` value, and then unpack it into the type `T` using the `unpack` function.

Here's an example of storing a `Time` struct with two `u8` values using the `StorePacking` trait:

```rust
{{#include ../listings/ch03-optimisations/store_using_packing/src/packing.cairo:Packing}}
```

Play with this contract in [Remix](https://remix.ethereum.org/?#activate=Starknet-cairo1-compiler&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch03-optimisations/store_using_packing/src/packing.cairo).

0 comments on commit f17fe80

Please sign in to comment.