
O Vault
Um vault permite que os usuários armazenem seus ativos de forma segura. Um vault é um bloco fundamental na DeFi que, em sua essência, permite que os usuários armazenem seus ativos (lamports neste caso) de forma segura e que apenas esse mesmo usuário possa sacar depois.
Neste desafio, construiremos um vault simples de lamports que demonstra como trabalhar com contas básicas, Program Derived Addresses (PDAs) e Cross-Program Invocation (CPI). Se você não está familiarizado com o Anchor, deve começar lendo a Introdução ao Anchor para se familiarizar com os conceitos principais que vamos usar neste programa.
Instalação
Antes de começar, certifique-se de que Rust e Anchor estejam instalados (veja a documentação oficial se precisar de um lembrete). Então, no seu terminal execute:
anchor init blueshift_anchor_vaultNão precisamos de nenhum crate adicional para este desafio, então você pode abrir a pasta recém-gerada e está pronto para começar a programar!
Template
Vamos começar com a estrutura básica do programa. Vamos implementar tudo no lib.rs já que este é um programa direto. Aqui está o template inicial com os componentes principais que precisaremos:
declare_id!("22222222222222222222222222222222222222222222");
#[program]
pub mod blueshift_anchor_vault {
use super::*;
pub fn deposit(ctx: Context<VaultAction>, amount: u64) -> Result<()> {
// lógica de depósito
Ok(())
}
pub fn withdraw(ctx: Context<VaultAction>) -> Result<()> {
// lógica de saque
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 {
// enum de erros
}Nota: lembre-se de alterar o program ID para 22222222222222222222222222222222222222222222 já que usamos isso internamente para testar seu programa.
Contas
Como ambas as instruções usam as mesmas contas, para facilitar e melhorar a legibilidade, podemos criar apenas um contexto chamado VaultAction e usá-lo tanto para deposit quanto para withdraw.
A struct de contas VaultAction precisará ter:
signer: este é o dono do vault, e a única pessoa que pode sacar os lamports após criar o vault.vault: um PDA derivado das seguintes seeds:[b"vault", signer.key().as_ref()]que guarda os lamports do signer.system_program: a conta do system program que precisa ser incluída já que vamos usar a instrução de transferência CPI do system program
Veja como definimos a struct de contas:
#[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>,
}Vamos detalhar cada restrição de conta:
signer: A restriçãomuté necessária porque modificaremos seus lamports durante as transferências.vault:mutporque modificaremos seus lamports.seeds&bumpsdefinem como derivar um PDA válido a partir das seeds.
system_program: verifica se a conta está definida como executável e se o endereço é o do System Program.
Erros
Não precisamos de muitos erros para este programa pequeno, então vamos criar apenas 2 enums:
VaultAlreadyExists: que nos avisa se já existem alguns lamports na conta, pois isso significaria que o vault já existe.InvalidAmount: não podemos depositar um valor menor que o aluguel mínimo para uma conta básica, então verificamos se o valor é maior que isso.
Ficará algo assim:
#[error_code]
pub enum VaultError {
#[msg("Vault already exists")]
VaultAlreadyExists,
#[msg("Invalid amount")]
InvalidAmount,
}Depósito
A instrução de depósito executa os seguintes passos:
Verifica se o vault está vazio (tem zero lamports) para prevenir depósitos duplicados
Garante que o valor do depósito exceda o mínimo isento de aluguel para uma
SystemAccountTransfere lamports do signer para o vault usando uma CPI para o System Program
Vamos implementar essas verificações primeiro:
// Verificar se o vault está vazio
require_eq!(ctx.accounts.vault.lamports(), 0, VaultError::VaultAlreadyExists);
// Garantir que o valor exceda o mínimo isento de aluguel
require_gt!(amount, Rent::get()?.minimum_balance(0), VaultError::InvalidAmount);As duas macros require funcionam como cláusulas de guarda personalizadas:
require_eq!confirma que o vault está vazio (prevenindo depósitos duplicados).require_gt!verifica se o valor ultrapassa o limite isento de aluguel.
Após as verificações passarem, o helper do System Program do Anchor chama a CPI de Transfer assim:
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,
)?;Saque
A instrução de saque executa os seguintes passos:
Verifica se o vault contém lamports (não está vazio)
Usa o PDA do vault para assinar a transferência em seu próprio nome
Transfere todos os lamports do vault de volta para o signer
Primeiro, vamos verificar se o vault tem algum lamport para sacar:
// Verificar se o vault tem algum lamport
require_neq!(ctx.accounts.vault.lamports(), 0, VaultError::InvalidAmount);Então, precisamos criar as seeds do assinante PDA e realizar a transferência:
// Criar seeds do assinante PDA
let signer_key = ctx.accounts.signer.key();
let signer_seeds = &[b"vault", signer_key.as_ref(), &[ctx.bumps.vault]];
// Transferir todos os lamports do vault para o 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()
)?;A segurança deste saque é garantida por dois fatores:
O PDA do vault é derivado usando a chave pública do signer, garantindo que apenas o depositante original pode sacar
A capacidade do PDA de assinar a transferência é verificada através das seeds que fornecemos ao
CpiContext::new_with_signer
Conclusão
Agora você pode testar seu programa contra nossos testes unitários e reivindicar seus NFTs!
Comece compilando seu programa usando o seguinte comando no seu terminal:
anchor buildIsso gerou um arquivo .so diretamente na sua pasta target/deploy.
Agora clique no botão take challenge e solte o arquivo lá!