Como migrar smart contracts do OpenZeppelin v4 para v5

Cripto

Como migrar smart contracts do OpenZeppelin v4 para v5

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

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

A OpenZeppelin é indiscutivelmente uma das empresas que mais contribui com os desenvolvedores de smart contracts, fornecendo templates de contratos validados, seguros e bem documentados, o que facilita muito a construção de projetos comuns como tokens ERC-20, ERC-721 e outros. Recentemente eles fizeram um grande update na sua lib de contratos, a OpenZeppelin Contracts, evoluindo da versão 4 para a versão 5 e com isso quebrando diversos projetos.

Claro que você sempre tem a opção de ficar na versão que está, mas considerando que com esses grandes updates sempre vem diversas melhorias de performance (o que impacta nos custos de gás) e de segurança, pode ser que valha a pena para você fazer esta migração.

Escrevo este post da maneira que eu gostaria de ter encontrado na Internet quando precisei atualizar aplicações da OpenZeppelin v4 para OpenZeppelin v5 e espero que lhe ajude também. Algumas mudanças são sutis e possivelmente você já resolveu sozinho, outras são mais grosseiras mesmo e afetam principalmente usuários de alguns contratos que deixaram de existir. Não é para ser um post extenso e ao mesmo tempo ele deve ser atualizado ao longo do tempo, conforme vou aprendendo mais sobre OZv5.

Curso Node.js e MongoDB

Mudanças Gerais e Utilitários

Vou tentar organizar aqui o racional com base em algumas mudanças gerais e depois vou para mudanças em contratos específicos. A primeira mudança geral significativa foi a atualização da versão de Solidity dos contratos, sendo agora a versão 0.8.20 do compilador usada nos contratos. Isso não me impactou por aqui, já que eu já usava a 0.8.20, mas pode significar alguma coisa para você.

Outra mudança, muito bem vinda na minha opinião, foi a extinção do contrato utilitário Counters.sol. O Counters era um contrato de utilidade duvidosa (afirmado pela própria equipe da OZ em issues no GitHub deles) já que o mesmo resultado poderia ser facilmente obtido sem o mesmo, bastando ter uma variável de estado como contador no seu contrato. Então se você usa(va) o Counters.sol, pode remover ele dos seus imports e usar alguma código como o abaixo em substituição (o que vai te fazer economizar gás inclusive).

Uma outra mudança bem sutil é o do contrato Pausable.sol, que serve para pausar funções em contratos. Antes (v4), o import dela ficava em uma pasta security, agora se mudou para uma pasta utils, como abaixo. O mesmo vale para o contrato ReentrancyGuard.sol.

Mais uma mudança, esta não exatamente sutil pois vai requerer que tome uma decisão a respeito, foi com o contrato Ownable.sol. Antes (v4), o owner por padrão de um contrato era quem fazia o deploy do mesmo. Agora na v5, o contrato Ownable exige que você chame o seu construtor na inicialização do contrato (deploy) e especifique o endereço de quem você deseja que seja o owner inicial. Claro, você pode usar o msg.sender se quiser, mas é algo que terá de especificar ou seu smart contract não vai compilar.

Outra mudança bem drástica diz respeito ao lançamento de exceções. Antes, eram disparados erros genéricos com mensagens especificando o que houve na transação. Agora, são disparados Custom Errors específicos para cada cenário de fracasso, o que acaba fazendo com que seus testes de erros deixem de funcionar até que os ajuste. Falarei melhor sobre este item nos tópicos específicos de tokens. Independente disso, o caminho aqui é sempre entrar no contrato específico, procurar o trecho de código que faz a validação que está acusando erro e pegar o nome do custom error para usar no seu teste.

Também vou abrir um tópico mais à frente dedicado aos contratos atualizáveis (upgradeable contracts).

Contratos ERC-20

Agora saindo das mudanças gerais e entrando nas específicas, vamos falar de contratos de tokens, a começar pelo padrão ERC-20, o mais utilizado para tokens fungíveis, as também chamadas “criptomoedas”. Aqui, se você usava apenas o contrato ERC20.sol, não há nada digno de nota, mas se usava outros contratos sim, pode passar a ter problemas ao migrar da OZv4 para OZv5, pois como mencionei antes, Ownable, Pausable, etc tiveram alterações.

Mas falando dos seus testes, se você costumava testar também as validações (o que recomendo que faça), você deve ter visto que eles deixaram de funcionar. Como mencionei antes, o time da OpenZeppelin deixou de usar erros genéricos com mensagens para usar custom errors, exemplo:

O processo é sempre o mesmo: trocar o revertedWith pelo revertedWithCustomError e usar o nome do erro personalizado ao invés da mensagem de erro na asserção. Abaixo, os custom errors do ERC20.sol que eu costumo usar em meus testes:

  • ERC20InsufficientBalance: para balance insuficiente;
  • ERC20InsufficientAllowance: para allowance insuficiente;

Além desses, dependendo dos contratos que você usa em conjunto do ERC20.sol, pode precisar usar outros custom errors. O mesmo vale para sobrescrita de funções (na dúvida, use o editor de contratos no site da OZ para ver os overrides necessários).

Contratos ERC-721

