Rust
Pinocchio AMM

Pinocchio AMM

77 Graduates

Swap

Die Instruction swap erfüllt zwei Hauptaufgaben:

  • Berechnen, wie viel mint_x wir erhalten, indem wir eine bestimmte Menge mint_y in den Amm senden (und umgekehrt), inklusive der Gebühr.

  • Das from Token in den Vault und das to Token auf das Token-Konto des Nutzers übertragen.

Wie im Abschnitt zur initialize Instruction erwähnt, werden wir alle Associated Token Accounts außerhalb unserer Instruction aus Optimierungsgründen initialisieren.

Benötigte Accounts

Folgende Accounts werden in diesem Kontext benötigt:

  • user: Der Nutzer, der im AMM swapt. Muss ein signer sein.

  • user_x_ata: Das Associated Token Account des Nutzers für Token X. Muss als mutable übergeben werden.

  • user_y_ata: Das Associated Token Account des Nutzers für Token Y. Muss als mutable übergeben werden.

  • vault_x: Das Token Account, das Token X für den Pool hält. Muss als mutable übergeben werden.

  • vault_y: Das Token Account, das Token Y für den Pool hält. Muss als mutable übergeben werden.

  • config: Das Konfigurationskonto des AMM. Es speichert Pool-Zustand und Parameter.

  • token_program: Das Token-Programm-Konto. Es wird für Token-Operationen wie Transfers und Minting benötigt. Muss executable sein.

Hier überlasse ich dir wieder die Implementierung:

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]

Instruction-Daten

Hier sind die Instruction-Daten, die wir übergeben müssen:

  • is_x: Ob dieser Swap von Token X nach Token Y oder umgekehrt ausgeführt wird; erforderlich, um die Accounts korrekt auszurichten. Muss ein [u8] sein.

  • amount: Die Menge an Token, die der Nutzer im Austausch gegen das andere Token im Paar senden möchte. Muss ein [u64] sein.

  • min: Die Mindestmenge an Token, die der Nutzer im Austausch für amount erhalten möchte. Muss ein [u64] sein.

  • expiration: Das Ablaufdatum dieser Order. Wichtig, um sicherzustellen, dass die Transaktion innerhalb einer bestimmten Zeit ausgeführt werden muss. Muss ein [i64] sein.

Wir werden die Implementierung für SwapInstructionData genauso wie bei der Initialisierung handhaben. Also überlasse ich dir die Implementierung:

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> {
        //..
    }
}

Stelle sicher, dass Beträge wie amount und min größer als null sind und dass die Order mithilfe der Clock-Sysvar noch nicht abgelaufen ist.

Instruction-Logik

Wir beginnen mit der Deserialisierung sowohl von instruction_data als auch von accounts.

Dann müssen wir:

  • Das Config-Konto laden, um alle darin enthaltenen Daten zu erfassen. Wir können dafür den Config::load()-Helfer verwenden.

  • Überprüfen, ob der AmmState gültig ist (also ob er gleich AmmState::Initialized ist).

  • Die Ableitung von vault_x und vault_y überprüfen, um sicherzustellen, dass es sich um Associated Token Accounts handelt.

  • Alle beteiligten Token-Konten deserialisieren und die darin enthaltenen Daten verwenden, um die zu swappende Menge mit dem constant-product-curve-Crate zu berechnen und die Slippage wie folgt zu prüfen:

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 die Token-Transfers in einen einzigen CPI. Du hast das Batch-Pattern bereits in deposit gesehen, also machen wir hier dasselbe mit den beiden Transfer-Instruktionen und rufen das Token-Programm nur einmal auf. Da der Transfer aus dem Vault weiterhin die Signatur der config PDA benötigt, sollte der Batch mit batch.invoke_signed(&[signer])?; enden.

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]

Stelle sicher, dass pinocchio-token mindestens Version 0.6.0 hat, um p-token Batch-Operationen zu unterstützen.

Du solltest kompetent genug sein, um dies selbstständig zu tun, daher überlasse ich dir die Implementierung:

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