Swap
L'instruction swap effectue deux tâches principales :
Calculer le montant de
mint_xque nous allons recevoir en envoyant une certaine quantité demint_ydans l'amm (et vice versa), y compris les frais.Transférer le jeton
fromvers le vault et le jetontovers le compte de jetons de l'utilisateur.
Comptes Nécessaires
Voici les comptes nécessaires pour ce contexte :
user: l'utilisateur qui effectue le swap dans l'AMM. Doit êtresigner.user_x_ata: le compte de jetons associé de l'utilisateur pour le jeton X. Doit être passé commemutable.user_y_ata: le compte de jetons associé de l'utilisateur pour le jeton Y. Doit être passé commemutable.vault_x: le compte de jetons qui détient le jeton X pour la pool. Doit être passé commemutable.vault_y: le compte de jetons qui détient le jeton Y pour la pool. Doit être passé commemutable.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 êtreexecutable.
Ici encore, je vous laisse le soin de l'implémenter :
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> {
//..
}
}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 deamount. 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 :
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> {
//..
}
}Logique d'Instruction
Nous commençons par désérialiser à la fois instruction_data et accounts.
Nous devons ensuite :
Charger le compte
Configpour récupérer toutes les données qu'il contient. Nous pouvons le faire à l'aide du helperConfig::load().Vérifier que
AmmStateest valide (donc s'il est égal àAmmState::Initialized).Vérifier que la dérivation de
vault_xetvault_ycorrespond 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-curveet vérifier le slippage comme ceci :
// 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);
}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 instructionsTransferet nous n'invoquons le token program qu'une seule fois. Comme le transfer depuis le vault nécessite toujours la signature de la PDAconfig, la batch doit se terminer parbatch.invoke_signed(&[signer])?;.
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])?;Vous devriez être suffisamment compétent pour faire cela vous-même, donc je vous laisse le soin de l'implémenter :
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(())
}
}