Lição: 062: Máquinas de Estado em Programas Solana
Máquinas de estado são um conceito fundamental na programação que ajudam a gerenciar a transição de estados em um programa. No contexto dos programas Solana, usar máquinas de estado pode gerenciar efetivamente transições de estado e lógicas complexas. Esta aula irá guiá-lo na criação de um programa básico em Solana com uma implementação de máquina de estado.
Compreendendo Máquinas de Estado
Uma máquina de estado consiste em estados, transições entre esses estados e ações que podem ser executadas com base no estado atual. Em um programa Solana, o design da máquina de estado pode ajudar em cenários como o tratamento de interações do usuário, gerenciamento de tokens ou controle do fluxo de um processo em várias etapas.
Implementando uma Máquina de Estado em Solana
Vamos criar um exemplo simples de um sistema de votação que permite que os usuários votem em propostas. O sistema de votação incluirá três estados:
- Inicializado: A votação foi configurada, mas não iniciada.
- Votação: A votação está aberta e os usuários podem registrar seus votos.
- Concluído: A votação foi encerrada e os resultados foram contabilizados.
Configurando Seu Programa Solana
Primeiro, certifique-se de que você tenha a ferramenta Solana configurada. Crie um novo programa Solana usando o anchor
:
anchor init programa_votacao
cd programa_votacao
Definindo o Estado do Programa
No arquivo lib.rs
do seu programa, vamos definir os estados e a estrutura para nosso processo de votação.
use anchor_lang::prelude::*;
declare_id!("SeuIdDoProgramaAqui");
#[program]
pub mod programa_votacao {
use super::*;
pub fn inicializar(ctx: Context<Inicializar>, nome_proposta: String) -> Result<()> {
let conta_votacao = &mut ctx.accounts.conta_votacao;
conta_votacao.estado = EstadoVotacao::Inicializado;
conta_votacao.nome_proposta = nome_proposta;
Ok(())
}
pub fn iniciar_votacao(ctx: ContextIniciarVotacao) -> Result<()> {
let conta_votacao = &mut ctx.accounts.conta_votacao;
require!(conta_votacao.estado == EstadoVotacao::Inicializado, ErroPersonalizado::EstadoInvalido);
conta_votacao.estado = EstadoVotacao::Votacao;
Ok(())
}
pub fn votar(ctx: Context<Votar>, escolha: bool) -> Result<()> {
let conta_votacao = &mut ctx.accounts.conta_votacao;
require!(conta_votacao.estado == EstadoVotacao::Votacao, ErroPersonalizado::EstadoInvalido);
// Lógica para contar os votos vai aqui
// ...
Ok(())
}
pub fn encerrar_votacao(ctx: Context<EncerrarVotacao>) -> Result<()> {
let conta_votacao = &mut ctx.accounts.conta_votacao;
require!(conta_votacao.estado == EstadoVotacao::Votacao, ErroPersonalizado::EstadoInvalido);
conta_votacao.estado = EstadoVotacao::Concluido;
Ok(())
}
}
#[account]
pub struct ContaVotacao {
pub estado: EstadoVotacao,
pub nome_proposta: String,
}
#[derive(PartialEq, Clone, AnchorSerialize, AnchorDeserialize)]
pub enum EstadoVotacao {
Inicializado,
Votacao,
Concluido,
}
#[error]
pub enum ErroPersonalizado {
EstadoInvalido,
}
Contextos para Cada Instrução
Agora, vamos definir os contextos para cada uma das instruções do nosso programa. Isso garante que as contas necessárias para cada operação sejam fornecidas e sejam válidas.
#[derive(Accounts)]
pub struct Inicializar<'info> {
#[account(init, payer = usuario, space = 8 + std::mem::size_of::<ContaVotacao>())]
pub conta_votacao: Account<'info, ContaVotacao>,
#[account(mut)]
pub usuario: Signer<'info>,
pub sistema_programa: Program<'info, System>,
}
#[derive(Accounts)]
pub struct IniciarVotacao<'info> {
#[account(mut)]
pub conta_votacao: Account<'info, ContaVotacao>,
}
#[derive(Accounts)]
pub struct Votar<'info> {
#[account(mut)]
pub conta_votacao: Account<'info, ContaVotacao>,
pub usuario: Signer<'info>,
}
#[derive(Accounts)]
pub struct EncerrarVotacao<'info> {
#[account(mut)]
pub conta_votacao: Account<'info, ContaVotacao>,
}
Construindo e Testando
Uma vez que tudo esteja configurado, construa seu programa:
anchor build
Para testar seu programa, crie um cenário de teste no diretório tests
, onde você pode simular os diferentes estados e transições.
#[cfg(test)]
mod testes {
use super::*;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::pubkey::Pubkey;
#[test]
fn teste_maquina_estado_votacao() {
let programa = Program::new();
let (conta_votacao, _) = Pubkey::find_program_address(&[b"chave_votacao"], &programa.id());
// Testar inicialização
let inicializado = programa.inicializar(conta_votacao, "Proposta A").unwrap();
assert_eq!(inicializado.estado, EstadoVotacao::Inicializado);
// Testar início da votação
let iniciado = programa.iniciar_votacao(conta_votacao).unwrap();
assert_eq!(iniciado.estado, EstadoVotacao::Votacao);
// Testar votação
programa.votar(conta_votacao, true).unwrap();
// Testar encerramento da votação
let concluido = programa.encerrar_votacao(conta_votacao).unwrap();
assert_eq!(concluido.estado, EstadoVotacao::Concluido);
}
}
Conclusão
Nesta aula, implementamos uma máquina de estado básica para um programa de votação na Solana. Ao definir estados, transições e instruções associadas, criamos uma estrutura funcional para gerenciar transições de estado em um programa Solana. À medida que você continua a desenvolver aplicações mais complexas, considerar máquinas de estado o ajudará a gerenciar o fluxo da aplicação de forma eficaz.