Swap
Інструкція swap виконує дві основні задачі:
Обчислити кількість
mint_x, яку ми отримаємо, надіславши певну кількістьmint_yв amm (і навпаки), включно з комісією.Передати token
fromу vault, а tokento— на token account користувача.
Потрібні Акаунти
Ось акаунти, які потрібні в цьому контексті:
user: користувач, який робить swap в AMM. Має бутиsigner.user_x_ata: associated token account користувача для токена X. Має передаватися якmutable.user_y_ata: associated token account користувача для токена Y. Має передаватися якmutable.vault_x: token account, що зберігає токен X пулу. Має передаватися якmutable.vault_y: token account, що зберігає токен Y пулу. Має передаватися якmutable.config: акаунт конфігурації AMM. Зберігає стан пулу та параметри.token_program: акаунт token program. Він потрібен для токен-операцій, таких як transfer і minting. Має бути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: чи виконується цей swap з token X на token Y або навпаки; потрібно, щоб правильно зіставити accounts. Має бути[u8]amount: кількість token, яку користувач готовий відправити в обмін на інший token у парі. Має бути[u64]min: мінімальна кількість token, яку користувач готовий отримати в обмін на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, щоб отримати всі дані всередині нього. Ми можемо зробити це за допомогою helperConfig::load().Перевірити, що
AmmStateє дійсним (тобто дорівнюєAmmState::Initialized).Перевірити derivation
vault_xтаvault_yяк Associated Token Accounts.Десеріалізувати всі задіяні token accounts і використати дані всередині них для обчислення суми swap за допомогою crate
constant-product-curve, а також перевірити slippage таким чином:
// 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-іть token transfers в один CPI. Ти вже бачив batch-патерн у
deposit, тож тут ми робимо те саме з двома інструкціямиTransferі викликаємо token program лише один раз. Оскільки transfer з vault, як і раніше, вимагає підпису PDAconfig, batch має завершуватися викликомbatch.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])?;Ви повинні бути достатньо кваліфікованими, щоб зробити це самостійно, тому я залишаю реалізацію вам:
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(())
}
}