Hoje no mercado de programação web3 existem dois toolkits que são muito utilizados em projetos de smart contracts para redes EVM: HardHat e Foundry, considerados em empate técnico nas últimas pesquisas de popularidade (SolidityLang.org). Apesar do HardHat ter reinado absoluto por alguns anos, algumas vantagens do Foundry estavam lhe dando tração, dentre elas as principais eram performance e testes em Solidity. Eis que surge HardHat v3, unindo o melhor dos dois mundos e endereçando as principais deficiências que possuía. No entanto, assim como acontece em todo grande salto técnico, algumas coisas mudaram.
Escrevo este post da maneira que eu gostaria de ter encontrado na Internet quando precisei entender o que mudou entre HardHat v2 para HardHat v3 e espero que lhe ajude também. Algumas mudanças são sutis e possivelmente você já entendeu sozinho, fuçando, outras são maiores mesmo e este artigo pode ajudar a economizar tempo. Não é para ser um post extenso e ao mesmo tempo ele deve ser atualizado ao longo do tempo, conforme vou aprendendo mais sobre HardHat v3.
Vamos lá!
#1 – Performance
A principal inovação do HardHat v3 é anunciada como sendo a performance.
Eu particularmente nunca tive problemas com a performance do HardHat v2, mas uso uma máquina muito boa para trabalhar (M1), então não sou parâmetro. Conforme seu projeto cresce e as baterias de testes se tornam intermináveis, é comum que passe a levar mais tempo a compilação e execução dos mesmos pois antigamente o único test runner do HardHat era escrito em TypeScript, mas isso mudou. Agora, assim como acontece no Foundry, seu principal concorrente, o test runner do HardHat possui suporte ao EDR ou Ethereum Development Runtime, que é escrito em Rust, o que garante uma execução muito mais rápida de testes de contratos usando a nova opção Solidity.
Ou seja, além do ganho substancial de performance o EDR garante ao HardHat a nova habilidade de rodar testes escritos em Solidity também, algo que era a segunda maior crítica ao toolkit e maior vantagem do principal concorrente. Falaremos mais sobre testes em Solidity adiante, mas o que mais importa neste ponto é que não há nenhum impacto direto nos seus projetos antigos por causa desta mudança e só sinalizei ela em primeiro lugar para que tome ciência.

#2 – Testes em Solidity
A segunda maior diferença após a performance é a possibilidade de você poder escrever testes na linguagem Solidity, a mesma que você usa para a escrita dos smart contracts. O padrão utilizado para os testes é o mesmo do Forge (forge-std), utilizado no Foundry, o que permite uma transição mais fácil entre os dois toolkits para os desenvolvedores.
Neste padrão você cria um contrato de teste que herda de Test.sol, escrevendo os casos de teste como se fossem funções do contrato e interagindo com o contrato de implementação como se ele fosse um import comum. As asserções são feitas com requires, enquanto para algumas simulações, existe ainda um objeto global “vm”, que permite impersonar carteiras e outras funcionalidades.
Veja abaixo um exemplo de contrato que testa um CRUD de livros, mas com apenas uma função para ilustrar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import {BookDatabase} from "./BookDatabase.sol"; import {Test} from "forge-std/Test.sol"; contract BookDatabaseTest is Test { BookDatabase bookDatabase; function setUp() public { bookDatabase = new BookDatabase(); } function test_addBook() public { bookDatabase.addBook( BookDatabase.Book({ title: "Criando apps para empresas com Android", year: 2015 }) ); require(bookDatabase.count() == 1, "Count should be 1"); } } |
Repare como temos uma função de SetUp, que instancia o contrato de implementação e depois a função do teste em si, que usa a instância para cadastrar um livro e fzer a verificação se teve sucesso, usando uma outra função count.
O HardHat v3 não obriga essa mudança, que você tenha de deixar de escrever testes em TypeScript e vá para Solidity. Você pode usar um, outro ou ambos, bastando um único comando npx hardhat test para rodar todos os testes ao mesmo tempo, tanto Solidity quanto TS. Como eu venho do mundo JS, particularmente acho mais prático e fácil escrever os testes em TypeScript mesmo, pelas próprias características das linguagens (testar strings é um saco no Solidity, por exemplo), mas entendo que existem cenários de teste que podem ser interessante escrever em Solidity, principalmente aqueles envolvendo segurança e de contratos que serão chamados por outros contratos.

