10个版本 (6个破坏性)
0.7.0 | 2024年6月25日 |
---|---|
0.6.3 | 2024年3月28日 |
0.5.0 | 2023年12月11日 |
0.4.0 | 2023年9月26日 |
0.2.0 | 2023年6月26日 |
#1 in #discriminator
183,584 每月下载量
用于 451 个crate(4直接)
235KB
4.5K SLoC
TLV账户解析
定义了一个泛型状态接口的库,用于使用类型-长度-值结构编码指令所需的附加账户。
示例用法
如果您想将指令所需的附加账户编码到账户中的TLV条目中,可以执行以下操作
use {
solana_program::{account_info::AccountInfo, instruction::{AccountMeta, Instruction}, pubkey::Pubkey},
spl_discriminator::{ArrayDiscriminator, SplDiscriminate},
spl_tlv_account_resolution::{
account::ExtraAccountMeta,
seeds::Seed,
state::ExtraAccountMetaList
},
};
struct MyInstruction;
impl SplDiscriminate for MyInstruction {
// For ease of use, give it the same discriminator as its instruction definition
const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]);
}
// Prepare the additional required account keys and signer / writable
let extra_metas = [
AccountMeta::new(Pubkey::new_unique(), false).into(),
AccountMeta::new_readonly(Pubkey::new_unique(), true).into(),
ExtraAccountMeta::new_with_seeds(
&[
Seed::Literal {
bytes: b"some_string".to_vec(),
},
Seed::InstructionData {
index: 1,
length: 1, // u8
},
Seed::AccountKey { index: 1 },
],
false,
true,
).unwrap(),
ExtraAccountMeta::new_external_pda_with_seeds(
0,
&[Seed::AccountKey { index: 2 }],
false,
false,
).unwrap(),
];
// Allocate a new buffer with the proper `account_size`
let account_size = ExtraAccountMetaList::size_of(extra_metas.len()).unwrap();
let mut buffer = vec![0; account_size];
// Initialize the structure for your instruction
ExtraAccountMetaList::init::<MyInstruction>(&mut buffer, &extra_metas).unwrap();
// Off-chain, you can add the additional accounts directly from the account data
// You need to provide the resolver a way to fetch account data off-chain
let client = RpcClient::new_mock("succeeds".to_string());
let program_id = Pubkey::new_unique();
let mut instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);
ExtraAccountMetaList::add_to_instruction::<_, _, MyInstruction>(
&mut instruction,
|address: &Pubkey| {
client
.get_account(address)
.map_ok(|acct| Some(acct.data))
},
&buffer,
)
.await
.unwrap();
// On-chain, you can add the additional accounts *and* account infos
let mut cpi_instruction = Instruction::new_with_bytes(program_id, &[0, 1, 2], vec![]);
// Include all of the well-known required account infos here first
let mut cpi_account_infos = vec![];
// Provide all "remaining_account_infos" that are *not* part of any other known interface
let remaining_account_infos = &[];
ExtraAccountMetaList::add_to_cpi_instruction::<MyInstruction>(
&mut cpi_instruction,
&mut cpi_account_infos,
&buffer,
&remaining_account_infos,
).unwrap();
为了在链上使用方便,《code>ExtraAccountMetaList::init 也提供了直接从给定账户集合初始化的功能。
动机
Solana账户模型对程序接口提出了独特的挑战。由于无法在链上加载附加账户,如果程序需要附加账户以正确实现指令,客户端就无法明确地检索这些账户。
有两种主要方法可以检索附加账户,一种是动态通过程序模拟,另一种是静态地检索账户数据。此库实现了静态的附加账户解析。有关动态账户解析的更多信息,请参阅附录。
静态账户解析
程序可以将所需的附加账户信息写入账户数据中,这样链上和链下客户端只需读取数据即可确定所需的附加账户。
而不是通过程序执行动态地公开此数据,此方法使用静态账户数据。
例如,让我们想象存在一个 Transferable
接口,以及一个 transfer
指令。一些实现 transfer
的程序可能需要比接口中定义的更多账户。链上或链下客户端如何确定所需的额外账户呢?
“静态”方法要求程序将额外所需的账户写入在给定地址上定义的账户。这可以直接在 mint
中进行,或者从 mint 地址派生的某些地址。
链下,客户端必须获取这个额外账户并读取其数据,以找出所需的额外账户,然后将它们包含在指令中。
链上,程序必须能够访问包含特殊账户和所有其他所需账户的“剩余账户信息”,以便正确创建 CPI 指令并给出正确的账户信息。
这种方法也可以称为“状态接口”。
所需账户的类型
该库能够存储两种类型的额外所需账户配置
- 具有固定地址的账户
- 具有由种子派生的 动态程序地址 的账户,这些种子可能来自以下任何组合
- 硬编码的值,如字符串字面量或整数
- 提供给 transfer-hook 程序的指令数据的切片
- 总账户列表中另一个账户的地址
- 指令中另一个账户的程序 ID
当在额外所需账户中存储动态程序地址的配置时,PDA 本身将在指令调用时使用指令本身进行评估(或解析)。这发生在下面提到的链下和链上辅助程序中,这些辅助程序利用 SPL TLV 账户解析库自动执行此解析。
它是如何工作的
该库使用 spl-type-length-value
从账户数据中读取和写入所需的指令账户。
接口指令必须有一个 8 字节的选择器,以便暴露的 ExtraAccountMetaList
类型可以使用指令选择器作为 ArrayDiscriminator
,这允许该选择器作为识别特定指令条目的唯一 TLV 选择器。
这可能会令人困惑。通常,类型实现 SplDiscriminate
,以便可以将类型写入 TLV 数据。在这种情况下,ExtraAccountMetaList
是泛化的 SplDiscriminate
,这意味着程序可以使用不同的 ArrayDiscriminator
将多个 ExtraAccountMetaList
实例写入一个账户。
此外,它还重用了指令判别器作为TLV判别器。例如,如果 transfer
指令的判别器为 [1, 2, 3, 4, 5, 6, 7, 8]
,那么账户使用TLV判别器 [1, 2, 3, 4, 5, 6, 7, 8]
来表示附加账户元数据的存储位置。
这不是必需的,但使客户端更容易找到指令所需的附加账户。
附录
动态账户解析
为了暴露所需的附加账户,指令接口可以包括补充指令以返回所需的账户。
例如,在 Transferable
接口示例中,除了 transfer
指令外,还需要实现者公开一个 get_additional_accounts_for_transfer
指令。
在程序实现中,此指令将附加账户写入返回数据,使链上和链下客户端易于消费。
有关动态方法的更多信息,请参阅相关sRFC。
依赖项
~16–24MB
~338K SLoC