存储优化

智能合约只有有限的存储槽位。每个槽位可以存储一个 felt252 值。 写入一个存储槽位会产生成本,因此我们希望尽可能少地使用存储槽位。

在 Cairo 中,每种类型都源自 felt252 类型,它使用 252 位来存储一个值。 这种设计相当简单,但它有一个缺点:它在存储效率方面并不高。例如,如果要存储一个 u8 值,我们需要使用整个槽位,尽管我们只需要 8 位。

打包

当存储多个值时,我们可以使用一种称为**打包(packing)**的技术。打包是一种允许我们在单个 felt 值中存储多个值的技术。这是通过使用 felt 值的位来存储多个值来实现的。

例如,如果我们想存储两个 u8 值,我们可以使用 felt 值的前 8 位来存储第一个 u8 值,而使用后 8 位来存储第二个 u8 值。这样,我们就可以在单个 felt 值中存储两个 u8 值。

Cairo 提供了一个内置的打包存储功能,您可以通过 StorePacking 特性来使用它。

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

这允许通过首先使用 pack 函数将类型 T 打包成 PackedT 类型,然后使用其 Store 实现来存储 PackedT 值。在读取值时,我们首先获取 PackedT 值,然后使用 unpack 函数将其解包为类型 T

以下是一个使用 StorePacking 特性存储包含两个 u8 值的 Time 结构体的示例:

#[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();
        }
    }
}

Remix 上测试这个合约.

Last change: 2023-10-12, commit: 90aa7c0