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
Cuando almacenamos múltiples valores, podemos utilizar una técnica llamada packing. El empaquetado es una técnica que nos permite almacenar múltiples valores en un único valor de felt. Esto se hace utilizando los bits del valor de felt para almacenar múltiples valores.
Por ejemplo, si queremos almacenar dos valores u8
, podemos utilizar los primeros 8 bits del valor de felt para almacenar el primer valor u8
, y los últimos 8 bits para almacenar el segundo valor u8
. De esta forma, podemos almacenar dos valores u8
en un único valor de felt.
Cairo proporciona un almacén incorporado que usa empaquetado que puedes usar con el trait StorePacking
.
trait StorePacking<T, PackedT> {
fn pack(value: T) -> PackedT;
fn unpack(value: PackedT) -> T;
}
Esto permite almacenar el tipo T
empaquetándolo primero en el tipo PackedT
con la función pack
, y luego almacenando el valor PackedT
con su implementación Store
. Al leer el valor, primero recuperamos el valor PackedT
, y luego lo desempaquetamos en el tipo T
utilizando la función unpack
.
He aquí un ejemplo de almacenamiento de una estructura Time
con dos valores u8
utilizando el trait StorePacking
:
#[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 };
}
}
#[abi(embed_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();
}
}
}
Juega con este contrato en Remix.