Rust
Pinocchio 托管

Pinocchio 托管

47 Graduates

托管服务

匹诺曹托管挑战

托管是一种强大的金融工具,可以在两方之间实现安全的代币交换。

可以将其视为一个数字保险箱,其中一位用户可以锁定代币 A,等待另一位用户存入代币 B,然后完成交换。

这创造了一个无需信任的环境,双方都不需要担心对方会退出交易。

在本次挑战中,我们将通过三个简单但强大的指令来实现这一概念:

  • 创建(Make):创建者(第一位用户)定义交易条款,并将约定数量的代币 A 存入一个安全的保险库。这就像将您的物品放入保险箱并设置交换条款。

  • 接受(Take):接受者(第二位用户)通过将承诺的代币 B 转移给创建者来接受报价,并作为回报,获得锁定的代币 A。这是双方完成各自交易的一刻。

  • 退款(Refund):如果创建者改变主意或未找到合适的接受者,他们可以取消报价并取回代币 A。这就像在交易失败时从保险箱中取回您的物品。

注意:如果您不熟悉匹诺曹,建议先阅读匹诺曹简介,以熟悉我们将在本程序中使用的核心概念。

安装

让我们从创建一个全新的 Rust 环境开始:

# create workspace
cargo new blueshift_escrow --lib --edition 2021
cd blueshift_escrow

添加 pinocchio、pinocchio-system、pinocchio-token 和 pinocchio-associated-token:

cargo add pinocchio pinocchio-system pinocchio-token pinocchio-associated-token-account

Cargo.toml 中声明 crate 类型,以便在 target/deploy 中生成部署工件:

toml
[lib]
crate-type = ["lib", "cdylib"]

现在您可以开始编写您的托管程序了。

Template

这次我们将把程序分成小而集中的模块,而不是将所有内容都塞进 lib.rs 中。文件夹结构大致如下:

text
src
├── instructions
│       ├── make.rs
│       ├── helpers.rs
│       ├── mod.rs
│       ├── refund.rs
│       └── take.rs
├── errors.rs
├── lib.rs
└── state.rs

入口点位于 lib.rs 中,与我们在上一课中所做的非常相似,因此我们会快速浏览:

rust
use pinocchio::{account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, ProgramResult};
entrypoint!(process_instruction);

pub mod instructions;
pub use instructions::*;

pub mod state;
pub use state::*;

// 22222222222222222222222222222222222222222222
pub const ID: Pubkey = [
    0x0f, 0x1e, 0x6b, 0x14, 0x21, 0xc0, 0x4a, 0x07,
    0x04, 0x31, 0x26, 0x5c, 0x19, 0xc5, 0xbb, 0xee,
    0x19, 0x92, 0xba, 0xe8, 0xaf, 0xd1, 0xcd, 0x07,
    0x8e, 0xf8, 0xaf, 0x70, 0x47, 0xdc, 0x11, 0xf7,
];

fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    match instruction_data.split_first() {
        Some((Make::DISCRIMINATOR, data)) => Make::try_from((data, accounts))?.process(),
        Some((Take::DISCRIMINATOR, _)) => Take::try_from(accounts)?.process(),
        Some((Refund::DISCRIMINATOR, _)) => Refund::try_from(accounts)?.process(),
        _ => Err(ProgramError::InvalidInstructionData)
    }
}

State

我们将进入 state.rs,其中存储了我们 Escrow 的所有数据。我们将其分为两部分:结构体定义及其实现。

首先,让我们看看结构体定义:

rust
use pinocchio::{program_error::ProgramError, pubkey::Pubkey};
use core::mem::size_of;

#[repr(C)]
pub struct Escrow {
    pub seed: u64,        // Random seed for PDA derivation
    pub maker: Pubkey,    // Creator of the escrow
    pub mint_a: Pubkey,   // Token being deposited
    pub mint_b: Pubkey,   // Token being requested
    pub receive: u64,     // Amount of token B wanted
    pub bump: [u8;1]      // PDA bump seed
}

#[repr(C)] 属性确保我们的结构体具有可预测的内存布局,这对于链上数据至关重要。每个字段都有特定的用途:

  • seed:一个随机数,允许一个创建者使用相同的代币对创建多个托管

  • maker:创建托管并将接收代币的钱包地址

  • mint_a:存入代币的 SPL 代币铸造地址

  • mint_b:请求代币的 SPL 代币铸造地址

  • receive:创建者希望接收的代币 B 的确切数量

  • bump:在 PDA 推导中使用的单字节,用于确保地址不在 Ed25519 曲线上

现在,让我们看看包含所有辅助方法的实现:

rust
impl Escrow {
    pub const LEN: usize = size_of::<u64>() 
    + size_of::<Pubkey>() 
    + size_of::<Pubkey>() 
    + size_of::<Pubkey>() 
    + size_of::<u64>()
    + size_of::<[u8;1]>();

    #[inline(always)]
    pub fn load_mut(bytes: &mut [u8]) -> Result<&mut Self, ProgramError> {
        if bytes.len() != Escrow::LEN {
            return Err(ProgramError::InvalidAccountData);
        }
        Ok(unsafe { &mut *core::mem::transmute::<*mut u8, *mut Self>(bytes.as_mut_ptr()) })
    }

    #[inline(always)]
    pub fn load(bytes: &[u8]) -> Result<&Self, ProgramError> {
        if bytes.len() != Escrow::LEN {
            return Err(ProgramError::InvalidAccountData);
        }
        Ok(unsafe { &*core::mem::transmute::<*const u8, *const Self>(bytes.as_ptr()) })
    }

    #[inline(always)]
    pub fn set_seed(&mut self, seed: u64) {
        self.seed = seed;
    }

    #[inline(always)]
    pub fn set_maker(&mut self, maker: Pubkey) {
        self.maker = maker;
    }

    #[inline(always)]
    pub fn set_mint_a(&mut self, mint_a: Pubkey) {
        self.mint_a = mint_a;
    }

    #[inline(always)]
    pub fn set_mint_b(&mut self, mint_b: Pubkey) {
        self.mint_b = mint_b;
    }

    #[inline(always)]
    pub fn set_receive(&mut self, receive: u64) {
        self.receive = receive;
    }

    #[inline(always)]
    pub fn set_bump(&mut self, bump: [u8;1]) {
        self.bump = bump;
    }

    #[inline(always)]
    pub fn set_inner(&mut self, seed: u64, maker: Pubkey, mint_a: Pubkey, mint_b: Pubkey, receive: u64, bump: [u8;1]) {
        self.seed = seed;
        self.maker = maker;
        self.mint_a = mint_a;
        self.mint_b = mint_b;
        self.receive = receive;
        self.bump = bump;
    }
}

该实现提供了几个关键功能:

  1. 精确的大小计算LEN 通过汇总每个字段的大小,精确计算账户大小

  2. 安全加载load 提供了一种安全的方式来加载和验证托管数据

  3. 性能优化

    • 在 getter 上使用 #[inline(always)] 以实现最大性能

    • 当我们确定借用是安全时,使用不安全方法

    • 使用 set_inner 高效地设置字段

  4. 内存安全:对账户数据长度和所有权进行适当验证

  5. 文档:清晰的注释,解释每个方法的目的和安全注意事项

此实现确保我们的托管状态既安全又高效,在适当的地方进行了适当的验证和性能优化。

Next Page创建
或者跳到挑战
准备接受挑战了吗?
Blueshift © 2025Commit: e573eab