Tutorial ERC-1155 com Solidity e HardHat (JS) - Parte 2

Cripto

Tutorial ERC-1155 com Solidity e HardHat (JS) - Parte 2

Luiz Duarte
Escrito por Luiz Duarte em 02/12/2023
Junte-se a mais de 34 mil devs

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

Recentemente escrevi um tutorial ensinando como implementar um contrato multi-token usando HardHat, Solidity e TypeScript, além da biblioteca de contratos OpenZeppelin que segue a especificação ERC-1155. Se você não fez este primeiro tutorial, corre lá que é obrigatório pois nessa parte 2 vamos escrever apenas os testes automatizados para nossos contratos, ok? Link aqui.

Outra recomendação é você entender primeiro do padrão de multi-tokens em si, algo que abordei em outro tutorial de ERC1155 onde escrevemos todo o contrato do zero, pois é ali que construímos o conhecimento de como tudo funciona, permitindo que a gente consiga saber como testar um contrato multi-token também.

Então mãos à obra pois temos muitos testes a escrever!

Curso Node.js e MongoDB

Principais testes do nosso contrato

Comece abrindo o arquivo MyTokens.test.ts na pasta test e vamos escrever nosso primeiro teste, que irá mintar um novo token.

Começamos os testes sempre chamando a função deployFixture que criamos anteriormente para pegar uma instância zerada do contrato a fim de manter a independência entre os testes. Na sequência chamamos a função em questão que iremos testar: mint(). Eu resolvi fazer um teste de integração mais completo ao invés de unitário, para ganharmos tempo. Começamos carregando a instância do contrato e o owner que fez o deploy dele. Depois, chamamos a função de mint() em si, enquanto que o resto todo do código são as variáveis de verificação se o minting funcionou. Para isso eu optei por verificar se o saldo do owner é 1,  e se o total supply de tokens na coleção é 49 (ele começa em 50 e deve ter decrementado 1 após o mint). Aqui vale uma atenção especial à chamada do mint passando o último parâmetro que é objeto de transação, onde devemos pagar pelo mint.

Opcionalmente você pode quebrar este grande teste em vários testes menores ou então até mesmo criar variações, testando de forma diferente.

Eu não vou ficar citando, mas a cada teste codificado, o ideal é rodar a bateria toda com o comando ‘npx hardhat test’ a ver se estão passando, ok? Outra possibilidade é rodar com ‘npx hardhat coverage’ para ter relatórios de cobertura de código.

Agora vamos pensar nos cenários de fracasso do mint.

Aqui testamos a falha em decorrência no token id não existir, do pagamento ser insuficiente e do supply máximo daquele token ter sido atingido (são semi-fungíveis, lembra?). Os dois primeiros são bem diretos e dispensam grandes explicações, enquanto que o último exige que você esgote todo o supply para simular a falta em estoque, o que fiz com um laço for mintando sem parar.

Agora vamos escrever o teste para a funcionalidade de burn.

Aqui nós temos de primeiro mintar um token, já que precisamos que exista algum para queimá-lo. Então logo após o mint chamamos a função de burn e o restante do código são para coleta de indícios que comprovem que o burn foi efetuado com sucesso como verificar se o total supply está inalterado e se o saldo do owner também está zerado.

Outra variação de teste da funcionalidade de burn diz respeito a burn delegado, isto é, o owner do token pode ceder o controle dele a outra pessoa, que pode decidir por queimar o token. Isso envolve uma série de mecanismos internos e é interessante de ser testado.

Esta variação é bem mais complexa que o teste de burn tradicional pois envolve aprovar um operador/controlador diferente para o token após mintá-lo. Então usamos a função connect para gerar uma conexão com o contrato se fazendo passar por outra carteira e realizamos o burn a partir dela. O restante do código é a coleta de evidências do burn.

Mas e os cenários de fracasso? Para o burn nós temos ao menos dois.

No primeiro cenário nós tentamos queimar um token que não temos unidades na conta, o que deve gerar um erro com o custom error que especifiquei. Este custom error eu peguei diretamente do ERC1155Burnable.sol na biblioteca OpenZeppelin. No segundo teste, uma segunda conta tenta queimar um token que não é dela e que não possui o controle, obviamente obtendo fracasso.

Agora vamos implementar os testes ligados aos metadados dos nossos tokens. Vou criar um teste que retorna com sucesso a URL do JSON de metadados e outra que fracassa pois o token em questão não existe.

