Quando se procura distinguir entre Web2 e Web3, um valor-chave que diferencia os dois é o conceito de propriedade.
Simplificando, o que você cria é o que você possui e pode gerar receita. Assim como ele deve ser. Nada menos, nada mais. Na verdade, o dia em que você cunha seu primeiro NFT é quando você o faz na Web3 como proprietário, graças à imutabilidade do blockchain. Na verdade, a sensação de estar protegido não tem preço.
Falando nisso, esse conceito de propriedade é tão importante para contratos inteligentes em relação a funções acessíveis e mudança de estado permitida.
Então, por que é importante que você seja o "proprietário" do contrato inteligente que escreveu?
Pense nisso: se você tivesse uma bicicleta, hesitaria em manter os registros de propriedade? Apenas no caso de alguém ter roubado e usado para fins nefastos? Claro que não.
É pelo mesmo motivo que você deseja provar sua propriedade de um NFT ou um contrato inteligente. Bem, alguém pode obter acesso não autorizado e se beneficiar financeiramente com isso, certo?
Digamos que você trabalhe com Dunzo e tenha escrito este contrato inteligente onde você pode definir o preço do pacote mais as taxas de entrega e se foi pago ou não pelo comprador.
No cenário ideal, você pode definir o nome do comprador, o preço total do pacote e a entrega para um determinado valor e definir setPackageDelivered como true. Assim que terminar de entregar o pacote, é claro.
Mas, como todos sabemos, nada sai como planejado.
Agora, e se o comprador tivesse acesso às funções do contrato e a capacidade de redefinir o valor packageDelivered para false ou alterar o valor packageDeliveryPrice para um menor? Eles não apenas podem pagar menos pelo produto, mas também podem se safar sem pagar nada.
Claramente, além de Dunzo, ninguém mais deveria ter acesso para fazer tais alterações, e é por isso que definir o controle de acesso é importante.
Agora, vamos implantar esse contrato inteligente e ver como é fácil para um intruso alterar os valores nas variáveis de estado.
Com as funções setPrice e setPackageDelivered acessíveis, qualquer pessoa pode fazer alterações que podem resultar em perdas financeiras para a entrega Dunzo.
Se Dunzo definiu o preço original do item em 5 ETH, o cliente agora pode alterar esse valor para 3 ETH. Obviamente, como o cliente também pode acessar a função setPackageDelivered e alterar o valor booleano para false, ele pode receber outra entrega do mesmo item. Portanto, em ambos os casos, Dunzo pode perder dinheiro e pode nem perceber até que seja tarde demais.
Por exemplo, digamos que o produto que está sendo entregue vale 5 ETH, conforme imagem abaixo:
Após a implantação, ninguém além de Dunzo deve poder modificar os termos do contrato. Ainda assim, quando trocamos os endereços no Remix e redefinimos o preço para 3, isso é possível devido à falta de proteção.
Podemos até alterar o status setPackageDelivered de verdadeiro para falso. Mesmo que o pacote já tenha sido entregue ao cliente.
Como você pode ver, é importante distinguir entre o proprietário e outros usuários desse contrato inteligente. Então, vamos ver 4 correções que definirão o controle de acesso adequado para este contrato inteligente e garantirão sua segurança como resultado.
Então, como diferenciar entre o proprietário e outros usuários ao escrever contratos inteligentes?
Claramente, isso é necessário devido à perda financeira que pode ocorrer se for ignorado.
Método #1: Use uma declaração de requisição
Como você pode ver no código abaixo, um novo proprietário de variável de estado do tipo de endereço foi adicionado junto com uma instrução 'require'. No construtor, a variável de estado do proprietário armazena o endereço que é usado ao implantar o contrato. Como você sabe, os construtores em contratos inteligentes são invocados apenas uma vez, portanto, o endereço não pode ser alterado.
Agora, ao invocar a função setPrice com um valor menor e um endereço diferente, a transação é revertida ao estado inicial, muito parecido com a mensagem abaixo:
Como você pode ver, o motivo fornecido pelo contrato envolve a mensagem de erro que adicionamos à instrução 'require'. Dito isso, ninguém além do dono do contrato - Dunzo, neste caso - pode alterar os termos de venda.
Método #2: Use um modificador de função
Por mais simples que seja adicionar uma instrução 'require' com a condição correta, um modificador é um bloco de código que você pode usar repetidamente. Isso certamente faz muito mais sentido do que escrever várias declarações de 'exigência' para tal verificação.
No código compartilhado anteriormente, apenas a função setPrice é protegida por uma instrução 'require', mas nada foi feito para a função setPackageDelivered aqui. Usar um modificador deve funcionar, conforme mostrado no código do Solidity abaixo:
Agora, se você tentar acessar as funções setPrice ou setPackageDelivered, obterá a mesma mensagem de erro obtida anteriormente e conforme mostrado abaixo:
Método nº 3: Possuível
Essa correção exige que você use o contrato inteligente Ownable fornecido pelo OpenZeppelin. Primeiro, você precisa importar o contrato inteligente Ownable e, em seguida, adicionar o modificador onlyOwner às funções que deseja proteger. Portanto, conforme mostrado abaixo, as funções setPrice e setPackageDelivered possuem o modificador onlyOwner abaixo.
Agora, como o contrato inteligente dunzoDelivery usará algumas das funções em Ownable.sol, o “is Ownable” também será adicionado ao nome do contrato.
Dito isso, existem duas outras funções que podem ser acessadas ao usar o contrato inteligente Ownable: renounceOwnership e transferOwnership. Embora a conta que foi usada para implantar o contrato geralmente seja considerada a proprietária, isso pode ser alterado usando essas funções.
Além de ver o endereço do proprietário e outros detalhes do contrato, você pode ver as funções renounceOwnership e transferOwnership para uso abaixo:
Conforme esperado, quando você tenta invocar a função setPrice usando outro endereço, a transação não será concluída. Em vez disso, ele volta ao estado original, com o motivo fornecido abaixo:
Usar o contrato inteligente Ownable é bom se você precisar de apenas um único proprietário entre outros usuários. Se você estiver procurando por mais hierarquia, o contrato inteligente AccessControl.sol deve ser usado.
Método #4: Controle de Acesso
Esta última correção requer que você acesse o contrato inteligente AccessControl da Open Zeppelin.
Para começar, você deve adicionar o hiperlink de controle de acesso à declaração de importação, bem como adicionar “is AccessControl” à declaração de contrato.
Conforme mencionado anteriormente, este contrato inteligente permite adicionar uma série de papéis dentro de uma hierarquia e que você deve declarar, conforme as duas primeiras linhas do contrato inteligente mostrado abaixo:
Neste contrato, existem duas funções, Dunzo e Cliente. Agora, ao implantar o contrato, você terá que atribuir as funções declaradas acima aos referidos endereços, para determinar quem pode acessar o quê. Além disso, duas instruções 'require' foram adicionadas às funções setPrice e setPackageDelivered.
Como você pode ver, apenas a conta que recebeu a função Dunzo poderá alterar os termos de venda mencionados durante o período de implantação, mas nenhuma outra. Dito isso, você pode atribuir várias funções com diferentes níveis de acesso a funções de um contrato inteligente usando AccessControl.sol, enquanto Ownable.sol não entrará em tanta profundidade.
Agora, essas não são as únicas soluções disponíveis para implementar o controle de acesso em contratos inteligentes. RBAC.sol e WhitelistCrowdSale.sol também estavam disponíveis no passado e também podem associar funções a endereços, assim como AccessControl.sol e Ownable.sol.
Portanto, se você deseja obter um entendimento completo sobre a evolução da implementação do controle de acesso, valerá a pena revisar o código nesses dois contratos inteligentes também.
Também publicado aqui.
A imagem principal deste artigo foi gerada peloAI Image Generator do HackerNoon por meio do prompt "acesso negado varredura biométrica".