Rust
Pinocchio 量子金库

Pinocchio 量子金库

9 Graduates

打开保险库

open 指令会创建一个程序派生地址(PDA)保险库,用于安全存放 lamports。此 PDA 使用哈希公钥作为种子,确保只有原始密钥的持有者才能在之后提取资金。

PDA 提供了一种安全存储资金的方式,因为:

  • 只有您的程序可以控制账户(不存在私钥)

  • 保险库地址是从用户的哈希值确定性派生的

  • 提款只能通过您的程序逻辑进行

所需账户

该指令需要以下账户:

  • payer:支付保险库创建费用(必须是签名者且可变)

  • vault:正在初始化的 PDA(必须是可变的)

  • system_program:账户创建所需(必须是可执行的)

在代码中,这看起来像这样:

rust
pub struct OpenVaultAccounts<'a> {
    pub payer: &'a AccountInfo,
    pub vault: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for OpenVaultAccounts<'a> {
    type Error = ProgramError;
 
    fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
        let [payer, vault, _system_program] = accounts else {
            return Err(ProgramError::NotEnoughAccountKeys);
        };
 
        Ok(Self { payer, vault })
    }
}

账户验证由 CreateAccount CPI 处理。如果账户不符合要求(签名者、可变性、可执行性),指令将自动失败。

指令数据

需要两部分数据:

  • hash:用户 Winternitz 密钥对公钥的 SHA-256 哈希值 ([u8; 32])

  • bump:客户端传递的 PDA 派生 bump 值 (u8)

我们从客户端传递 bump 值,而不是在链上派生,以节省计算成本。

代码中如下所示:

rust
pub struct OpenVaultInstructionData {
    pub hash: [u8; 32],
    pub bump: [u8; 1],
}

impl<'a> TryFrom<&'a [u8]> for OpenVaultInstructionData {
    type Error = ProgramError;
 
    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        if data.len() != core::mem::size_of::<OpenVaultInstructionData>() {
            return Err(ProgramError::InvalidInstructionData);
        }

        let hash = data[0..32].try_into().map_err(|_| ProgramError::InvalidInstructionData)?;
        let bump = data[32..33].try_into().map_err(|_| ProgramError::InvalidInstructionData)?;
 
        Ok(Self { hash, bump })
    }
}

再次说明,无效参数会自动纠正:传递错误的哈希值会使保险库失效,传递错误的 bump 值会导致 PDA 派生失败。

指令逻辑

该指令创建了一个分配给我们程序的空 PDA。虽然账户不包含数据,但程序所有权确保提款只能通过我们控制的逻辑进行。

即使是空账户,也必须达到免租金标准才能在 Solana 上持久存在。

以下是代码中的实现方式:

rust
pub struct OpenVault<'a> {
    pub accounts: OpenVaultAccounts<'a>,
    pub instruction_data: OpenVaultInstructionData,
}

impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for OpenVault<'a> {
    type Error = ProgramError;

    fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
        let instruction_data = OpenVaultInstructionData::try_from(data)?;
        let accounts = OpenVaultAccounts::try_from(accounts)?;

        Ok(Self { accounts, instruction_data })
    }
}

impl<'a> OpenVault<'a> {
    pub const DISCRIMINATOR: &'a u8 = &0;
 
    pub fn process(&self) -> ProgramResult {
        let lamports = Rent::get()?.minimum_balance(0);
        let seeds = [Seed::from(&self.instruction_data.hash), Seed::from(&self.instruction_data.bump)];

        // Create our vault
        CreateAccount {
            from: self.accounts.payer,
            to: self.accounts.vault,
            lamports,
            space: 0,
            owner: &crate::ID,
        }
        .invoke_signed(&[Signer::from(&seeds)])
    }
}

此指令仅创建金库结构。存款通过简单的 lamport 转账到金库账户单独处理。

Next Page拆分金库
或者跳到挑战
准备接受挑战了吗?
Blueshift © 2025Commit: e573eab