Repare que você terá de ajustar o primeiro teste à sua realidade de URL, e lembro também que esta minha URL é fictícia, apenas para fins didáticos, não vai funcionar se tentar acessá-la no navegador, mas em um cenário real, deveria.

Testes de Transferência

Agora todos os demais testes são questionáveis de serem feitos. Isso porque dos códigos que escrevemos, os testes acima cobrem completamente e qualquer outra coisa que testarmos estará testando na verdade os contratos da OpenZeppelin, códigos que não são nossos. Claro que é interessante escrever estes testes principalmente para fins didáticos, para termos um melhor entendimento do funcionamento dos contratos e de como usar o nosso contrato em uma aplicação real, por isso vou fazê-lo, mas do ponto de vista de cobertura de código/testes, eles não agregam nem 1%.

Vamos começar então testando a transferência de token entre contas.

Aqui começamos mintando o token para em seguida transferi-lo, sendo o restante apenas as verificações do teste, para saber se o owner original não tem mais a propriedade do mesmo e se o novo owner tem.

Uma outra coisa que é interessante verificarmos, pois a especificação ERC1155 assim exige, é se os eventos estão sendo corretamente emitidos. Então vamos testar a emissão do evento de transferência, na mesma situação acima.

Aqui o segredo é o expect que usa o .to.emit para ler os logs da blockchain e saber se o evento foi devidamente registrado. Porque na prática os eventos da blockchain nada mais são do que logs registrados e associados aos blocos.

Uma diferença dos contratos 1155 para os 721 são as transferências em lote (batch transfer). Então vamos escrever os testes para a transferência e para a captura de eventos em lote, sendo que a única diferença são os parâmetros de conta e endereço como arrays. Repare que aproveitei esse teste também para testar a função de saldo em lote.

Agora vamos testar outro tipo de transferência, a delegada, quando o owner do token permite que outra carteira possa transferir o mesmo.

Aqui a única diferença em relação aos testes de transferência anteriores é a chamada ao approve para permitir que a gente se conecte à segunda carteira na sequência e faça a transferência normalmente. Como já verificamos o processo normal de transferência, deixo os expects aqui cobrirem apenas o que toca a ideia de aprovação. E como a aprovação emite também um evento específico, vamos testá-lo em outro teste.

Na sequência, temos que escrever os cenários de fracasso, certo? Porque existem várias coisas que podem impedir uma transferência de ser realizada com sucesso e vou cobrir aqui a falta de saldo, falta de permissão, a não existência do token, a aprovação para outra carteira que não a sua, parâmetros inválidos de lote e falta de permissão no transfer em lote, respectivamente.

Note que a maioria dos testes de fracasso falham com o mesmo custom error pois caem na mesma situação de ausência de permissão. Escrevi todos eles mais para testar os mecanismos de aprovação mesmo.

Na sequência, vamos escrever mais um teste para a função supportsInterface, sendo que o código hexadecimal ali eu peguei da documentação da ERC1155 e representa a interface, facilitando a verificação se ela está suportada em nosso contrat.

E por último temos os testes de sucesso e de fracasso do saque do saldo do contrato para sua carteira.

No testes de sucesso primeiro temos de mintar um token, a fim de que recebamos saldo no contrato, que será transferido mais tarde no mesmo teste. Então coletamos o saldo antes do saque, fazemos o saque e depois coletamos o saldo após o mesmo, verificando se houve um incremento para o dono do contrato. Note que é difícil de conseguir saber o valor exato que ele terá em virtude das taxas de transação que são pagas para fazer o saque em si, por isso optei por uma comparação não literal (greater than), mas relativa.

Já o cenário de fracasso eu fiz com uma tentativa de alguém realizar o saque que não seja o próprio dono do contrato.

Agora experimente rodar a bateria de testes com ‘npxhardhat test’ e espero que você tenha o meso resultado que eu abaixo.

E com isso finalizamos mais este tutorial. Se quiser aprender como fazer deploy desta moeda em blockchain local, recomendo aprender a usar o HardHat Network neste tutorial. Agora se quiser colocar em uma blockchain pública, recomendo fazer este tutorial aqui. E se quiser aprender a reduzir o consumo de gás nos seus smart contracts, este é o artigo certo. Uma última dica é hospedar os metadados e as mídias dos seus NFTs no IPFS, falo disso no vídeo abaixo.

Um abraço e sucesso!

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 *