Rust
Pinocchio AMM

Pinocchio AMM

77 Graduates

Swap

Інструкція swap виконує дві основні задачі:

  • Обчислити кількість mint_x, яку ми отримаємо, надіславши певну кількість mint_y в amm (і навпаки), включно з комісією.

  • Передати token from у vault, а token to — на token account користувача.

Як зазначено в розділі інструкції initialize; ми ініціалізуємо всі Associated Token Accounts поза нашою інструкцією з метою оптимізації.

Потрібні Акаунти

Ось акаунти, які потрібні в цьому контексті:

  • user: користувач, який робить swap в AMM. Має бути signer.

  • user_x_ata: associated token account користувача для токена X. Має передаватися як mutable.

  • user_y_ata: associated token account користувача для токена Y. Має передаватися як mutable.

  • vault_x: token account, що зберігає токен X пулу. Має передаватися як mutable.

  • vault_y: token account, що зберігає токен Y пулу. Має передаватися як mutable.

  • config: акаунт конфігурації AMM. Зберігає стан пулу та параметри.

  • token_program: акаунт token program. Він потрібен для токен-операцій, таких як transfer і minting. Має бути 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 або навпаки; потрібно, щоб правильно зіставити accounts. Має бути [u8]

  • amount: кількість token, яку користувач готовий відправити в обмін на інший token у парі. Має бути [u64]

  • min: мінімальна кількість token, яку користувач готовий отримати в обмін на amount. Має бути [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> {
        //..
    }
}

Переконайтеся, що будь-які суми, як-от amount і min, більші за нуль, і що термін дії ордера ще не минув, використовуючи sysvar Clock.

Логіка Інструкції

Ми починаємо з десеріалізації як instruction_data, так і accounts.

Потім нам потрібно:

  • Завантажити акаунт Config, щоб отримати всі дані всередині нього. Ми можемо зробити це за допомогою helper Config::load().

  • Перевірити, що AmmState є дійсним (тобто дорівнює AmmState::Initialized).

  • Перевірити derivation vault_x та vault_y як Associated Token Accounts.

  • Десеріалізувати всі задіяні token accounts і використати дані всередині них для обчислення суми swap за допомогою crate constant-product-curve, а також перевірити slippage таким чином:

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]
  • Batch-іть token transfers в один CPI. Ти вже бачив batch-патерн у deposit, тож тут ми робимо те саме з двома інструкціями Transfer і викликаємо token program лише один раз. Оскільки transfer з vault, як і раніше, вимагає підпису PDA config, 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, щоб підтримувати batch-операції 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