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 bằng cách gửi một số lượng nhất địnhmint_yvào AMM (và ngược lại) bao gồm phí.Chuyển token
fromđến vault và tokentođến tài khoản token của người dùng
Các tài khoản cần thiết
Dưới đây là các tài khoản cần thiết cho context này:
user: Người dùng đang hoán đổi token trong thanh khoản của AMM. Phải làsigner.vault_x: Tài khoản token chứa tất cả token X được gửi vào pool. Phải được truyền vào nhưmutable.vault_y: Tài khoản token chứa tất cả token Y được gửi vào pool. Phải được truyền vào nhưmutable.user_x_ata: Tài khoản token liên kết của người dùng cho token X. Đây là tài khoản sẽ nhận hoặc gửi token X vào pool. Phải được truyền vào nhưmutable.user_y_ata: Tài khoản token liên kết của người dùng cho token Y. Đây là tài khoản sẽ nhận hoặc gửi token Y vào pool. Phải được truyền vào nhưmutable.config: Tài khoản cấu hình cho AMM pool. Lưu trữ tất cả các tham số và trạng thái pool liên quan.token program: Tài khoản chương trình SPL Token. Điều này cần thiết để thực hiện các hoạt động token như chuyển khoản và minting. Phải làexecutable.
Ở đây, một lần nữa, tôi sẽ để việc triển khai cho bạn:
pub struct SwapAccounts<'a> {
pub user: &'a AccountInfo,
pub user_x: &'a AccountInfo,
pub user_y: &'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 cho Instruction
Đây là dữ liệu cho instruction mà chúng ta cần truyền vào:
is_x: Liệu swap này có được thực hiện từ token X sang token Y hay ngược lại; cần thiết để sắp xếp các tài khoản một cách chính xác. Phải là[u8]amount: Số lượng token mà người dùng sẵn sàng gửi để đổi lấy token khác trong cặp. Phải là[u64]min: Số lượng tối thiểu token mà người dùng sẵn sàng nhận để đổi lấyamount. Phải là[u64]expiration: Thời gian hết hạn của lệnh này. Quan trọng để đảm bảo rằng 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ý việc triển khai cho SwapInstructionData như khởi tạo. Vì vậy tôi sẽ để việc 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> {
//..
}
}Instruction Logic
Chúng ta bắt đầu bằng cách deserialize cả instruction_data và accounts.
Sau đó chúng ta cần:
Tải tài khoản
Configđể lấy tất cả dữ liệu bên trong nó. Chúng ta có thể làm điều này bằng cách sử dụng helperConfig::load().Xác minh rằng
AmmStatehợp lệ (vì vậy nếu nó bằngAmmState::Initialized).Kiểm tra việc tạo
vault_xvàvault_yđể trở thành Associated Token Accounts.Deserialize tất cả các tài khoản token liên quan và sử dụng dữ liệu bên trong chúng để tính toán số lượng cần swap bằng cách sử dụng crate
constant-product-curvevà kiểm tra slippage như thế này:
// 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))?;
// Kiểm tra các giá trị chính xác
if swap_result.deposit == 0 || swap_result.withdraw == 0 {
return Err(ProgramError::InvalidArgument);
}Tạo logic chuyển khoản kiểm tra giá trị
is_xvà chuyển số lượngfromđến các vault và số lượngtođến tài khoản token của người dùng như thế này:
if self.instruction_data.is_x {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
} else {
Transfer {
//...
}
.invoke()?;
Transfer {
//...
}
.invoke_signed(&signer_seeds)?;
}Bạn đủ khả năng triển khai logic này, vì vậy tôi sẽ để việc triển khai 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)?;
// Trả về struct đã khởi tạo
Ok(Self {
accounts,
instruction_data,
})
}
}
impl<'a> Swap<'a> {
pub const DISCRIMINATOR: &'a u8 = &3;
pub fn process(&mut self) -> ProgramResult {
//..
Ok(())
}
}