paint-brush
4 antipadrões comuns de design de contrato de NFTpor@jrkosinski
1,180 leituras
1,180 leituras

4 antipadrões comuns de design de contrato de NFT

por John R. Kosinski8m2022/08/26
Read on Terminal Reader
Read this story w/o Javascript

Muito longo; Para ler

Neste artigo, darei minhas opiniões sobre o que considero 5 das "falha de design" mais comuns para NFTs. Este artigo foi escrito principalmente com blockchains compatíveis com EVM em mente, mas muitos dos pontos são aplicáveis ou têm alguma analogia ou equivalente em outras redes.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - 4 antipadrões comuns de design de contrato de NFT
John R. Kosinski HackerNoon profile picture

O boom do NFT aconteceu e ainda está acontecendo (no momento em que este artigo foi escrito em julho de 2022). O Etherscan tem um prático utilitário de pesquisa que, junto com seus recursos úteis de verificação e descompilação, permite que você dê uma olhada no código de muitos ERC721 para comparar. Juntamente com muitos contratos bem elaborados, também podemos ver muitos cometendo os mesmos erros repetidamente. Neste artigo, darei minhas opiniões sobre o que considero 4 das "falha de design" mais comuns para NFTs, que geralmente noto ao visualizar contratos NFT no etherscan.


Observe que este artigo foi escrito principalmente com blockchains compatíveis com EVM em mente, mas muitos dos pontos são aplicáveis ou têm alguma analogia ou equivalente em outras redes também.

Por favor, não se sinta insultado se você cometeu algo do que chamo de “falha de design”. Estas são minhas opiniões e, além disso, como desenvolvedor, entendo perfeitamente a necessidade de economizar nos custos de gás nestes tempos de congestionamento de rede caros. Considere, porém, meu ponto de vista como consultor freelancer; um cliente que pode gastar milhares de dólares em ajuda de desenvolvimento pode certamente definir uma centena extra para implantação, na busca da excelência.


É claro que isso está falando da cadeia Ethereum, que é a mais cara até o momento; Polygon é menos, e outras cadeias como Solana (um não-EVM) ainda menos. Meu ponto é que, se houver fundos disponíveis, os benefícios de uma implementação de maior qualidade podem valer o custo adicional.


Antipadrão nº 1: inclua informações e lógica de preço/venda em seu contrato

Isso é extremamente comum, mas quando vejo isso, sinaliza o contrato, aos meus olhos, como feito de forma amadora. Para ser justo, existem motivações válidas e compreensíveis. Por um lado, implantar e gerenciar contratos em muitas redes tornou-se muito caro, e esforços foram feitos para economizar nesses custos. E, para simplificar, pode-se pensar, por que não colocar a lógica de cunhagem e venda no próprio contrato?


Mas isso não é realmente uma boa ideia. O próprio contrato deve ser o centro imutável de uma rede de lógica, mas nunca deve sujar as próprias mãos ao lidar diretamente com o dinheiro. Isso inclui venda, horários de venda, lista de permissões, etc., diretamente no mesmo código de contrato da implementação do ERC721. A lógica de venda e a lógica central estão fortemente ligadas.


típico de muitos contratos NFT no etherscan, este lida com vendas no ERC721


Embora economizar nos custos de gás possa ser o melhor e mais compreensível motivo para colocar toda a lógica em um contrato, acho que, considerando tudo, há motivos muito melhores para não implementar esse atalho de design. Sua lógica básica de contrato deve ser a única coisa imutável e, na maioria dos casos, implementará o padrão de uma maneira muito, bem... padrão. Muitos clones são (ou poderiam ser) quase clones uns dos outros. Sua estratégia de cunhagem, preços (se você estiver vendendo balas) - esses tipos de coisas devem ser dissociados. Isso permite que seu contrato seja flexível de uma forma que não prejudique a confiança do usuário. Projeto desacoplado e princípio de responsabilidade única. Observação: acho que faz sentido restringir o fornecimento (ou seja, maxSupply) no próprio contrato ERC721, desde que possa ser modificado por alguém com função administrativa.


