8 个版本 (5 个重大更改)
0.5.0 | 2024年6月25日 |
---|---|
0.4.6 | 2024年7月11日 |
0.4.3 | 2024年3月28日 |
0.3.1 |
|
0.0.1 | 2023年5月18日 |
#2 在 #discriminator
170,997 每月下载量
在 459 个软件包中(16 个直接使用)
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