Make
Die make Anweisung erfüllt drei Aufgaben:
Initialisiert den Escrow-Datensatz und speichert alle Vertragsbedingungen.
Erstellt den Vault (ein ATA für
mint_aim Besitz desescrow).Überträgt die Token A des Makers in diesen Vault mittels eines CPI an das SPL-Token-Programm.
Required Accounts
Nachfolgend sind die Konten aufgeführt, die der Kontext benötigt:
maker: der Ersteller des Escrows. Muss ein Unterzeichner und veränderbar sein
escrow: das Escrow-Konto, das wir initialisieren. Muss veränderbar sein
mint_a: der Token, den wir im Escrow hinterlegen
mint_b: der Token, den wir erhalten möchten
maker_ata_a: das zugehörige Token-Konto im Besitz des Makers. Muss veränderbar sein
vault: das zugehörige Token-Konto im Besitz des Escrows. Muss veränderbar sein
system_program: das System-Programm. Muss ausführbar sein
token_program: das Token-Programm. Muss ausführbar sein
Hinweis: Wir werden die Typen verwenden, die in der Einführung in Pinocchio vorgestellt wurden.
Im Code sieht das so aus:
pub struct MakeAccounts<'a> {
pub maker: &'a AccountInfo,
pub escrow: &'a AccountInfo,
pub mint_a: &'a AccountInfo,
pub mint_b: &'a AccountInfo,
pub maker_ata_a: &'a AccountInfo,
pub vault: &'a AccountInfo,
pub system_program: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for MakeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [maker, escrow, mint_a, mint_b, maker_ata_a, vault, system_program, token_program, _] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Basic Accounts Checks
SignerAccount::check(maker)?;
MintInterface::check(mint_a)?;
MintInterface::check(mint_b)?;
AssociatedTokenAccount::check(maker_ata_a, maker, mint_a, token_program)?;
// Return the accounts
Ok(Self {
maker,
escrow,
mint_a,
mint_b,
maker_ata_a,
vault,
system_program,
token_program,
})
}
}Instruction Data
Hier sind die Anweisungsdaten, die wir übergeben müssen:
seed: die Zufallszahl, die bei der Seed-Ableitung verwendet wird. Muss ein u64 sein
receive: die Menge, die der Maker erhalten möchte. Muss ein u64 sein
amount: die Menge, die der Maker hinterlegen möchte. Muss ein u64 sein
Wir werden überprüfen, ob amount nicht null ist, da das für ein Escrow keinen Sinn ergeben würde.
So sieht es im Code aus:
pub struct MakeInstructionData {
pub seed: u64,
pub receive: u64,
pub amount: u64,
}
impl<'a> TryFrom<&'a [u8]> for MakeInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
if data.len() != size_of::<u64>() * 3 {
return Err(ProgramError::InvalidInstructionData);
}
let seed = u64::from_le_bytes(data[0..8].try_into().unwrap());
let receive = u64::from_le_bytes(data[8..16].try_into().unwrap());
let amount = u64::from_le_bytes(data[16..24].try_into().unwrap());
// Instruction Checks
if amount == 0 {
return Err(ProgramError::InvalidInstructionData);
}
Ok(Self {
seed,
receive,
amount,
})
}
}Instruction Logic
Wir beginnen mit der Initialisierung der erforderlichen Konten in der TryFrom Implementierung, nachdem wir sowohl die instruction_data als auch die accounts deserialisiert haben.
Für diesen Schritt erstellen wir das EscrowKonto mit dem ProgramAccount::init::<Escrow>Trait aus den Hilfsfunktionen, die in der Einführung in Pinocchio vorgestellt wurden. Ebenso initialisieren wir das Vault-Konto, da es neu erstellt werden muss:
pub struct Make<'a> {
pub accounts: MakeAccounts<'a>,
pub instruction_data: MakeInstructionData,
pub bump: u8,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Make<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = MakeAccounts::try_from(accounts)?;
let instruction_data = MakeInstructionData::try_from(data)?;
// Initialize the Accounts needed
let (_, bump) = find_program_address(&[b"escrow", accounts.maker.key(), &instruction_data.seed.to_le_bytes()], &crate::ID);
let seed_binding = instruction_data.seed.to_le_bytes();
let bump_binding = [bump];
let escrow_seeds = [
Seed::from(b"escrow"),
Seed::from(accounts.maker.key().as_ref()),
Seed::from(&seed_binding),
Seed::from(&bump_binding),
];
ProgramAccount::init::<Escrow>(
accounts.maker,
accounts.escrow,
&escrow_seeds,
Escrow::LEN
)?;
// Initialize the vault
AssociatedTokenAccount::init(
accounts.vault,
accounts.mint_a,
accounts.maker,
accounts.escrow,
accounts.system_program,
accounts.token_program,
)?;
Ok(Self {
accounts,
instruction_data,
bump,
})
}
}Wir können uns jetzt auf die eigentliche Logik konzentrieren, die einfach das Escrow-Konto befüllt und dann Token an den Vault überträgt.
impl<'a> Make<'a> {
pub const DISCRIMINATOR: &'a u8 = &0;
pub fn process(&mut self) -> ProgramResult {
// Populate the escrow account
let mut data = self.accounts.escrow.try_borrow_mut_data()?;
let escrow = Escrow::load_mut(data.as_mut())?;
escrow.set_inner(
self.instruction_data.seed,
*self.accounts.maker.key(),
*self.accounts.mint_a.key(),
*self.accounts.mint_b.key(),
self.instruction_data.receive,
[self.bump],
);
// Transfer tokens to vault
Transfer {
from: self.accounts.maker_ata_a,
to: self.accounts.vault,
authority: self.accounts.maker,
amount: self.instruction_data.amount
}.invoke()?;
Ok(())
}
}