Ініціалізація
Інструкція initialize виконує два основних завдання:
Ініціалізує обліковий запис
Configта зберігає всю інформацію, необхідну для правильної роботи amm.Створює обліковий запис Mint
mint_lpта призначаєmint_authorityобліковому записуconfig.
Required Accounts
Нижче наведено облікові записи, необхідні для цього контексту:
initializer: Творець облікового записуconfig. Це не обов'язково має бути також орган влади над ним. Повинен бутиsignerтаmutable, оскільки цей обліковий запис оплачуватиме ініціалізацію якconfig, так іmint_lp.mint_lp: Обліковий запис Mint, який представлятиме ліквідність пулу.mint_authorityмає бути встановлений на обліковий записconfig. Повинен бути переданий якmutable.config: Обліковий запис конфігурації, що ініціалізується. Повинен бутиmutable.Програми
systemтаtoken: Програмні облікові записи, необхідні для ініціалізації вищезазначених облікових записів. Повинні бутиexecutable.
Оскільки тут не так багато змін порівняно зі звичайною структурою, яку ми створюємо, я залишу реалізацію вам:
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> {
//..
}
}Дані інструкції
Ось дані інструкції, які нам потрібно передати:
seed: Випадкове число, яке використовується для отримання PDA (Program Derived Address). Це дозволяє створювати унікальні екземпляри пулів. Має бути типу[u64]fee: Комісія за обмін, виражена в базисних пунктах (1 базисний пункт = 0,01%). Ця комісія стягується за кожну угоду і розподіляється між постачальниками ліквідності. Має бути типу[u16]mint_x: Адреса монети SPL токена X у пулі. Має бути типу[u8; 32]mint_y: Адреса монети SPL токена Y у пулі. Має бути типу[u8; 32]config_bump: Значення bump seed, яке використовується для отримання PDA облікового записуconfig. Має бути типуu8lp_bump: Значення bump seed, яке використовується для отримання PDA облікового записуlp_mint. Має бути типуu8authority: Публічний ключ, який матиме адміністративні повноваження над AMM. Якщо не вказано, пул можна встановити як незмінний. Має бути типу[u8; 32]
У цій реалізації ми обробляємо парсинг даних інструкцій більш гнучким і низькорівневим способом, ніж зазвичай. З цієї причини ми пояснимо, чому приймаємо такі рішення:
#[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),
}
}
}Поле authority в InitializeInstructionData є необов'язковим і може бути пропущене для створення незмінного пулу.
Щоб полегшити це та заощадити 32 байти даних транзакції при створенні незмінних пулів, ми перевіряємо довжину даних інструкції та аналізуємо дані відповідно; якщо дані коротші, ми встановлюємо поле authority як None, записуючи 32 нульових байти в кінець буфера; якщо воно включає повне поле authority, ми безпосередньо перетворюємо байтовий зріз на структуру.
Instruction Logic
Ми починаємо з десеріалізації як instruction_data, так і accounts.
Потім нам потрібно:
Створити обліковий запис
Configза допомогою інструкціїCreateAccountіз системної програми та наступних сідів:
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),
];Заповнити обліковий запис
Config, завантаживши його за допомогою хелпераConfig::load_mut_unchecked()і потім заповнивши його всіма необхідними даними за допомогою хелпераconfig.set_inner().Створити обліковий запис
Mintдля lp, використовуючи інструкціїCreateAccountтаInitializeMint2і наступні сіди:
let mint_lp_seeds = [
Seed::from(b"mint_lp"),
Seed::from(self.accounts.config.key()),
Seed::from(&self.instruction_data.lp_bump),
];Ви повинні бути достатньо кваліфікованими, щоб зробити це самостійно, тому я залишу реалізацію вам:
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
Як згадувалося раніше, це може здатися незвичним, але нам не потрібно виконувати явні перевірки переданих облікових записів.
Це тому, що на практиці інструкція не виконається, якщо щось не так; або під час CPI (Cross-Program Invocation), або раніше через перевірки, які ми вбудували в програму.
Наприклад, розглянемо обліковий запис initializer. Ми очікуємо, що він буде одночасно signer та mutable, але якщо це не так, інструкція CreateAccount автоматично завершиться невдачею, оскільки вона вимагає цих властивостей для payer.
Аналогічно, якщо обліковий запис config передається з недійсним mint_x або mint_y, будь-яка спроба внести депозит у протокол завершиться невдачею під час переказу токенів.
З набуттям досвіду ви виявите, що багато перевірок можна опустити, щоб зробити інструкції легшими та оптимізованими, покладаючись на систему та наступні інструкції для забезпечення обмежень.