Обмін
Інструкція swap виконує два основних завдання:
Обчислює кількість
mint_x, яку ми отримаємо, відправивши певну кількістьmint_yв amm (і навпаки), включаючи комісію.Переказує токен
fromдо сховища та токенtoна рахунок користувача
Required Accounts
Нижче наведено рахунки, необхідні для цього контексту:
user: Користувач, який обмінює токен на ліквідність Amm. Має бутиsigner.user_x_ata: Асоційований токен-рахунок користувача для токена X. Це рахунок, який отримуватиме або надсилатиме токен X у пул. Має бути переданий якmutable.user_y_ata: Асоційований токен-рахунок користувача для токена Y. Це рахунок, який отримуватиме або надсилатиме токен Y у пул. Має бути переданий якmutable.vault_x: Токен-рахунок, який містить усі токени X, внесені в пул. Має бути переданий якmutable.vault_y: Токен-рахунок, який містить усі токени Y, внесені в пул. Має бути переданий якmutable.config: Конфігураційний рахунок для пулу AMM. Зберігає всі відповідні параметри та стан пулу.token program: Рахунок програми SPL Token. Це необхідно для виконання операцій з токенами, таких як перекази та емісія. Має бутиexecutable.
Тут, знову ж таки, я залишу реалізацію вам:
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> {
//..
}
}Дані інструкції
Ось дані інструкції, які нам потрібно передати:
is_x: Чи виконується цей обмін з токена X на токен Y чи навпаки; потрібно для правильного вирівнювання рахунків. Має бути[u8]amount: Кількість токенів, яку користувач готовий надіслати в обмін на інший токен у парі. Має бути[u64]min: Мінімальна кількість токенів, яку користувач готовий отримати в обмін наamount. Має бути[u64]expiration: Термін дії цього ордера. Важливо переконатися, що транзакція має бути виконана за певний проміжок часу. Має бути[i64]
Ми будемо обробляти реалізацію для SwapInstructionData так само, як і ініціалізацію. Тому я залишу реалізацію вам:
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_data, так і accounts.
Потім нам потрібно:
Завантажити рахунок
Config, щоб отримати всі дані всередині нього. Ми можемо зробити це за допомогою хелпераConfig::load().Перевірити, що
AmmStateє дійсним (тобто дорівнюєAmmState::Initialized).Перевірити деривацію
vault_xтаvault_yяк Associated Token Accounts.Десеріалізувати всі задіяні токен-рахунки та використати дані всередині них для обчислення суми для обміну, використовуючи крейт
constant-product-curveта перевіряючи проковзування таким чином:
// 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);
}Створіть логіку переказу, перевіряючи значення
is_xта переказуючи сумиfromдо сховищ і сумиtoна токен-рахунки користувача, як показано нижче:
if self.instruction_data.is_x {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
} else {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
}Ви повинні бути достатньо кваліфікованими, щоб зробити це самостійно, тому я залишу реалізацію вам:
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(())
}
}