NFTStore recebe o papel MINTER de seu contrato associado, portanto, tem a responsabilidade de cunhar o NFT


Um exemplo de um contrato sendo implantado no código web3, com o contrato “loja” sendo implantado ao lado dele. Observe que o contrato de “loja” recebe a função de minter.


Antipadrão nº 2: não implemente segurança baseada em função

Um contrato de token precisa de algum tipo de controle de acesso, porque existem funções (como cunhar ou fazer qualquer coisa nos parâmetros de fornecimento) que devem estar disponíveis apenas para endereços autorizados. A maneira mais simples de conseguir isso é usar um modelo Ownable (geralmente usando o contrato Ownable do OpenZeppelin, porque reinventar a roda para uma necessidade tão básica). Mas eu sugeriria fortemente o uso de um controle de acesso baseado em função, pelos seguintes motivos. A motivação por trás do uso do Ownable (ou algo semelhante) é provavelmente a simplicidade (e economia nos custos de combustível), o que é bom na superfície. Você também pode “saber” que você (ou seu cliente) será “sempre” o único a administrar o contrato. A preparação para o futuro é preferível, quando o custo é baixo; e a complexidade da segurança baseada em função (por exemplo, IAccessControl do OpenZeppelin) é honestamente um pouco mais complexa (e cara) quando comparada ao modelo Ownable. Se os custos de gás ainda forem um problema, você sempre pode cortar o código de segurança baseado em função (seja OpenZeppelin ou o seu próprio) para apenas o que você precisa. Mas o motivo mais importante para usar baseado em função é que ele permite desacoplar a funcionalidade (como no ponto anterior, informações de venda e preços) do próprio contrato ERC721. Ele permite que você designe um contrato separado como o minter, atribuindo-lhe a função de “minter”, sem permitir permissões administrativas totais. Considerando que o administrador (ou administradores, que provavelmente são humanos e não contratados) ainda têm permissões de nível superior (como remover e adicionar permissões). Quando a cunhagem (por exemplo) não atende mais às suas necessidades, simplesmente a aposenta, revogando seus direitos de cunhagem e atribuindo direitos de cunhagem a um novo contrato implementando uma nova estratégia de cunhagem; é modular, conveniente e seguro. Outras atividades além da cunhagem podem ser tratadas da mesma forma, com base nos casos de uso específicos do projeto.


AccessControl.sol da OZ implementa segurança baseada em funções


As ações podem ser restritas a funções


Antipadrão nº 3: não implemente o ERC-165 (introspecção) corretamente

Muitos tokens (ou contratos em geral) não implementam o ERC-165 ou não o implementam de maneira ideal. ERC-165, a meu ver, é sobre interoperabilidade. Isso torna seu contrato compatível com o futuro e as exchanges podem chamá-lo para descobrir (por exemplo) sobre a estrutura de royalties do seu NFT. Eu vejo isso muitas vezes não implementado de forma alguma, ou implementado de forma sub-ótima.

Aqui está uma regra de ouro para implementá-lo corretamente:

  • qualquer classe pai que implemente ERC-165 deve estar na lista de substituição. Então, eles serão invocados quando você chamar super.supportsInterface, automaticamente.
  • quaisquer outras interfaces implementadas que não sejam representadas nas classes pai podem ser adicionadas com uma cláusula ou, como esta:

|| type(ISomeInterface).interfaceId == _interfaceId


exemplo:

 function supportsInterface(bytes4 _interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(_interfaceId) || _interfaceId == type(IERC2981).interfaceId; }


Se seu código não tiver classes pai que implementem ERC-165, apenas o segundo tipo deverá ser representado, como

 function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { return _interfaceId == type(IERC721).interfaceId || _interfaceId == type(IERC2981).interfaceId || _interfaceId == type(IAccessControl).interfaceId; }


