Rust
Pinocchio AMM

Pinocchio AMM

交换

swap 指令执行两个主要任务:

  • 计算当我们向 amm 发送一定数量的 mint_y 时将收到多少 mint_x(反之亦然),并包含手续费。

  • from 代币转移到 vault,并将 to 代币转移到用户的代币账户。

initialize 指令部分所述;为了优化,我们将所有 Associated Token Accounts 初始化在指令之外。

所需账户

此上下文需要以下账户:

  • user:执行 AMM 交换的用户。必须是 signer

  • user_x_ata:用户的 token X 关联代币账户。必须作为 mutable 传入。

  • user_y_ata:用户的 token Y 关联代币账户。必须作为 mutable 传入。

  • vault_x:池子持有 token X 的代币账户。必须作为 mutable 传入。

  • vault_y:池子持有 token Y 的代币账户。必须作为 mutable 传入。

  • config:AMM 配置账户。保存池子的状态和参数。

  • token_program:代币程序账户。它用于执行转账和铸造等代币操作。必须是 executable

这里,我再次把实现留给你:

rust
pub struct SwapAccounts<'a> {
    pub user: &'a AccountInfo,
    pub user_x_ata: &'a AccountInfo,
    pub user_y_ata: &'a AccountInfo,
    pub vault_x: &'a AccountInfo,
    pub vault_y: &'a AccountInfo,
    pub config: &'a AccountInfo,
    pub token_program: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for SwapAccounts<'a> {
    type Error = ProgramError;

    fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
        //..
    }
}
Expand
[2 more lines]

指令数据

以下是我们需要传入的指令数据:

  • is_x:该 swap 是从 token X 到 token Y,还是反过来;需要它来正确对齐账户。必须是 [u8]

  • amount:用户愿意发送的 token 数量,以换取该交易对中的另一种 token。必须是 [u64]

  • min:用户愿意为 amount 接受的最小 token 数量。必须是 [u64]

  • expiration:该订单的过期时间。确保交易必须在一定时间内完成非常重要。必须是 [i64]

我们将像初始化一样处理 SwapInstructionData 的实现。所以我将实现留给你:

rust
pub struct SwapInstructionData {
    pub is_x: bool,
    pub amount: u64,
    pub min: u64,
    pub expiration: i64,
}

impl<'a> TryFrom<&'a [u8]> for SwapInstructionData {
    type Error = ProgramError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        //..
    }
}

请确保像 amountmin 这样的数量都大于零,并且使用 Clock sysvar 确保订单尚未过期。

指令逻辑

我们首先反序列化 instruction_dataaccounts

然后我们需要:

  • 加载 Config 账户以获取其中的所有数据。我们可以使用 Config::load() 辅助工具来完成。

  • 验证 AmmState 是否有效(也就是它是否等于 AmmState::Initialized)。

  • 检查 vault_xvault_y 的派生是否为关联代币账户(Associated Token Accounts)。

  • 反序列化所有涉及的代币账户,并使用其中的数据通过 constant-product-curve crate 计算交换数量,并像这样检查滑点:

rust
// Deserialize the token accounts
let vault_x = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_x)? };
let vault_y = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_y)? };

// Swap Calculations
let mut curve = ConstantProduct::init(
    vault_x.amount(),
    vault_y.amount(),
    vault_x.amount(),
    config.fee(),
    None,
)
.map_err(|_| ProgramError::Custom(1))?;

let p = match self.instruction_data.is_x {
    true => LiquidityPair::X,
    false => LiquidityPair::Y,
};

let swap_result = curve
    .swap(p, self.instruction_data.amount, self.instruction_data.min)
    .map_err(|_| ProgramError::Custom(1))?;

// Check for correct values
if swap_result.deposit == 0 || swap_result.withdraw == 0 {
    return Err(ProgramError::InvalidArgument);
}
Expand
[12 more lines]
  • 将代币转账批量合并到单一 CPI 中。你已经在 deposit 里见过 batch 的模式,所以这里我们对两条 Transfer 指令做同样的事情,只调用一次代币程序。由于从 vault 转出的那笔转账仍然需要 config PDA 签名,batch 应该以 batch.invoke_signed(&[signer])?; 结束。

rust
const MAX_ACCOUNTS_LEN: usize = Transfer::MAX_ACCOUNTS_LEN * 2;
const MAX_DATA_LEN: usize = Batch::header_data_len(2) + Transfer::DATA_LEN * 2;

// ...

let mut batch_state = BatchState::new(MAX_ACCOUNTS_LEN, MAX_DATA_LEN);
let mut batch = batch_state.as_batch()?;

if self.instruction_data.is_x {
    Transfer::new(
        // ...
    )
    .into_batch(&mut batch)?;

    Transfer::new(
        // ...
    )
    .into_batch(&mut batch)?;
} else {
    Transfer::new(
        // ...
    )
    .into_batch(&mut batch)?;

    Transfer::new(
        // ...
    )
    .into_batch(&mut batch)?;
}

batch.invoke_signed(&[signer])?;
Expand
[16 more lines]

请确保 pinocchio-token 至少为 0.6.0 版本,以支持 p-token 的批量操作。

你应该已经足够熟练可以独立完成这部分内容,所以我将实现部分留给你:

rust
pub struct Swap<'a> {
    pub accounts: SwapAccounts<'a>,
    pub instruction_data: SwapInstructionData,
}

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

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

        // Return the initialized struct
        Ok(Self {
            accounts,
            instruction_data,
        })
    }
}

impl<'a> Swap<'a> {
    pub const DISCRIMINATOR: &'a u8 = &3;

    pub fn process(&mut self) -> ProgramResult {
        //..

        Ok(())
    }
}
Expand
[14 more lines]
Blueshift © 2026Commit: 3c44267