8 个版本 (5 个重大更改)

0.5.0 2024年6月25日
0.4.6 2024年7月11日
0.4.3 2024年3月28日
0.3.1 2024年2月5日
0.0.1 2023年5月18日

#2#discriminator

Download history 28986/week @ 2024-05-06 30993/week @ 2024-05-13 24504/week @ 2024-05-20 27637/week @ 2024-05-27 30142/week @ 2024-06-03 32357/week @ 2024-06-10 31076/week @ 2024-06-17 33456/week @ 2024-06-24 28729/week @ 2024-07-01 31375/week @ 2024-07-08 30517/week @ 2024-07-15 35126/week @ 2024-07-22 36796/week @ 2024-07-29 56659/week @ 2024-08-05 39445/week @ 2024-08-12 36182/week @ 2024-08-19

170,997 每月下载量
459 个软件包中(16 个直接使用)

Apache-2.0

125KB
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_value2.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 特性来使用 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);

依赖项

~16–24MB
~339K SLoC