Controles de acesso em Smart Contracts com Solidity

Cripto

Controles de acesso em Smart Contracts com Solidity

Luiz Duarte
Escrito por Luiz Duarte em 02/03/2023
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 contratos Solidity nós precisamos incluir funções administrativas, para uso do administrador ou dono (owner) do contrato. Às vezes é uma função de mint do NFT, às vezes é uma função para atualizar alguma informação no contrato ou ainda uma função de sacar o saldo do contrato 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 contrato.

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 contrato de exemplo sobre o qual vamos aplicar os dois cenários de implementação. Abaixo uma sugestão.

Imagine que você tem o contrato acima e que enquanto a função getMessage é de acesso geral, a função setMessage você quer ter mais controle sobre quem pode acessá-la. Um exemplo é mostrado no vídeo abaixo, mas o tutorial a seguir traz mais detalhes.

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

Curso Node.js e MongoDB

Design Pattern: Ownable

Um dos padrões mais populares no mundo Solidity é o Ownable. O padrão Ownable determina que um contrato possui um dono, que esse dono possui acesso a funções especiais e também que a propriedade do contrato pode ser transferida. Claro que você pode pegar apenas o que lhe convém do contrato, mas em linhas gerais ele permitira tudo isso.

Para implementar o padrão ownable vamos começar criando um contrato abstrato. Um contrato abstrato é um meio termo entre uma interface e um contrato. Ele não pode ser deployado na blockchain pois não é um contrato real, mas ele serve como base para um contrato e obriga-o a seguir certas regras, como uma interface faria.

Nosso abstract contract Ownable definirá os poderes de nosso smart contract com permissão de acesso.

Aqui definimos uma variável de endereço privada que setamos no constructor do contrato, ou seja, quando for feito o deploy dele será pego o endereço de quem fez e esta pessoa será o primeiro owner do mesmo. Caso você não queira que esse owner mude nunca, você pode tornar o _owner immutable e não implementar a função transferOwnership, mas isso raramente é uma boa ideia.

Além da variável _owner e da sua atribuição no deploy, nosso contrato abstrato define um function modifier chamado onlyOwner, que determina a regra de um novo modificador que poderemos usar em nossas funções. Essa regra é bem simples: funções com o modificador onlyOwner somente podem ser invocadas pelo owner do contrato.

E por fim, definimos uma função transferOwnership que serve para que o owner atual ceda a propriedade do contrato para um novo owner.

Agora que temos a nossa base pronta, podemos aplicar em nosso contrato RawContract.sol da seguinte forma.

A keyword ‘is’ diz que RawContract herda todas as características de Ownable. Com isso, agora o construtor de Ownable estará presente na versão final deployada de RawContract, bem como demais funções e variáveis de estado, sem precisarmos repetir todo aquele código. Repare como aplico então o modificador onlyOwner na função setMessage, fazendo com que agora ela só possa ser chamada pelo owner do contrato.

E é isso. Com esse padrão você consegue facilmente determinar donos para contratos e aplicar regras em funções.

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

Design Pattern: Access Control

Outro padrão muito popular é o Access Control ou Controle de Acesso. Com ele, definimos alguns papéis em nosso contrato e quem possui aqueles papéis, além de definir maneiras de validar a permissão de acesso a determinadas funções facilmente.

Assim como vários outros design patterns, aqui nós usamos contratos abstratos para definir a estrutura-base dos contratos com controle de acesso. Dá pra fazer bem mais complicado do que vou mostrar (e fica mais completo também), mas a base é essa abaixo.

Comecei definindo em um enumerador todas as roles que vou permitir no meu contrato, sendo que por default todo mundo tem a primeira (NONE). Em algumas implementações isso seria um array ou um mapping de roles para que possam ser criadas novas roles com o passar do tempo. Outra abordagem nesta linha de lista estática é definir as roles em constantes no código, mas acho que com enum fica mais amigável a nós humanos.

Depois eu defini um mapping onde para cada carteira nós descobrimos a role da pessoa, lembrando que o default é NONE (0). Nesta minha implementação do AccessControl cada wallet pode ter apenas uma role, ok? Em outras variações que você vai encontrar na internet o mapeamento é feito de role para members (um array de wallet), o que permite que cada wallet possa ter várias roles ao mesmo tempo.

Com isto nós podemos criar o construtor que definirá a role de owner para a wallet que fez o deploy do contrato. Do contrato real, é claro, pois este é apenas um abstract contract. Esta role de owner é importantíssima pois mais à frente você verá uma função setRole que somente pode ser chamada pelo owner do contrato. Essa função adiciona uma role a um endereço de carteira se ele já não estiver nela.

E por último, definimos o modifier onlyRole que permite atribuir a exigência de roles específicas às funções, como fizemos com a setRole.

Comparando esse abstract contract com o anterior você verá muitas semelhanças, como se ele fosse uma evolução.

Agora temos um contrato que implementa esse contrato abstrato.

Aqui nós começando herdando de AccessControle com a keyword ‘is’ e em seguida definimos agora que setMessage somente pode ser chamada por endereços com a role de Manager. Desta forma, quando for testar, primeiro você deve usar o owner para atribuir a role de manager a algum endereço e depois podemos usar este endereço para chamar a setMessage. Opcionalmente você pode colocar um bypass na function modifier onlyRole para que o owner sempre possa chamar qualquer função, mesmo que ele não tenha a role específica ou qualquer outra lógica que você julgar conveniente, como uma hierarquia de roles (uma role mais alta pode chamar funções qiue exijam roles mais baixas).

E com isso finalizamos mais este tutorial de Solidity aqui no blog.

E se quiser aprender a reduzir o consumo de gás nos seus smart contracts, este é o artigo certo.

Até a próxima!

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 *