Swap
Die Instruction swap erfüllt zwei Hauptaufgaben:
Berechnen, wie viel
mint_xwir erhalten, indem wir eine bestimmte Mengemint_yin den Amm senden (und umgekehrt), inklusive der Gebühr.Das
fromToken in den Vault und dastoToken auf das Token-Konto des Nutzers übertragen.
Benötigte Accounts
Folgende Accounts werden in diesem Kontext benötigt:
user: Der Nutzer, der im AMM swapt. Muss einsignersein.user_x_ata: Das Associated Token Account des Nutzers für Token X. Muss alsmutableübergeben werden.user_y_ata: Das Associated Token Account des Nutzers für Token Y. Muss alsmutableübergeben werden.vault_x: Das Token Account, das Token X für den Pool hält. Muss alsmutableübergeben werden.vault_y: Das Token Account, das Token Y für den Pool hält. Muss alsmutableü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. Mussexecutablesein.
Hier überlasse ich dir wieder die Implementierung:
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> {
//..
}
}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üramounterhalten 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:
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> {
//..
}
}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 denConfig::load()-Helfer verwenden.Überprüfen, ob der
AmmStategültig ist (also ob er gleichAmmState::Initializedist).Die Ableitung von
vault_xundvault_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:
// 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);
}Batch die Token-Transfers in einen einzigen CPI. Du hast das Batch-Pattern bereits in
depositgesehen, also machen wir hier dasselbe mit den beidenTransfer-Instruktionen und rufen das Token-Programm nur einmal auf. Da der Transfer aus dem Vault weiterhin die Signatur derconfigPDA benötigt, sollte der Batch mitbatch.invoke_signed(&[signer])?;enden.
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])?;Du solltest kompetent genug sein, um dies selbstständig zu tun, daher überlasse ich dir die Implementierung:
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(())
}
}