#length #byte #byte-length #value #discriminator #account #tlv

solarti-type-length-value

Solarti 程序库类型-长度-值管理

6 个版本

0.3.5 2024 年 3 月 29 日
0.3.4 2023 年 12 月 24 日
0.3.3 2023 年 11 月 19 日

#609神奇豆

Download history 153/week @ 2024-04-16 193/week @ 2024-04-23 151/week @ 2024-04-30 155/week @ 2024-05-07 136/week @ 2024-05-14 155/week @ 2024-05-21 189/week @ 2024-05-28 148/week @ 2024-06-04 121/week @ 2024-06-11 181/week @ 2024-06-18 107/week @ 2024-06-25 1/week @ 2024-07-02 101/week @ 2024-07-09 170/week @ 2024-07-16 110/week @ 2024-07-23 144/week @ 2024-07-30

526 每月下载次数
用于 7070 个crate (5 直接)

Apache-2.0

120KB
2K SLoC

类型-长度-值

用于处理类型-长度-值结构的实用程序库。

示例用法

这个简单的示例定义了一个具有其判别器的零拷贝类型。

use {
    borsh::{BorshSerialize, BorshDeserialize},
    bytemuck::{Pod, Zeroable},
    spl_discriminator::{ArrayDiscriminator, SplDiscriminate}
    spl_type_length_value::{
        state::{TlvState, TlvStateBorrowed, TlvStateMut}
    },
};

#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
struct MyPodValue {
    data: [u8; 32],
}
impl SplDiscriminate for MyPodValue {
    // Give it a unique discriminator, can also be generated using a hash function
    const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
struct MyOtherPodValue {
    data: u8,
}
// Give this type a non-derivable implementation of `Default` to write some data
impl Default for MyOtherPodValue {
    fn default() -> Self {
        Self {
            data: 10,
        }
    }
}
impl SplDiscriminate for MyOtherPodValue {
    // Some other unique discriminator
    const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([2; ArrayDiscriminator::LENGTH]);
}

// Account will have two sets of `get_base_len()` (8-byte discriminator and 4-byte length),
// and enough room for a `MyPodValue` and a `MyOtherPodValue`
let account_size = TlvState::get_base_len() + std::mem::size_of::<MyPodValue>() + \
    TlvState::get_base_len() + std::mem::size_of::<MyOtherPodValue>();

// Buffer likely comes from a Solana `solana_program::account_info::AccountInfo`,
// but this example just uses a vector.
let mut buffer = vec![0; account_size];

// Unpack the base buffer as a TLV structure
let mut state = TlvStateMut::unpack(&mut buffer).unwrap();

// Init and write default value
// Note: you'll need to provide a boolean whether or not to allow repeating
// values with the same TLV discriminator.
// If set to false, this function will error when an existing entry is detected.
let value = state.init_value::<MyPodValue>(false).unwrap();
// Update it in-place
value.data[0] = 1;

// Init and write another default value
// This time, we're going to allow repeating values.
let other_value1 = state.init_value::<MyOtherPodValue>(true).unwrap();
assert_eq!(other_value1.data, 10);
// Update it in-place
other_value1.data = 2;

// Let's do it again, since we can now have repeating values!
let other_value2 = state.init_value::<MyOtherPodValue>(true).unwrap();
assert_eq!(other_value2.data, 10);
// Update it in-place
other_value1.data = 4;

// Later on, to work with it again, since we did _not_ allow repeating entries,
// we can just get the first value we encounter.
let value = state.get_first_value_mut::<MyPodValue>().unwrap();

// Or fetch it from an immutable buffer
let state = TlvStateBorrowed::unpack(&buffer).unwrap();
let value1 = state.get_first_value::<MyOtherPodValue>().unwrap();

// Since we used repeating entries for `MyOtherPodValue`, we can grab either one by
// its entry number
let value1 = state.get_value_with_repetition::<MyOtherPodValue>(1).unwrap();
let value2 = state.get_value_with_repetition::<MyOtherPodValue>(2).unwrap();

动机

Solana 区块链向链上程序暴露字节块,允许程序编写者解释这些字节并根据需要更改它们。目前,程序将账户字节解释为只有一种类型。例如,代币铸造账户始终是代币铸造,AMM 池账户始终是 AMM 池,代币元数据账户只能存储代币元数据等。

在一个接口的世界中,程序可能会实现多个接口。作为一个具体且重要的例子,想象一个代币程序,其中铸造持有自己的元数据。这意味着单个账户可以是铸造和元数据。

为了便于实现多个接口,账户必须能够在一个不透明的字节块中存储多个不同的类型。类型-长度-值方案有助于实现这种情况。

工作原理

这个库允许通过编码类型、然后长度、然后值,在同一个账户中存储多个不同的类型。

类型是一个 8 字节的 ArrayDiscriminator,可以设置为任何值。

长度是一个小端序的 u32

值是 length 字节的一个块,程序可以根据需要使用这些字节。

在缓冲区中搜索特定类型时,库会查看前8字节的区分器。如果全部为零,则表示它未初始化。如果不为零,则读取下一个4字节长度。如果区分器匹配,则返回下一个length字节。如果不匹配,则跳过length字节并读取下一个8字节区分器。

可变长度类型的序列化

初始示例使用bytemuck crate进行零拷贝序列化和反序列化。可以通过在您的类型上实现VariableLenPack trait来使用Borsh。

use {
    borsh::{BorshDeserialize, BorshSerialize},
    solana_program::borsh::{get_instance_packed_len, try_from_slice_unchecked},
    spl_type_length_value::{
        state::{TlvState, TlvStateMut},
        variable_len_pack::VariableLenPack
    },
};
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)]
struct MyVariableLenType {
    data: String, // variable length type
}
impl SplDiscriminate for MyVariableLenType {
    const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([5; ArrayDiscriminator::LENGTH]);
}
impl VariableLenPack for MyVariableLenType {
    fn pack_into_slice(&self, dst: &mut [u8]) -> Result<(), ProgramError> {
        borsh::to_writer(&mut dst[..], self).map_err(Into::into)
    }

    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
        try_from_slice_unchecked(src).map_err(Into::into)
    }

    fn get_packed_len(&self) -> Result<usize, ProgramError> {
        get_instance_packed_len(self).map_err(Into::into)
    }
}
let initial_data = "This is a pretty cool test!";
// Allocate exactly the right size for the string, can go bigger if desired
let tlv_size = 4 + initial_data.len();
let account_size = TlvState::get_base_len() + tlv_size;

// Buffer likely comes from a Solana `solana_program::account_info::AccountInfo`,
// but this example just uses a vector.
let mut buffer = vec![0; account_size];
let mut state = TlvStateMut::unpack(&mut buffer).unwrap();

// No need to hold onto the bytes since we'll serialize back into the right place
// For this example, let's _not_ allow repeating entries.
let _ = state.alloc::<MyVariableLenType>(tlv_size, false).unwrap();
let my_variable_len = MyVariableLenType {
    data: initial_data.to_string()
};
state.pack_variable_len_value(&my_variable_len).unwrap();
let deser = state.get_first_variable_len_value::<MyVariableLenType>().unwrap();
assert_eq!(deser, my_variable_len);

依赖项

~21–29MB
~480K SLoC