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

Download history 32503/week @ 2024-04-28 32526/week @ 2024-05-05 34583/week @ 2024-05-12 28926/week @ 2024-05-19 31270/week @ 2024-05-26 34234/week @ 2024-06-02 36196/week @ 2024-06-09 34508/week @ 2024-06-16 37825/week @ 2024-06-23 31625/week @ 2024-06-30 34387/week @ 2024-07-07 33189/week @ 2024-07-14 38873/week @ 2024-07-21 40339/week @ 2024-07-28 61288/week @ 2024-08-04 40779/week @ 2024-08-11

183,584 每月下载量
用于 451 个crate(4直接)

Apache-2.0

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