Rust
Pinocchio AMM

Pinocchio AMM

77 Graduates

交換

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 的派生是否為關聯代幣帳戶。

  • 反序列化所有涉及的代幣帳戶,並使用其中的數據通過 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