
Le Vault
Un vault permet aux utilisateurs de stocker leurs actifs en toute sécurité. Un vault est un composant fondamental de la DeFi qui permet aux utilisateurs de stocker en toute sécurité leurs actifs (dans notre cas, des lamports) que seul cet utilisateur peut retirer plus tard.
Dans ce challenge, nous allons créer un vault à lamport simple qui montre comment travailler avec les comptes de base, les Adresses Dérivées de Programmes (PDAs) et les Invocations de Programme Croisé (CPI). Si vous n'êtes pas familier avec Anchor, vous devriez commencer par lire l'Introduction à Anchor pour vous familiariser avec les concepts de base que nous allons utiliser dans ce programme.
Installation
Avant de commencer, assurez-vous que Rust et Anchor sont installés (voir la documentation officielle si vous avez besoin d'un rappel). Ensuite, exécutez dans votre terminal :
anchor init blueshift_anchor_vaultNous n'avons pas besoin de crate supplémentaire pour ce challenge donc vous pouvez maintenant ouvrir le dossier précédemment créé et commencer à coder !
Modèle
Commençons par la structure de base du programme. Nous allons tout implémenter dans lib.rs puisque c'est un programme simple. Voici le modèle de départ avec les composants essentiels dont nous aurons besoin :
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_vault {
use super::*;
pub fn deposit(ctx: Context<VaultAction>, amount: u64) -> Result<()> {
// deposit logic
Ok(())
}
pub fn withdraw(ctx: Context<VaultAction>) -> Result<()> {
// withdraw logic
Ok(())
}
}
#[derive(Accounts)]
pub struct VaultAction<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"vault", signer.key().as_ref()],
bump,
)]
pub vault: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}
#[error_code]
pub enum VaultError {
// 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 deux instructions utilisent les mêmes comptes, pour rendre les choses plus faciles et plus lisibles, nous pouvons nous contenter de créer un seul contexte appelé VaultAction et de l'utiliser pour deposit et withdraw.
La structure de compte VaultAction devra avoir :
signer: il s'agit du propriétaire du vault et de la seule personne qui peut retirer les lamports après avoir créé le vault.vault: un PDA dérivé des seeds suivantes :[b"vault", signer.key().as_ref()]qui contient les lamports du signataire.system_program: le compte du programme système qui doit être inclus puisque nous allons faire un CPI à ce programme pour réaliser le transfert
Voici comment nous définissons la structure du compte :
#[derive(Accounts)]
pub struct VaultAction<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"vault", signer.key().as_ref()],
bump,
)]
pub vault: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}Détaillons les contraintes de chaque compte :
signer: La contraintemutest nécessaire puisque nous allons modifier ses lamports pendant les transferts.vault:mutcar nous allons modifier ses lamports.seeds&bumpsdéfinit comment dériver un PDA valide à partir des seeds.
system_program: vérifie que le compte est exécutable et que l'adresse est celle du programme système.
Erreurs
Nous n'avons pas besoin de beaucoup d'erreurs pour ce petit programme, nous allons donc créer 2 enums :
VaultAlreadyExists: cela nous permet de savoir s'il y a déjà des lamports dans le compte. Cela signifierait dans ce cas que le vault existe déjà.InvalidAmount: nous ne pouvons pas déposer un montant inférieur à la rente minimale d'un compte de base, nous vérifions donc que le montant est supérieur à cette rente.
Cela ressemblera à quelque chose comme ça :
#[error_code]
pub enum VaultError {
#[msg("Vault already exists")]
VaultAlreadyExists,
#[msg("Invalid amount")]
InvalidAmount,
}Dépôt
L'instruction de dépôt effectue les étapes suivantes :
Vérifie que le vault est vide (qu'il n'y a pas de lamports) pour éviter les doubles dépôts
S'assure que le montant du dépôt dépasse la rente minimale pour un
SystemAccountTransfère les lamports du signataire vers le vault à l'aide d'un CPI au Programme Système
Commençons par implémenter ces vérifications :
// Check if vault is empty
require_eq!(ctx.accounts.vault.lamports(), 0, VaultError::VaultAlreadyExists);
// Ensure amount exceeds rent-exempt minimum
require_gt!(amount, Rent::get()?.minimum_balance(0), VaultError::InvalidAmount);Les deux macros require agissent comme des gardes personnalisés :
require_eq!confirme que le vault est vide (ce qui évite les doubles dépôts).require_gt!vérifie que le montant dépasse la rente minimale pour être exempt de rente.
Une fois ces vérifications effectuées, la fonction d'aide Anchor du Programme Système appelle le CPI Transfer de la manière suivante :
use anchor_lang::system_program::{transfer, Transfer};
transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.signer.to_account_info(),
to: ctx.accounts.vault.to_account_info(),
},
),
amount,
)?;Retrait
L'instruction de retrait effectue les étapes suivantes :
Vérifie que le vault contient des lamports (c'est à dire qu'il n'est pas vide)
Utilise le PDA du vault pour signer le transfert en son nom
Transfère tous les lamports du vault au signataire
Tout d'abord, vérifions si le vault contient des lamports à retirer :
// Check if vault has any lamports
require_neq!(ctx.accounts.vault.lamports(), 0, VaultError::InvalidAmount);Ensuite, nous devons créer les seeds du signataire du PDA et effectuer le transfert :
// Create PDA signer seeds
let signer_key = ctx.accounts.signer.key();
let signer_seeds = &[b"vault", signer_key.as_ref(), &[ctx.bumps.vault]];
// Transfer all lamports from vault to signer
transfer(
CpiContext::new_with_signer(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.vault.to_account_info(),
to: ctx.accounts.signer.to_account_info(),
},
&[&signer_seeds[..]]
),
ctx.accounts.vault.lamports()
)?;La sécurité de ce retrait est garantie par deux moyens :
Le PDA du vault est dérivé à l'aide de la clé publique du signataire, ce qui garantit que seul le déposant initial peut effectuer un retrait
La capacité du PDA à signer le transfert est vérifiée par les seeds que nous fournissons à
CpiContext::new_with_signer
Conclusion
Vous pouvez maintenant tester votre programme à l'aide de nos tests unitaires et réclamer votre NFT !
Commencez par compiler votre programme en utilisant la commande suivante dans votre terminal :
anchor buildCela générera un fichier .so directement dans votre dossier target/deploy.
Cliquez ensuite sur le bouton Relever le challenge et déposez le fichier !