Rust
AMM avec Pinocchio

AMM avec Pinocchio

Swap

L'instruction swap effectue deux tâches principales :

  • Calculer le montant de mint_x que nous allons recevoir en envoyant une certaine quantité de mint_y dans l'amm (et vice versa), y compris les frais.

  • Transférer le jeton from vers le vault et le jeton to vers le compte de jetons de l'utilisateur.

Comme mentionné dans la section relative à l'instruction initialize, nous allons initialiser tous les Associated Token Accounts en dehors de notre instruction à des fins d'optimisation.

Comptes Nécessaires

Voici les comptes nécessaires pour ce contexte :

  • user : l'utilisateur qui effectue le swap dans l'AMM. Doit être signer.

  • user_x_ata : le compte de jetons associé de l'utilisateur pour le jeton X. Doit être passé comme mutable.

  • user_y_ata : le compte de jetons associé de l'utilisateur pour le jeton Y. Doit être passé comme mutable.

  • vault_x : le compte de jetons qui détient le jeton X pour la pool. Doit être passé comme mutable.

  • vault_y : le compte de jetons qui détient le jeton Y pour la pool. Doit être passé comme mutable.

  • config : le compte de configuration de l'AMM. Il stocke l'état et les paramètres de la pool.

  • token_program : le compte du token program. Il est nécessaire pour effectuer des opérations sur les jetons comme les transfers et le mint. Doit être executable.

Ici encore, je vous laisse le soin de l'implémenter :

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]

Données d'Instruction

Voici les données d'instruction que nous devons transmettre :

  • is_x : indique si ce swap est effectué du token X vers le token Y ou inversement ; nécessaire pour aligner correctement les comptes. Doit être un [u8]

  • amount : le montant de jetons que l'utilisateur est prêt à envoyer en échange de l'autre jeton de la paire. Doit être un [u64]

  • min : le montant minimum de jetons que l'utilisateur est prêt à recevoir en échange de amount. Doit être un [u64]

  • expiration : l'expiration de cet ordre. Il est important de s'assurer que la transaction doit être effectuée dans un certain délai. Doit être un [i64]

Nous allons gérer l'implémentation de SwapInstructionData de la même manière que l'initialisation. Je vous laisse donc le soin de l'implémenter :

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

Assurez-vous que tous les montants, comme amount et min, sont supérieurs à zéro et que l'ordre n'a pas encore expiré à l'aide de la sysvar Clock.

Logique d'Instruction

Nous commençons par désérialiser à la fois instruction_data et accounts.

Nous devons ensuite :

  • Charger le compte Config pour récupérer toutes les données qu'il contient. Nous pouvons le faire à l'aide du helper Config::load().

  • Vérifier que AmmState est valide (donc s'il est égal à AmmState::Initialized).

  • Vérifier que la dérivation de vault_x et vault_y correspond bien à des Comptes de Jetons Associés.

  • Désérialiser tous les comptes de jetons impliqués et utiliser les données qu'ils contiennent pour calculer le montant à swapper à l'aide du crate constant-product-curve et vérifier le slippage comme ceci :

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]
  • Regroupez les transfers de jetons dans un seul CPI. Vous avez déjà vu le pattern batch dans deposit, donc ici nous faisons la même chose avec les deux instructions Transfer et nous n'invoquons le token program qu'une seule fois. Comme le transfer depuis le vault nécessite toujours la signature de la PDA config, la batch doit se terminer par 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]

Assurez-vous que pinocchio-token est au moins en version 0.6.0 pour prendre en charge les opérations batch de p-token.

Vous devriez être suffisamment compétent pour faire cela vous-même, donc je vous laisse le soin de l'implémenter :

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