Take
Instruction take hoàn thiện việc hoán đổi:
Chuyển Token A từ vault đến taker.
Đóng tài khoản token vault hiện đã trống.
Chuyển số lượng Token B đã thỏa thuận từ taker đến maker.
Đóng tài khoản chương trình escrow.
Các tài khoản cần thiết
Dưới đây là các tài khoản mà context cần:
taker: người muốn nhận giao dịch. Phải là signer và mutable.
maker: người tạo escrow. Phải là mutable.
escrow: tài khoản escrow mà chúng ta đang khởi tạo. Phải là mutable.
mint_a: token mà chúng ta đang gửi vào escrow
mint_b: token mà chúng ta muốn nhận
vault: tài khoản token liên kết thuộc sở hữu của escrow. Phải là mutable
taker_ata_a: tài khoản token liên kết thuộc sở hữu của taker cho mint_a. Phải là mutable
taker_ata_b: tài khoản token liên kết thuộc sở hữu của taker cho mint_b. Phải là mutable
maker_ata_b: tài khoản token liên kết thuộc sở hữu của maker cho mint_b. Phải là mutable
system_program: chương trình hệ thống. Phải là executable
token_program: chương trình token. Phải là executable
Và chúng ta thực hiện các kiểm tra này trên nó:
pub struct TakeAccounts<'a> {
pub taker: &'a AccountInfo,
pub maker: &'a AccountInfo,
pub escrow: &'a AccountInfo,
pub mint_a: &'a AccountInfo,
pub mint_b: &'a AccountInfo,
pub vault: &'a AccountInfo,
pub taker_ata_a: &'a AccountInfo,
pub taker_ata_b: &'a AccountInfo,
pub maker_ata_b: &'a AccountInfo,
pub system_program: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for TakeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [taker, maker, escrow, mint_a, mint_b, vault, taker_ata_a, taker_ata_b, maker_ata_b, system_program, token_program, _] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Basic Accounts Checks
SignerAccount::check(taker)?;
ProgramAccount::check(escrow)?;
MintInterface::check(mint_a)?;
MintInterface::check(mint_b)?;
AssociatedTokenAccount::check(taker_ata_b, taker, mint_b, token_program)?;
AssociatedTokenAccount::check(vault, escrow, mint_a, token_program)?;
// Return the accounts
Ok(Self {
taker,
maker,
escrow,
mint_a,
mint_b,
taker_ata_a,
taker_ata_b,
maker_ata_b,
vault,
system_program,
token_program,
})
}
}Dữ liệu cho Instruction
Tất cả dữ liệu mà chúng ta cần để thực hiện logic đã tồn tại trong tài khoản Escrow hoặc trên các tài khoản mà chúng ta đang deserialize. Vì lý do này, chúng ta không cần bất kỳ instruction_data nào cho instruction này.
Instruction Logic
Chúng ta bắt đầu bằng cách khởi tạo các tài khoản cần thiết trong việc triển khai TryFrom, sau khi chúng ta đã deserialize các tài khoản.
Đối với bước này, chúng ta đảm bảo cả tài khoản Token A của taker và tài khoản Token B của maker đều được khởi tạo bằng cách sử dụng AssociatedTokenAccount::init_if_needed vì chúng ta không chắc chắn rằng chúng đã tồn tại
pub struct Take<'a> {
pub accounts: TakeAccounts<'a>,
}
impl<'a> TryFrom<&'a [AccountInfo]> for Take<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let accounts = TakeAccounts::try_from(accounts)?;
// Initialize necessary accounts
AssociatedTokenAccount::init_if_needed(
accounts.taker_ata_a,
accounts.mint_a,
accounts.taker,
accounts.taker,
accounts.system_program,
accounts.token_program,
)?;
AssociatedTokenAccount::init_if_needed(
accounts.maker_ata_b,
accounts.mint_b,
accounts.taker,
accounts.maker,
accounts.system_program,
accounts.token_program,
)?;
Ok(Self {
accounts,
})
}
}Bây giờ chúng ta có thể tập trung vào logic chính sẽ:
Chuyển token từ
vaultđếntaker_ata_a.Đóng tài khoản token
vaulthiện đã trống và thu lại tiền thuê của nó.Chuyển token từ
taker_ata_bđếnmaker_ata_b.Đóng tài khoản chương trình
escrowsau khi việc hoán đổi hoàn tất.
Thay vì thực hiện ba lệnh gọi CPI token riêng biệt để hoàn tất việc hoán đổi, chúng ta có thể tận dụng batch instruction trong p-token để thực hiện hiệu quả tất cả các thao tác token chỉ trong một lần gọi CPI.
impl<'a> Take<'a> {
pub const DISCRIMINATOR: &'a u8 = &1;
const MAX_ACCOUNTS_LEN: usize = Transfer::MAX_ACCOUNTS_LEN * 2 + CloseAccount::MAX_ACCOUNTS_LEN;
const MAX_DATA_LEN: usize = Batch::header_data_len(3) + Transfer::DATA_LEN * 2 + CloseAccount::DATA_LEN;
pub fn process(&mut self) -> ProgramResult {
let data = self.accounts.escrow.try_borrow_data()?;
let escrow = Escrow::load(&data)?;
// Check if the escrow is valid
let escrow_key = create_program_address(&[b"escrow", self.accounts.maker.key(), &escrow.seed.to_le_bytes(), &escrow.bump], &crate::ID)?;
if &escrow_key != self.accounts.escrow.key() {
return Err(ProgramError::InvalidAccountOwner);
}
let seed_binding = escrow.seed.to_le_bytes();
let bump_binding = escrow.bump;
let escrow_seeds = [
Seed::from(b"escrow"),
Seed::from(self.accounts.maker.key().as_ref()),
Seed::from(&seed_binding),
Seed::from(&bump_binding),
];
let signer = Signer::from(&escrow_seeds);
let amount = TokenAccount::from_account_info(self.accounts.vault)?.amount();
let batch_state = BatchState::new(Self::MAX_ACCOUNTS_LEN, Self::MAX_DATA_LEN);
let mut batch = batch_state.as_batch()?;
Transfer::new(
self.accounts.vault,
self.accounts.taker_ata_a,
self.accounts.escrow,
amount,
)
.into_batch(&mut batch)?;
CloseAccount::new(
self.accounts.vault,
self.accounts.maker,
self.accounts.escrow,
)
.into_batch(&mut batch)?;
Transfer::new(
self.accounts.taker_ata_b,
self.accounts.maker_ata_b,
self.accounts.taker,
escrow.receive,
)
.into_batch(&mut batch)?;
batch.invoke_signed(&[signer])?;
// Close the Escrow program account
drop(data);
ProgramAccount::close(self.accounts.escrow, self.accounts.taker)?;
Ok(())
}
}