Controles de acesso em programas Solana

Web3 e Blockchain

Controles de acesso em programas Solana

Luiz Duarte
Escrito por Luiz Duarte em 23/04/2026
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Muitas vezes quando criamos nossos programas Solana nós precisamos incluir funções administrativas, para uso do administrador ou dono (authority) do programa. Às vezes é uma função de mint de um token, às vezes é uma função para atualizar alguma informação no programa ou ainda uma função de sacar o saldo da aplicação para outra conta. Não importa, o fato é que mesmo funções públicas muitas vezes não são para uso do público em geral, mas apenas para uma ou mais pessoas definidas pelo dono do programa.

Neste tutorial vou lhe mostrar duas formas de fazer esse tipo de controle. Uma primeira bem simples e eficaz quando o acesso só possui dois perfis (comum e administrador) e outra com maior granularidade, permitindo roles/perfis.

Primeiro vamos determinar um programa de exemplo sobre o qual vamos aplicar os dois cenários de implementação. Abaixo uma sugestão.

E os testes:

Imagine que você tem o programa acima onde a função set_message é de acesso geral, mas a função clear_message não, você quer ter mais controle sobre quem pode acessá-la.

Atenção: este não é um tutorial iniciante em Anchor/Rust, ele requer que você já conheça o básico da linguagem, o que pode ser aprendido aqui.

Curso Web3 para Iniciantes

# Opção 1: Macro Require

Você pode validar na chamada de uma função se a account que está assinando a mesma é a mesma account que foi usada na criação da mensagem. Para isso você pode usar a macro require. Primeiro, adicione um CustomError no programa e um campo authority na struct Message. Atenção: o nome tem de ser authority pois ele será referenciado em outros códigos a seguir.

Agora que temos um campo para guardar o dono da account de message, vamos inicializar esse dono na função initialize.

Ou seja, além de zerar a string, a gente pega o user que assinou o initialize e define ele como authority. Agora com esta informação armazenada, precisamos ajustar o ClearMessageContext para que ele exija o authority também.

Com o contexto atualizado, podemos usar a macro require na chamada da clear_message para fazer a validação.

A macro require (atenção à exclamação após o nome, pois é uma macro) exige dois parâmetros:

  • a condição booleana de sucesso (neste caso que a authority da mensagem seja a mesma autority que assinou o clear_message);
  • o erro que será disparado caso a condição retorne false;

Para testar essa nova versão do clear_message, vamos ajustar o unit test para que inclua o user authority, caso contrário o teste não passará.

Agora, para realmente ver que uma outra conta qualquer não conseguirá chamar a clear_message, vamos criar outro unit test.

Aqui a gente está passando otherUser como autority, o que fará com que aconteça o disparo do Custom Error Unauthorized em nosso código.

# Opção 2: Constraint has_one

A macro require é interessante principalmente em cenários de validações mais complexas do que mera comparação de endereços. Outra opção, com ainda menos código é usando a constraint has_one, que é completamente focada em ver se um endereço é autoridade/dono sobre outro endereço.

Para isso, volte no ClearMessageContext e modifique-o para que fique como abaixo.

Repare ali na macro account como adicionei uma constraint has_one, que diz que esta account message é propriedade do usuário authority, que é definido logo abaixo, no mesmo contexto. Isso simplifica o nosso clear_message novamente, que não precisa mais ter a validação com require e também não precisaremos mais ter Custom Error pois o erro enviado será nativo do has_one. Falando nisso, isso afeta o nosso unit test do cenário de erro, que deve ficar como abaixo pois a mensagem de erro mudará.

Mas e se eu precisar de mais de um papel que não somente o de autoridade/administrador?

# Opção 3: Roles

Outra opção muito popular é usando roles: definimos alguns roles/funções em nosso programa e quem possui aquelas roles, além de definir maneiras de validar a permissão de acesso a determinadas funções facilmente através de constraint macros. Vamos começar criando o enum com as roles do programa:

Atenção aos parâmetros do Attribute Macro derive, pois eles são necessários para serializari, desserializari, clonar e comparar por igualdade parcial ou completa. Sem esses parâmetros o seu enum vai falhar já na etapa de compilação se ele tiver de ser serializado, desserializado, clonado ou comparado.

Agora vamos criar a struct de um usuário do nosso programa, com a sua chave pública e role:

Para adição de um usuário na aplicação, algo permitido somente para o owner da mesma (definido na inicialização), precisamos de um context.

Nesse context recebemos a wallet account do usuário a ser adicionada como usuária do sistema (necessária para a constraint a seguir), a PDA account do usuário no formato wallet + role e usando a wallet ele na seed, o payer e o system program.

Agora o código de adição fica como abaixo:

Note como pegamos a wallet do usuário para adicionar o endereço na PDA account, bem como a role passada por parâmetro. Um teste para esta função pode ser visto abaixo. Atenção especial à troca de snake case para camel case feita pelo Anchor automaticamente e que caso você escreva errado, provoca erros silenciosos.

Agora que vimos como adicionar usuários com roles específicas, vamos falar de exclusão exclusiva para gerentes e donos do programa. Primeiro, vamos criar o contexto.

Aqui precisamos da PDA account do usuário a ser removido, já com a macro close para que ele seja encerrado nessa execução e o rent seja devolvido para authority_signer, que falaremos a seguir.

A wallet account que vai assinar a remoção (authority_signer) deve atender a alguns critérios específicos em sua PDA account tais como:

  • o PDA deve seguir a regra de ter o endereço gerado com a mesma chave pública de authority_signer;
  • o campo address do PDA deve ser igual à chave pública de authority_signer;
  • o PDA deve ter a role OWNER ou MANAGER;

Isso garante que somente usuários previamente cadastrados com roles específicas e que assinem a transação consigam excluir outros usuários. Agora vamos à função de exclusão, que é super simples já que a remoção acontece no próprio context. Atenção ao “_” no ctx para evitar warnings do Rust na compilação por não estarmos usando o context.

Agora o teste de remoção fica assim e aqui tem algumas pegadinhas.

Primeiro, precisamos adicionar o usuário gerente, depois o usuário cliente. Não esqueça de obter o PDA deles. Como a remoção vai impactar no saldo de quem mandar excluir, pegamos o saldo antes da exclusão também.

Na chamada do removeUser, temos de passar o PDA account do usuário a ser excluído, o PDA account de quem está excluindo (para verificação de role) e o endereço público do signer da transação (quem está excluindo também). Atenção aqui que o Anchor vai trocar o snake case do Rust por camel case e isso pode causar confusão. não esqueça também de assinar a transação com a mesma carteira gerente.

Após a exclusão, verificamos se o saldo do manager foi aumentado (por causa da devolução de rent) e se o PDA do usuário excluído dá erro quando fazemos um fetch.

Outro cenário válido de ser testado é o de não permitir a exclusão quando o usuário requisitante não for um manager ou owner, como abaixo.

Não tem muita novidade aqui, exceto que ao criarmos o suposto manager, dei a permissão de customer pra ele, o que fará com que dê erro se ele tentar assinar transações de exclusão de outros usuários.

E com isso finalizamos mais este tutorial de programação Solana aqui no blog.

Até a próxima!

TAGS:

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *