接受
take 指令完成交换操作:
将 Token A 从保管库转移到接受者。
关闭现在已空的 vault 代币账户。
将约定数量的 Token B 从接受者转移到创建者。
关闭
escrow程序账户。
所需账户
以下是上下文所需的账户:
taker:希望接受交易的人。必须是签名者且可变。
maker:托管的创建者。必须可变。
escrow:我们正在初始化的托管账户。必须可变。
mint_a:我们存入托管的代币。
mint_b:我们希望接收的代币。
vault:由托管拥有的关联代币账户。必须可变。
taker_ata_a:由接受者拥有的 mint_a 的关联代币账户。必须可变。
taker_ata_b:由接受者拥有的 mint_b 的关联代币账户。必须可变。
maker_ata_b:由创建者拥有的 mint_b 的关联代币账户。必须可变。
system_program:系统程序。必须可执行。
token_program:代币程序。必须可执行。
我们对其执行以下检查:
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,
})
}
}指令数据
执行逻辑所需的所有数据已经存在于托管账户或我们反序列化的账户中。因此,对于此指令,我们不需要任何 instruction_data。
指令逻辑
我们通过在 TryFrom 实现中初始化所需账户开始操作,在此之前我们已经对账户进行了反序列化。
在此步骤中,我们使用AssociatedTokenAccount::init_if_needed确保接收方的 Token A 账户和提供方的 Token B 账户均已初始化,因为我们无法确定它们是否已经存在。
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,
})
}
}现在我们可以专注于以下逻辑:
将代币从
vault转移到taker_ata_a。关闭现在已为空的
vault代币账户并提取其中的租金。将代币从
taker_ata_b转移到maker_ata_b。在交换完成后关闭
escrow程序账户。
与其用三个独立的代币 CPI 调用来完成这次交换,我们可以利用 p-token 中的 batch instruction,把所有代币操作高效地合并到一次 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(())
}
}