Agora entrando nos contratos de tokens não-fungíveis, padrão ERC-721, os também chamados “coleções NFT”. Aqui é muito comum o uso de diversas extensões em conjunto, como ERC721Enumerable.sol, ERC721Burnable.sol e ERC721URIStorage.sol. Se você usa todas estas extensões, as funções obrigatórias de serem sobrescritas são as abaixo (na dúvida, use o editor de contratos no site da OZ para ver os overrides necessários):

No mais as demais mudanças foram mais internas do que externas nos contratos, mas falando dos seus testes, se você costumava testar também as validações (o que recomendo que faça), você deve ter visto que eles deixaram de funcionar. Como mencionei antes, o time da OpenZeppelin deixou de usar erros genéricos com mensagens para usar custom errors, exemplo:

O processo é sempre o mesmo: trocar o revertedWith pelo revertedWithCustomError e usar o nome do erro personalizado ao invés da mensagem de erro na asserção. Abaixo, os custom errors do ERC721.sol (e extensões) que eu costumo usar em meus testes:

  • ERC721NonexistentToken: token não mintado/inexistente;
  • ERC721InsufficientApproval: sem aprovação prévia para fazer esta operação;

Além desses, dependendo dos contratos que você usa em conjunto do ERC721.sol, pode precisar usar outros custom errors.

Contratos ERC-1155

Agora entrando nos contratos multi-token, padrão ERC-1155, os também chamados “tokens semi-fungíveis”. Aqui é muito comum o uso de extensões em conjunto, como a ERC1155Burnable.sol, ERC1155Pausable.sol e ERC1155Supply.sol. Se você usa estas extensões, as funções obrigatórias de serem sobrescritas são as abaixo (na dúvida, use o editor de contratos no site da OZ para ver os overrides necessários):

No mais as demais mudanças foram mais internas do que externas nos contratos, mas falando dos seus testes, se você costumava testar também as validações (o que recomendo que faça), você deve ter visto que eles deixaram de funcionar. Como mencionei antes, o time da OpenZeppelin deixou de usar erros genéricos com mensagens para usar custom errors, exemplo:

O processo é sempre o mesmo: trocar o revertedWith pelo revertedWithCustomError e usar o nome do erro personalizado ao invés da mensagem de erro na asserção. Abaixo, os custom errors do ERC1155.sol (e extensões) que eu costumo usar em meus testes:

  • ERC1155InsufficientBalance: saldo insuficiente para a operação;
  • ERC1155MissingApprovalForAll: sem aprovação prévia para fazer esta operação;
  • ERC1155InvalidArrayLength: em operações em lote (batch), este erro indica que o length dos seus arrays de token ids e token amounts não está “batendo” (mismatch)

Além desses, dependendo dos contratos que você usa em conjunto do ERC1155.sol, pode precisar usar outros custom errors.

Contratos Atualizáveis

E por fim, vamos falar brevemente das mudanças que tivemos nos Upgradeable Contracts ou Contratos Atualizáveis da OpenZeppelin v5. Conforme citado anteriormente, algumas alterações em contratos utilitários impactam nos contratos atualizáveis também. Eu particularmente só uso o padrão Proxy, então é dele que vou estar me referindo aqui. Além disso, eu uso OpenZeppelin em conjunto do HardHat, então o plugin para atualizações de contratos é o @openzeppelin/hardhat-upgrades na versão 2.4.3 atualmente.

Importante salientar que esta versão de plugin do HardHat com a lib do OZv5 não está uma combinação perfeita havendo a necessidade de usar a flag –force na instalação dos pacotes pois a resolução de dependências está acusando que tem versões de um pacote específico do HardHat que não está compatível. Imagino que eles devam resolver isso em breve, mas por enquanto é o único jeito de fazer funcionar. Ainda nessa linha de incompatibilidades, estou tendo problemas nos testes do HardHat com o ERC1155SupplyUpgradeable.sol, que tem algumas funções de supply conflitantes no momento, que ainda não sei como resolver.

Uma das mudanças que você vai notar é se você usa o OwnableUpgradeable.sol, que agora assim como sua contraparte “estática”, exige a inicialização explícita do owner, como exemplificado abaixo.

E dependendo dos contratos upgradeable que você importar, terá de sobrescrever a função _update e outras, como abaixo (na dúvida, use o editor de contratos no site da OZ para ver os overrides necessários).

Agora falando de testes com HardHat, agora é necessário que você importe os typechains do seu contrato atualizável para poder fazer casting dele e com isso ter o autocomplete e até mesmo compilação dos testes quando usando a função connect, como abaixo, onde Multitoken é um contrato atualizável de ERC1155.

Sem o casting de Multitoken, nenhuma função do contrato atualizável é reconhecida no objeto instance.

E por enquanto estas são as alterações que tive de fazer em meus projetos e que resolvi compartilhar com vocês, para ajudá-los na atualização dos seus. Espero ter ajudado e 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 *

2 Replies to “Como migrar smart contracts do OpenZeppelin v4 para v5”

Rafael

20.274 (-1.063) ·
··················|
11.080 (-1.061) ·
··················|
9.439 (-1.215) ·
··················|
22.296 (-1.063) ·
··················|
7.615 (-1.242) ·
··················|
7.149 (-1.162) ·

Usando npx hardhat size-contracts, diminuiu cerca de 1kb meus contratos

Luiz Duarte

Legal!