Swap
Instruksi swap melakukan dua tugas utama:
Menghitung jumlah
mint_xyang akan kita terima dengan mengirim sejumlahmint_ytertentu ke amm (dan sebaliknya), termasuk fee.Mentransfer token
fromke vault dan tokentoke akun token pengguna.
Akun yang Diperlukan
Berikut akun-akun yang diperlukan untuk konteks ini:
user: Pengguna yang melakukan swap ke dalam AMM. Harus berupasigner.user_x_ata: Associated token account milik pengguna untuk token X. Harus dikirim sebagaimutable.user_y_ata: Associated token account milik pengguna untuk token Y. Harus dikirim sebagaimutable.vault_x: Token account yang menyimpan token X milik pool. Harus dikirim sebagaimutable.vault_y: Token account yang menyimpan token Y milik pool. Harus dikirim sebagaimutable.config: Account konfigurasi AMM. Menyimpan state dan parameter pool.token_program: Account token program. Ini diperlukan untuk operasi token seperti transfer dan minting. Harusexecutable.
Di sini, sekali lagi, saya akan menyerahkan implementasinya kepada Anda:
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> {
//..
}
}Data Instruksi
Berikut adalah data instruksi yang perlu kita berikan:
is_x: Jika swap ini dilakukan dari token X ke token Y atau sebaliknya; diperlukan untuk menyusun account dengan benar. Harus berupa[u8]amount: Jumlah token yang bersedia dikirim pengguna sebagai imbalan atas token lain di dalam pair. Harus berupa[u64]min: Jumlah minimum token yang bersedia diterima pengguna sebagai pertukaran untukamount. Harus berupa[u64]expiration: Kedaluwarsa order ini. Penting untuk memastikan bahwa transaksi harus dijalankan dalam jangka waktu tertentu. Harus berupa[i64]
Kita akan menangani implementasi SwapInstructionData sama seperti inisialisasi. Jadi saya akan menyerahkan implementasinya kepada Anda:
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> {
//..
}
}Logika Instruksi
Kita mulai dengan mendeserialkan baik instruction_data maupun accounts.
Kemudian kita perlu:
Memuat akun
Configuntuk mengambil semua data di dalamnya. Kita dapat melakukannya menggunakan helperConfig::load().Memverifikasi bahwa
AmmStatevalid (jika sama denganAmmState::Initialized).Memeriksa derivasi
vault_xdanvault_ysebagai Associated Token Accounts.Mendeserialkan semua akun token yang terlibat dan menggunakan data di dalamnya untuk menghitung jumlah yang akan di-swap menggunakan crate
constant-product-curveserta memeriksa slippage seperti ini:
// 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 transfer token ke dalam satu CPI. Anda sudah melihat pattern batch di
deposit, jadi di sini kita melakukan hal yang sama dengan dua instruksiTransferdan hanya memanggil token program sekali. Karena transfer dari vault tetap memerlukan tanda tangan PDAconfig, batch harus diakhiri denganbatch.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])?;Anda seharusnya cukup mahir untuk melakukan ini sendiri, jadi saya akan menyerahkan implementasinya kepada Anda:
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(())
}
}