Prêt Flash avec Anchor

L'introspection des instructions est une fonctionnalité puissante qui permet à un programme d'examiner et d'analyser d'autres instructions au sein de la même transactions. Cela inclut les instructions qui n'ont pas encore été exécutées, donnant à votre programme la possibilité de "regarder dans le futur" et de prendre des décisions basées sur ce qui se passera plus tard dans la transaction.
C'est un peu comme si vous aviez une vision à rayons X pour les transactions : votre programme peut voir l'ensemble de la transaction pour comprendre la séquence complète des opérations avant de décider de la marche à suivre.
L'application la plus pertinente de l'introspection des instructions est le prêt flash. Il s'agit d'un type de prêt unique qui intervient au sein d'une seule et même transaction.
Voici comment fonctionnent les prêts flash :
Emprunt (Borrow): Au début d'une transaction, vous pouvez instantanément emprunter un montant important de capital en utilisant l'instruction
loanUtilisation (Use): Vous pouvez utiliser ce capital emprunté pour le trading, l'arbitrage ou d'autres opérations au sein de la même transaction
Remboursement (Repay): Avant la fin de la transaction, vous devez rembourser le prêt ainsi que des frais faibles à l'aide de l'instruction
repay
L'idée clé est que les prêts flash reposent sur la nature atomique des transactions. Si une partie de la transaction échoue (y compris le remboursement), la totalité de la transaction est annulée comme si elle n'avait jamais eu lieu. Cela signifie que le risque du prêteur est nul : soit il est remboursé, soit le prêt n'a jamais eu lieu.
Dans ce challenge, vous créerez un programme simple de prêt flash qui illustrera l'introspection des instructions. Le programme examinera les données et les comptes des différentes instructions d'une même transaction afin de s'assurer que les conditions du prêt sont respectées.
Si vous êtes novice en matière d'introspection des instructions, nous vous recommandons de commencer par le Cours sur l'introspection des instructions pour comprendre les concepts fondamentaux utilisés dans ce programme.
Installation
Avant de commencer, assurez-vous que Rust et Anchor sont installés. Si vous avez besoin d'instructions d'installation, consultez la documentation officielle d'Anchor. Ensuite, exécutez dans votre terminal :
anchor init blueshift_anchor_flash_loanAjouter les dépendances nécessaires :
anchor-spl: Fournit des utilitaires pour travailler avec les jetons SPL (la norme de jetons de Solana)
cd blueshift_anchor_flash_loan
cargo add anchor-splVous êtes maintenant prêt à vous lancer dans la création de votre programme de prêts flash !
Modèle
Posons les bases de notre programme de prêt flash en mettant en place la structure de base, les comptes et la gestion des erreurs que nos instructions d'emprunt et de remboursement utiliseront.
Nous allons tout implémenter dans lib.rs puisque nous n'avons que deux instructions qui partagent la même structure de compte. Voici notre modèle de départ avec tous les composants essentiels :
use anchor_lang::prelude::*;
use anchor_spl::{
token::{Token, TokenAccount, Mint, Transfer, transfer},
associated_token::AssociatedToken
};
use anchor_lang::{
Discriminator,
solana_program::sysvar::instructions::{
ID as INSTRUCTIONS_SYSVAR_ID,
load_instruction_at_checked
}
};
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_flash_loan {
use super::*;
pub fn borrow(ctx: Context<Loan>, borrow_amount: u64) -> Result<()> {
// borrow logic...
Ok(())
}
pub fn repay(ctx: Context<Loan>) -> Result<()> {
// repay logic...
Ok(())
}
}
#[derive(Accounts)]
pub struct Loan<'info> {
// loan accounts...
}
#[error_code]
pub enum ProtocolError {
// error enum..
}Remarque : n'oubliez pas de changer l'ID du programme en 22222222222222222222222222222222222222222222 puisque nous l'utilisons pour tester votre programme.
Comptes
Puisque les instructions borrow et repay fonctionnent avec les mêmes comptes, nous pouvons créer un contexte Loan unique qui remplit les deux fonctions. Cela rend ainsi notre code plus facile à maintenir et à comprendre.
Notre structure de compte Loan a besoin de ces composants :
borrower: l'utilisateur qui demande le prêt flash.protocol: une Adresse Dérivée de Programme (PDA) qui détient la pool de liquidités du protocole.mint: le jeton emprunté.borrower_ata: le Compte de Jetons Associé de l'emprunteur qui recevra les jetons empruntés.protocol_ata: le Compte de Jetons Associé du protocole qui fournira les jetons empruntés.instructions: le compte Sysvar des Instructions (Instructions Sysvar) pour l'introspection.token_program,associated_token_program, etsystem_program: les programmes nécessaires pour notre programme.
Voici comment nous définissons la structure de compte :
#[derive(Accounts)]
pub struct Loan<'info> {
#[account(mut)]
pub borrower: Signer<'info>,
#[account(
seeds = [b"protocol".as_ref()],
bump,
)]
pub protocol: SystemAccount<'info>,
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = borrower,
associated_token::mint = mint,
associated_token::authority = borrower,
)]
pub borrower_ata: Account<'info, TokenAccount>,
#[account(
mut,
associated_token::mint = mint,
associated_token::authority = protocol,
)]
pub protocol_ata: Account<'info, TokenAccount>,
#[account(address = INSTRUCTIONS_SYSVAR_ID)]
/// CHECK: InstructionsSysvar account
instructions: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>
}Comme vous pouvez le constater, les comptes nécessaires à cette instruction et leur contrainte sont assez simples :
protocol: utiliseseeds = [b"protocol".as_ref()]pour créer une adresse déterministe qui possède toutes les liquidités du protocole. Cela garantit que seul notre programme peut contrôler ces fonds.borrower_ata: utiliseinit_if_neededcar l'emprunteur n'a peut-être pas encore de compte de jeton associé pour ce jeton. La contrainte en crée automatiquement un si nécessaire.protocol_ata: doit déjà exister et être mutable puisque nous allons transférer des jetons à partir de ce compte. La contrainteassociated_token::authority = protocolgarantit que seul le PDA protocol peut autoriser les transferts.instructions: utilise la contrainteaddresspour vérifier que nous accédons au bon compte de système qui contient les données d'instruction de la transaction.
Erreurs
Les prêts flash nécessitent une validation rigoureuse à plusieurs étapes, d'où la nécessité d'une bonne gestion des erreurs. Voici notre liste complète d'erreurs :
#[error_code]
pub enum ProtocolError {
#[msg("Invalid instruction")]
InvalidIx,
#[msg("Invalid instruction index")]
InvalidInstructionIndex,
#[msg("Invalid amount")]
InvalidAmount,
#[msg("Not enough funds")]
NotEnoughFunds,
#[msg("Program Mismatch")]
ProgramMismatch,
#[msg("Invalid program")]
InvalidProgram,
#[msg("Invalid borrower ATA")]
InvalidBorrowerAta,
#[msg("Invalid protocol ATA")]
InvalidProtocolAta,
#[msg("Missing repay instruction")]
MissingRepayIx,
#[msg("Missing borrow instruction")]
MissingBorrowIx,
#[msg("Overflow")]
Overflow,
}Ces bases étant posées, nous sommes prêts à implémenter la logique de base de nos instructions. La structure de compte garantit une bonne gestion des jetons, tandis que le système d'erreur fournit un retour d'information clair pour le débogage et la validation de la sécurité.