Swap
Instruction swap thực hiện hai nhiệm vụ chính:
Tính toán số lượng
mint_xmà chúng ta sẽ nhận được khi gửi một lượngmint_ynhất định vào amm (và ngược lại), bao gồm cả phí.Chuyển token
fromvào vault và chuyển tokentođến tài khoản token của người dùng.
Tài Khoản Cần Thiết
Các tài khoản cần cho ngữ cảnh này là:
user: người dùng đang swap vào AMM. Phải làsigner.user_x_ata: associated token account của người dùng cho token X. Phải được truyền dưới dạngmutable.user_y_ata: associated token account của người dùng cho token Y. Phải được truyền dưới dạngmutable.vault_x: token account giữ token X của pool. Phải được truyền dưới dạngmutable.vault_y: token account giữ token Y của pool. Phải được truyền dưới dạngmutable.config: tài khoản cấu hình của AMM. Lưu trạng thái và các tham số của pool.token_program: tài khoản token program. Tài khoản này cần thiết cho các thao tác token như transfer và minting. Phải làexecutable.
Ở đây, một lần nữa, tôi sẽ để phần triển khai cho bạn:
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> {
//..
}
}Dữ Liệu Instruction
Đây là dữ liệu instruction mà chúng ta cần truyền vào:
is_x: Swap này được thực hiện từ token X sang token Y hay ngược lại; cần thiết để sắp xếp đúng các account. Phải là[u8]amount: Số lượng token mà người dùng sẵn sàng gửi để đổi lấy token còn lại trong cặp. Phải là[u64]min: Số lượng token tối thiểu mà người dùng sẵn sàng nhận được khi đổiamount. Phải là[u64]expiration: Thời điểm hết hạn của lệnh này. Điều này quan trọng để đảm bảo giao dịch phải được thực hiện trong một khoảng thời gian nhất định. Phải là[i64]
Chúng ta sẽ xử lý phần triển khai SwapInstructionData giống như phần khởi tạo. Vì vậy tôi sẽ để phần triển khai cho bạn:
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> {
//..
}
}Logic Instruction
Chúng ta bắt đầu bằng cách deserialize cả instruction_data và accounts.
Sau đó chúng ta cần:
Load account
Configđể lấy toàn bộ dữ liệu bên trong. Chúng ta có thể thực hiện việc đó bằng helperConfig::load().Xác minh
AmmStatelà hợp lệ (tức là bằngAmmState::Initialized).Kiểm tra derivation của
vault_xvàvault_ylà Associated Token Accounts.Deserialize tất cả các token account liên quan và sử dụng dữ liệu bên trong để tính toán số lượng swap bằng crate
constant-product-curve, đồng thời kiểm tra slippage như sau:
// 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 các lần transfer token vào một CPI duy nhất. Bạn đã thấy pattern batch ở
deposit, nên ở đây chúng ta làm điều tương tự với hai instructionTransfervà chỉ gọi token program một lần. Vì transfer từ vault vẫn cần chữ ký của PDAconfig, batch nên kết thúc bằngbatch.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])?;Bạn nên đã đủ thành thạo để tự hoàn thành phần này, vì vậy tôi sẽ để việc triển khai lại cho bạn:
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(())
}
}