Se o seu código não implementa nenhuma outra interface além daquelas manipuladas pelas implementações das classes pai do ERC-165, então o segundo tipo não é necessário. Tal como:

 function supportsInterface(bytes4 _interfaceId) public view override(ERC721, ERC721Enumerable) //just make sure this list is complete returns (bool) { return super.supportsInterface(_interfaceId); }


A implementação correta do ERC-165 é opcional, mas importante. Você deseja que seus tokens sejam compatíveis com o maior número possível de outros sistemas (como exchanges), incluindo os futuros que ainda não foram implementados. O padrão ERC-165 provavelmente se tornará mais usado e importante conforme o tempo passa e o espaço amadurece.


Antipadrão nº 4: não teste exaustivamente antes de implantar

Seu token ERC721 pode ser muito padrão e pode usar todas as classes e bibliotecas pai de terceiros com muito pouca personalização, e você pode saber que esse código de terceiros é notoriamente bem testado e seguro. Mas você ainda precisa testar completamente seu código, porque você só tem uma chance de acertá-lo antes de ser implantado na rede principal, trazendo glória ou vergonha para sempre.


Primeiro, é claro, teste de unidade . Qual estrutura de teste você usa não é importante, na minha opinião; Eu uso capacete com éter e mocha. Para mim, a única parte importante é que a cobertura de teste e a cobertura de casos felizes, casos excepcionais e casos extremos são amplas e profundas. Mesmo que você esteja testando um código (por exemplo, OpenZeppelin) que já foi bem testado, (a) seu código personalizado pode ter quebrado alguns desses casos, portanto, eles devem ser testados novamente e (b) o OpenZeppelin já teve bugs antes e eles podem novamente no futuro. Para poupar algum tempo, você pode ter um conjunto padrão de testes para todos os tokens ERC721, todos os tokens ERC20, todos os tokens ERC1155 etc. que você pode reutilizar de projeto para projeto. Isso é bom. Em seguida, você pode adicionar casos para cada projeto para cobrir todas as personalizações do padrão; isso vai economizar tempo. Os testes de unidade devem abranger controle de acesso, funcionalidade básica (como cunhagem e transferência), capacidade de pausa (se seu contrato for pausável), implementação do padrão ERC165 e muito mais. Você pode testar sua cobertura usando solidity-coverage (um pacote nodejs).


Por fim, as ferramentas automatizadas podem fornecer uma grande ajuda nos testes. Slither , Manticore e Mythril são padrões da indústria, normalmente usados pelos principais nomes em auditoria de segurança como Consensys e Certik. Solidity-coverage (um pacote nodejs) informará as porcentagens estimadas de cobertura que seus testes de unidade estão fornecendo (muito útil como regra geral). Solgraph é uma ferramenta que pode ajudá-lo a ver relacionamentos e conexões no código do contrato; útil no planejamento de testes. Equidna também é útil; é uma ferramenta de teste fuzzing. Pessoalmente, eu uso uma metodologia de teste inicial quando aplicável. Isso garante uma boa cobertura de teste e o conjunto de testes torna-se semelhante a uma especificação de projeto. Eu me amo uma boa cobertura de teste.

 > pip3 install slither-analyzer > pip3 install mythril > npm install solidity-coverage


Então, para resumir:

  • testes de unidade completos e profundos, obtendo o máximo possível de casos felizes, casos excepcionais e casos extremos
  • testes e exames manuais
  • uso de ferramentas automatizadas como slither, manticore, mythril, echidna e cobertura de solidez


Dica: verifique seu contrato no Etherscan

A verificação do código do contrato no etherscan é um ótimo recurso. Ver a marca de seleção verde na guia Contrato e poder ver o próprio código, com a tag "correspondência exata", emana confiança e responsabilidade. Quando alguém está visualizando seu contrato pela primeira vez, tentando avaliar os riscos relativos de uso ou investimento, isso só pode ajudar. E isso em geral, para todos os contratos de todos os tipos, não apenas NFTs.