交換
swap 指令執行兩個主要任務:
計算當我們向 amm 發送一定數量的
mint_y時將收到多少mint_x(反之亦然),並包含手續費。將
from代幣轉移到 vault,並將to代幣轉移到用戶的代幣帳戶。
所需帳戶
此情境需要以下帳戶:
user:在 AMM 中進行交換的用戶。必須是signer。user_x_ata:用戶的 token X 關聯代幣帳戶。必須作為mutable傳入。user_y_ata:用戶的 token Y 關聯代幣帳戶。必須作為mutable傳入。vault_x:池子持有 token X 的代幣帳戶。必須作為mutable傳入。vault_y:池子持有 token Y 的代幣帳戶。必須作為mutable傳入。config:AMM 配置帳戶。儲存池子的狀態與參數。token_program:代幣程式帳戶。它用於執行轉帳和鑄造等代幣操作。必須是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,還是反過來;需要它來正確對齊帳戶。必須是[u8]amount:用戶願意送出的 token 數量,以換取該交易對中的另一種 token。必須是[u64]min:用戶願意為amount接受的最小 token 數量。必須是[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的派生是否為關聯代幣帳戶。反序列化所有涉及的代幣帳戶,並使用其中的數據通過
constant-product-curvecrate 計算交換數量,並像這樣檢查滑點:
// 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);
}將代幣轉帳批次合併到單一 CPI 中。你已經在
deposit裡看過 batch 的模式,所以這裡我們對兩條Transfer指令做同樣的事情,只呼叫一次代幣程式。由於從 vault 轉出的那筆轉帳仍然需要configPDA 簽名,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(())
}
}