Rust
Pinocchio Escrow

Pinocchio Escrow

47 Graduates

Make

Die make Anweisung erfüllt drei Aufgaben:

  • Initialisiert den Escrow-Datensatz und speichert alle Vertragsbedingungen.

  • Erstellt den Vault (ein ATA für mint_a im Besitz des escrow).

  • Ü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:

rust
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:

rust
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:

rust
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.

rust
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(())
  }
}
Next PageAnnehmen
ODER DIREKT ZUR HERAUSFORDERUNG
Bereit für die Herausforderung?
Blueshift © 2025Commit: e573eab