Rust
Pinocchio AMM

Pinocchio AMM

存款

deposit 指令执行以下三个主要任务:

  • 根据用户希望 mint 的 LP 数量,存入 mint_xmint_y 代币。

  • 计算存款金额,并检查金额是否超过用户指定的 max_xmax_y

  • 在用户的 ata 中铸造正确数量的 mint_lp

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

所需账户

以下是此上下文所需的账户:

  • user:将代币存入 AMM 流动性的用户。必须是 signer

  • mint_lp:代表池流动性的铸币账户。必须作为 mutable 传递。

  • vault_x:存储所有存入池中的 X 代币的代币账户。必须作为 mutable 传递。

  • vault_y:存储所有存入池中的 Y 代币的代币账户。必须作为 mutable 传递。

  • user_x_ata:用户的 X 代币关联账户。这是用户的 X 代币将从中转移到池中的源账户。必须作为 mutable 传递。

  • user_y_ata:用户的 Y 代币关联账户。这是用户的 Y 代币将从中转移到池中的源账户。必须作为 mutable 传递。

  • user_lp_ata:用户的 LP 代币关联账户。这是铸造 LP 代币的目标账户。必须作为 mutable 传递。

  • config:AMM 池的配置账户。存储所有相关的池参数和状态。

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

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

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

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

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

Instruction Data

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

  • amount:用户希望接收的 LP 代币数量。必须是 [u64]

  • max_x:用户愿意存入的最大 Token X 数量。必须是 [u64]

  • max_y:用户愿意存入的最大 Token Y 数量。必须是 [u64]

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

我们将以与初始化相同的方式处理 DepositInstructionData 的实现。所以我将实现留给你:

rust
pub struct DepositInstructionData {
    pub amount: u64,
    pub max_x: u64,
    pub max_y: u64,
    pub expiration: i64,
}

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

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

确保任何数量,例如 amountmax_ymax_x 都大于零,并且订单尚未过期,可以使用 Clock sysvar 进行检查。

Instruction Logic

我们首先反序列化 instruction_dataaccounts

然后我们需要:

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

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

  • 检查 vault_xvault_y 的派生是否为关联代币账户(Associated Token Accounts),如下所示:

rust
// Check if the vault_x is valid
let (vault_x, _) = find_program_address(
    &[
        self.accounts.config.key(),
        self.accounts.token_program.key(),
        config.mint_x(),
    ],
    &pinocchio_associated_token_account::ID,
);

if vault_x.ne(self.accounts.vault_x.key()) {
    return Err(ProgramError::InvalidAccountData);
}
  • 反序列化所有涉及的代币账户,并使用其中的数据通过 constant-product-curve crate 计算存款金额,并检查滑点,如下所示:

rust
// Deserialize the token accounts
let mint_lp = unsafe { Mint::from_account_info_unchecked(self.accounts.mint_lp)? };
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)? };

// Grab the amounts to deposit
let (x, y) = match mint_lp.supply() == 0 && vault_x.amount() == 0 && vault_y.amount() == 0 {
    true => (self.instruction_data.max_x, self.instruction_data.max_y),
    false => {
        let amounts = ConstantProduct::xy_deposit_amounts_from_l(
            vault_x.amount(),
            vault_y.amount(),
            mint_lp.supply(),
            self.instruction_data.amount,
            6,
        )
        .map_err(|_| ProgramError::InvalidArgument)?;

        (amounts.x, amounts.y)
    }
};

// Check for slippage
if !(x <= self.instruction_data.max_x && y <= self.instruction_data.max_y) {
    return Err(ProgramError::InvalidArgument);
}
Expand
[11 more lines]

如果是首次存款,我们可以跳过 LP 代币和存款的计算,直接采用用户建议的数值

  • 将用户的代币账户中的金额转移到金库,并向用户的代币账户铸造相应数量的 LP 代币。

借助 p-token 升级,我们可以把代币转账和铸造合并到单一 CPI 中,而不是执行三个独立的 CPI 调用。

rust
use pinocchio_token::instructions::{Batch, BatchState, MintTo, Transfer};

/// Batch 指令所需的账户数量。
const MAX_ACCOUNTS_LEN: usize =
    Transfer::MAX_ACCOUNTS_LEN * 2
    + MintTo::MAX_ACCOUNTS_LEN;

/// Batch 指令所需的指令数据长度。
const MAX_DATA_LEN: usize = Batch::header_data_len(3)
    + Transfer::DATA_LEN * 2
    + MintTo::DATA_LEN;


// ...

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

Transfer::new(
    self.accounts.user_x_ata,
    self.accounts.vault_x,
    self.accounts.user,
    x,
)
.into_batch(&mut batch)?;

Transfer::new(
    self.accounts.user_y_ata,
    self.accounts.vault_y,
    self.accounts.user,
    y,
)
.into_batch(&mut batch)?;

MintTo::new(
    self.accounts.mint_lp,
    self.accounts.user_lp_ata,
    self.accounts.config,
    self.instruction_data.amount,
)
.into_batch(&mut batch)?;

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

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

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

rust
pub struct Deposit<'a> {
    pub accounts: DepositAccounts<'a>,
    pub instruction_data: DepositInstructionData,
}

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

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

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

impl<'a> Deposit<'a> {
    pub const DISCRIMINATOR: &'a u8 = &1;

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

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