6 个版本
0.3.5 | 2024 年 3 月 29 日 |
---|---|
0.3.4 | 2023 年 12 月 24 日 |
0.3.3 | 2023 年 11 月 19 日 |
#609 在 神奇豆
526 每月下载次数
用于 70 个 70 个crate (5 直接)
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