Inisialisasi
Instruksi initialize melakukan dua tugas utama:
Menginisialisasi akun
Configdan menyimpan semua informasi yang diperlukan agar amm dapat beroperasi dengan benar.Membuat akun Mint
mint_lpdan menetapkanmint_authorityke akunconfig.
Required Accounts
Berikut adalah akun-akun yang diperlukan untuk konteks ini:
initializer: Pembuat akunconfig. Ini tidak harus menjadi otoritas atas akun tersebut juga. Harus berupasignerdanmutable, karena akun ini akan membayar untuk inisialisasi baikconfigmaupunmint_lp.mint_lp: Akun Mint yang akan mewakili likuiditas pool.mint_authorityharus ditetapkan ke akunconfig. Harus dilewatkan sebagaimutable.config: Akun konfigurasi yang sedang diinisialisasi. Harusmutable.Program
systemdantoken: Akun program yang diperlukan untuk menginisialisasi akun-akun di atas. Harusexecutable.
Karena tidak banyak perubahan dari struct biasa yang kita buat, saya akan menyerahkan implementasinya kepada Anda:
pub struct InitializeAccounts<'a> {
pub initializer: &'a AccountInfo,
pub mint_lp: &'a AccountInfo,
pub config: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for InitializeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
//..
}
}Instruction Data
Berikut adalah data instruksi yang perlu kita masukkan:
seed: Angka acak yang digunakan untuk derivasi seed PDA (Program Derived Address). Ini memungkinkan instance pool yang unik. Harus berupa[u64]fee: Biaya swap, dinyatakan dalam basis poin (1 basis poin = 0,01%). Biaya ini dikumpulkan pada setiap perdagangan dan didistribusikan kepada penyedia likuiditas. Harus berupa[u16]mint_x: Alamat SPL token mint untuk token X di pool. Harus berupa[u8; 32]mint_y: Alamat SPL token mint untuk token Y di pool. Harus berupa[u8; 32]config_bump: Bump seed yang digunakan untuk menurunkan PDA akunconfig. Harus berupau8lp_bump: Bump seed yang digunakan untuk menurunkan PDA akunlp_mint. Harus berupau8authority: Kunci publik yang akan memiliki otoritas administratif atas AMM. Jika tidak disediakan, pool dapat diatur sebagai tidak dapat diubah. Harus berupa[u8; 32]
Dalam implementasi ini, kita menangani parsing data instruksi dengan cara yang lebih fleksibel dan level rendah daripada biasanya. Karena alasan ini, kita akan menjelaskan mengapa kita membuat keputusan-keputusan berikut:
#[repr(C, packed)]
pub struct InitializeInstructionData {
pub seed: u64,
pub fee: u16,
pub mint_x: [u8; 32],
pub mint_y: [u8; 32],
pub config_bump: [u8; 1],
pub lp_bump: [u8; 1],
pub authority: [u8; 32],
}
impl TryFrom<&[u8]> for InitializeInstructionData {
type Error = ProgramError;
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
const INITIALIZE_DATA_LEN_WITH_AUTHORITY: usize = size_of::<InitializeInstructionData>();
const INITIALIZE_DATA_LEN: usize =
INITIALIZE_DATA_LEN_WITH_AUTHORITY - size_of::<[u8; 32]>();
match data.len() {
INITIALIZE_DATA_LEN_WITH_AUTHORITY => {
Ok(unsafe { (data.as_ptr() as *const Self).read_unaligned() })
}
INITIALIZE_DATA_LEN => {
// If the authority is not present, we need to build the buffer and add it at the end before transmuting to the struct
let mut raw: MaybeUninit<[u8; INITIALIZE_DATA_LEN_WITH_AUTHORITY]> = MaybeUninit::uninit();
let raw_ptr = raw.as_mut_ptr() as *mut u8;
unsafe {
// Copy the provided data
core::ptr::copy_nonoverlapping(data.as_ptr(), raw_ptr, INITIALIZE_DATA_LEN);
// Add the authority to the end of the buffer
core::ptr::write_bytes(raw_ptr.add(INITIALIZE_DATA_LEN), 0, 32);
// Now transmute to the struct
Ok((raw.as_ptr() as *const Self).read_unaligned())
}
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
}Kolom authority dalam InitializeInstructionData bersifat opsional dan dapat dihilangkan untuk membuat pool yang tidak dapat diubah.
Untuk memfasilitasi ini dan menghemat 32 byte data transaksi saat membuat pool yang tidak dapat diubah, kita memeriksa panjang data instruksi dan mengurai data sesuai dengan itu; jika datanya lebih pendek, kita mengatur kolom authority ke None dengan menulis 32 byte nol ke akhir buffer; jika data tersebut menyertakan seluruh kolom authority, kita mengkonversi slice byte langsung ke struct.
Instruction Logic
Kita mulai dengan mendeserialkan baik instruction_data maupun accounts.
Kemudian kita perlu:
Membuat akun
Configmenggunakan instruksiCreateAccountdari program sistem dan seed berikut:
let seed_binding = self.instruction_data.seed.to_le_bytes();
let config_seeds = [
Seed::from(b"config"),
Seed::from(&seed_binding),
Seed::from(&self.instruction_data.mint_x),
Seed::from(&self.instruction_data.mint_y),
Seed::from(&self.instruction_data.config_bump),
];Mengisi akun
Configdengan memuat menggunakan helperConfig::load_mut_unchecked()dan kemudian mengisinya dengan semua data yang diperlukan menggunakan helperconfig.set_inner().Membuat akun
Mintuntuk lp menggunakan instruksiCreateAccountdanInitializeMint2dan seed berikut:
let mint_lp_seeds = [
Seed::from(b"mint_lp"),
Seed::from(self.accounts.config.key()),
Seed::from(&self.instruction_data.lp_bump),
];Anda seharusnya cukup mahir untuk melakukan ini sendiri, jadi saya akan menyerahkan implementasinya kepada Anda:
pub struct Initialize<'a> {
pub accounts: InitializeAccounts<'a>,
pub instruction_data: InitializeInstructionData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Initialize<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = InitializeAccounts::try_from(accounts)?;
let instruction_data: InitializeInstructionData = InitializeInstructionData::try_from(data)?;
Ok(Self {
accounts,
instruction_data,
})
}
}
impl<'a> Initialize<'a> {
pub const DISCRIMINATOR: &'a u8 = &0;
pub fn process(&mut self) -> ProgramResult {
//..
Ok(())
}
}Security
Seperti yang disebutkan sebelumnya, mungkin tampak tidak biasa, tetapi kita tidak perlu melakukan pemeriksaan eksplisit pada akun-akun yang dimasukkan.
Ini karena, dalam praktiknya, instruksi akan gagal jika ada yang salah; baik selama CPI (Cross-Program Invocation) atau lebih awal melalui pemeriksaan yang kita masukkan ke dalam program.
Sebagai contoh, pertimbangkan akun initializer. Kita mengharapkannya menjadi signer dan mutable, tetapi jika tidak, instruksi CreateAccount akan gagal secara otomatis karena memerlukan properti tersebut untuk payer.
Demikian juga, jika akun config diberikan dengan mint_x atau mint_y yang tidak valid, setiap upaya untuk melakukan deposit ke dalam protokol akan gagal selama transfer token.
Seiring dengan bertambahnya pengalaman, Anda akan menemukan bahwa banyak pemeriksaan dapat dihilangkan untuk menjaga instruksi tetap ringan dan optimal, dengan mengandalkan sistem dan instruksi berikutnya untuk menerapkan batasan.