Pinjam
Instruksi pinjam adalah bagian pertama dari sistem flash loan kita. Instruksi ini melakukan tiga langkah penting untuk memastikan peminjaman yang aman dan atomik:
Transfer dana: Memindahkan
borrow_amountyang diminta dari treasury protokol ke akun peminjamVerifikasi pembayaran kembali: Menggunakan introspeksi instruksi untuk memastikan bahwa instruksi pembayaran kembali yang valid ada di akhir transaksi
Transfer Dana
Pertama, kita mengimplementasikan transfer dana yang sebenarnya dengan validasi yang tepat:
// Make sure we're not sending in an invalid amount that can crash our Protocol
require!(borrow_amount > 0, ProtocolError::InvalidAmount);
// Derive the Signer Seeds for the Protocol Account
let seeds = &[
b"protocol".as_ref(),
&[ctx.bumps.protocol]
];
let signer_seeds = &[&seeds[..]];
// Transfer the funds from the protocol to the borrower
transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.protocol_ata.to_account_info(),
to: ctx.accounts.borrower_ata.to_account_info(),
authority: ctx.accounts.protocol.to_account_info(),
},
signer_seeds
),
borrow_amount
)?;Kode ini memastikan kita mentransfer jumlah yang valid dan menggunakan Program Derived Address (PDA) protokol untuk mengotorisasi transfer.
Introspeksi Instruksi
Sekarang bagian yang kritis untuk keamanan: menggunakan introspeksi instruksi untuk memverifikasi struktur transaksi dan memastikan flash loan kita akan dibayar kembali.
Kita mulai dengan mengakses sysvar instructions, yang berisi informasi tentang semua instruksi dalam transaksi saat ini:
/*
Instruction Introspection
This is the primary means by which we secure our program,
enforce atomicity while making a great UX for our users.
*/
let ixs = ctx.accounts.instructions.to_account_info();Akhirnya, kita melakukan pemeriksaan yang paling penting: memastikan bahwa instruksi terakhir dalam transaksi adalah instruksi pembayaran kembali yang valid:
Kita mulai dengan memeriksa posisi instruksi pinjam untuk memastikan itu adalah satu-satunya
Kemudian kita memeriksa jumlah instruksi dan memastikan bahwa kita memuat instruksi terakhir dari transaksi
Lalu kita memverifikasi bahwa itu adalah instruksi pembayaran kembali dengan memverifikasi ID program dan diskriminator instruksi
Kita selesai dengan memverifikasi bahwa ATA yang diteruskan dalam instruksi pembayaran kembali sama dengan yang kita teruskan dalam instruksi
Borrowkita
/*
Repay Instruction Check
Make sure that the last instruction of this transaction is a repay instruction
*/
// Check if this is the first instruction in the transaction.
let current_index = load_current_index_checked(&ctx.accounts.sysvar_instructions)?;
require_eq!(current_index, 0, ProtocolError::InvalidIx);
// Check how many instruction we have in this transaction
let instruction_sysvar = ixs.try_borrow_data()?;
let len = u16::from_le_bytes(instruction_sysvar[0..2].try_into().unwrap());
// Ensure we have a repay ix
if let Ok(repay_ix) = load_instruction_at_checked(len as usize - 1, &ixs) {
// Instruction checks
require_keys_eq!(repay_ix.program_id, ID, ProtocolError::InvalidProgram);
require!(repay_ix.data[0..8].eq(instruction::Repay::DISCRIMINATOR), ProtocolError::InvalidIx);
// We could check the Wallet and Mint separately but by checking the ATA we do this automatically
require_keys_eq!(repay_ix.accounts.get(3).ok_or(ProtocolError::InvalidBorrowerAta)?.pubkey, ctx.accounts.borrower_ata.key(), ProtocolError::InvalidBorrowerAta);
require_keys_eq!(repay_ix.accounts.get(4).ok_or(ProtocolError::InvalidProtocolAta)?.pubkey, ctx.accounts.protocol_ata.key(), ProtocolError::InvalidProtocolAta);
} else {
return Err(ProtocolError::MissingRepayIx.into());
}