#3 – Testes em TypeScript
Algumas mudanças de sintaxe também podem ser observadas nos testes em TypeScript. Nada absurdo, mas que vale citar para que você consiga migrar seus projetos mais facilmente (falarei mais sobre migração ao final).
Antes, você escrevia um teste assim (exemplo de código de uma coleção NFT):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; describe("MyNFT", () => { async function deployFixture() { const [owner, otherAccount, oneMoreAccount] = await ethers.getSigners(); const MyNFT = await ethers.getContractFactory("MyNFT"); const myNFT = await MyNFT.deploy(); return { myNFT, owner, otherAccount, oneMoreAccount }; } it("Should has the correct name", async () => { const { myNFT } = await loadFixture(deployFixture); const name = await myNFT.name() as string; expect(name).to.equal("MyNFT", "The name is wrong"); }); }); |
Agora, com HardHat v3, os imports e preparação mudam para:
1 2 3 4 5 6 7 |
import { expect } from "chai"; import { network } from "hardhat"; const { ethers } = await network.connect(); const [owner, otherAccount, oneMoreAccount] = await ethers.getSigners(); |
Não se utiliza mais uma função deployFixture e a instância da ethers é obtida através da instâcia da HardHat Network. Avançando para a escrita de testes em si, a única mudança é o fato de não usarmos mais o loadFixture, mas ao inv’rs disso, mandamos fazer o deploy do contrato (o que será feito na HardHat Network). Exemplo abaixo de uma bateria de testes de um contrato CRUD de livros:
1 2 3 4 5 6 7 8 9 10 11 12 |
it("Should add book", async () => { const bookDatabase = await ethers.deployContract("BookDatabase"); await bookDatabase.addBook({ title: "Criando apps para empresas com Android", year: 2015 }); expect(await bookDatabase.count()).to.equal(1); }); |
Não notei até o momento outras mudanças significativas nos testes em TypeScript.
#4 – HardHat Config
Outras mudanças dignas de nota podem ser encontradas no arquivo de configuração do projeto, o HardHat.config.ts. No HardHat v2 você teria algo como:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; import 'dotenv/config'; const config: HardhatUserConfig = { solidity: { version: "0.8.28", settings: { optimizer: { enabled: true, runs: 200 } } }, networks: { bsctest: { url: process.env.NODE_URL, chainId: parseInt(`${process.env.CHAIN_ID}`), accounts: { mnemonic: process.env.MNEMONIC } } }, etherscan: { apiKey: process.env.API_KEY } }; export default config; |
Enquanto que no HardHat v3, você terá como equivalente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import type { HardhatUserConfig } from "hardhat/config"; import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; import "dotenv/config"; const config: HardhatUserConfig = { plugins: [hardhatToolboxMochaEthersPlugin], solidity: { profiles: { default: { version: "0.8.28", }, production: { version: "0.8.28", settings: { optimizer: { enabled: true, runs: 200, }, }, }, }, }, networks: { bsctest: { type: "http", chainType: "l1", url: `${process.env.NODE_URL}`, chainId: parseInt(`${process.env.CHAIN_ID}`), accounts: [`${process.env.PRIVATE_KEY}`] } }, verify: { etherscan: { apiKey: process.env.API_KEY } } }; export default config; |
Note que como várias estruturas de configuração mudaram, com a propriedade solidity, responsável por configurar a compilação, ficando mais complexa, a networks exigindo chave privada de cada conta e o tipo de nó RPC (type e chainType) e por fim a propriedade verify, necessária para fazer verificação de contratos, ficando mais estruturada também.

#5 – Como migrar do HardHat v2 para HardHat v3
Devido a essas mudanças que citei, principalmente nos testes TypeScript e no arquivo de configuração que eu NÃO recomendo pegar um projeto HardHat v2 funcional e apenas tentar atualizar ele mexendo no package.json e alterando demais arquivos um a um. O que eu recomendo SIM, para os casos em que você deseje mudar de HardHat v2 para v3 em projetos antigos é:
- crie um novo projeto HardHat v3;
- traga os contratos Solidity do antigo para o novo;
- faça o setup inicial dos testes TypeScript usando o padrão HH v3;
- traga os “its” dos testes do projeto antigo para o novo, mudando a instrução inicial para fazer deploy do contrato seguindo o novo padrão;
- traga as configurações do hardhat.config.ts antigo para o novo, uma a uma, respeitando as mudanças de estrutura;
- rode todos os testes para ver se não quebrou nada;
Assim eu acredito que você não terá tantos problemas e em caso de ter de voltar atrás, por qualquer motivo (HH v3 ainda está em beta enquanto escrevo este artigo), terá seu projeto anterior intacto. Informações oficiais sobre migração da v2 para v3 aqui.
Então é isso, espero que o artigo tenha lhe ajudado e querendo ver na prática um passo a passo usando HH v3, recomendo este tutorial.
Até a próxima!

Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.