endstep2
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: hardhat-toolbox-mocha-ethers
|
||||
description: Use alongside the `hardhat` skill when the project depends on `@nomicfoundation/hardhat-toolbox-mocha-ethers`. Covers the ethers helpers exposed on `network.create()`, contract interaction (`ethers.deployContract`, calling functions, `connect`), TypeChain-typed contract instances, and the chai matchers from `@nomicfoundation/hardhat-ethers-chai-matchers` (`.to.emit`, `.to.be.revertedWith*`, `.to.changeEtherBalance(s)`).
|
||||
metadata:
|
||||
package: "@nomicfoundation/hardhat-toolbox-mocha-ethers"
|
||||
---
|
||||
|
||||
# Hardhat toolbox: ethers + Mocha
|
||||
|
||||
This skill builds on the core **`hardhat`** skill. Load that first for test organization, the `network.create()` shape, `networkHelpers`, fixtures, and the typechecking workflow. Everything below hangs off the connection returned by `network.create()`.
|
||||
|
||||
This stack runs tests under **Mocha** (global `describe` / `it`) and asserts with **chai**. Mocha doesn't await `describe` callbacks, so the connection is set up at the top of the file with top-level `await`:
|
||||
|
||||
```ts
|
||||
import { expect } from "chai";
|
||||
import { network } from "hardhat";
|
||||
|
||||
const { ethers, networkHelpers } = await network.create();
|
||||
|
||||
describe("Counter", function () {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
One connection per file is the norm. State isolation between tests comes from `loadFixture` (see the `hardhat` skill), not from creating a fresh connection per `describe`.
|
||||
|
||||
## `ethers`: signers and contract interaction
|
||||
|
||||
Contract types are generated by **TypeChain** from the compiled ABI. Run `hardhat build` before typechecking so the types reflect the latest contracts.
|
||||
|
||||
```ts
|
||||
// Provider (read-only chain data)
|
||||
const blockNumber = await ethers.provider.getBlockNumber();
|
||||
const balance = await ethers.provider.getBalance("0xabc...");
|
||||
|
||||
// Signers (for sending transactions)
|
||||
const [owner, alice, bob] = await ethers.getSigners(); // default funded accounts
|
||||
|
||||
// Utilities
|
||||
const oneEth = ethers.parseEther("1.0");
|
||||
|
||||
// Deploy a contract, returns a fully typed instance.
|
||||
// Pass constructor args as the second argument, and (optionally) a signer
|
||||
// other than the default as the third.
|
||||
const counter = await ethers.deployContract("Counter");
|
||||
const counterWithArgs = await ethers.deployContract("Counter", [42n]);
|
||||
const counterFromAlice = await ethers.deployContract("Counter", [], alice);
|
||||
|
||||
// Read state and call functions. Args and return types are type-checked
|
||||
// against the ABI at compile time; passing wrong types or wrong argument
|
||||
// counts is a TypeScript error.
|
||||
const value = await counter.x();
|
||||
await counter.inc();
|
||||
await counter.incBy(3n);
|
||||
|
||||
// Send from a different signer
|
||||
await counter.connect(alice).inc();
|
||||
|
||||
// Send ETH along with a payable call
|
||||
await counter.deposit({ value: oneEth });
|
||||
|
||||
// Attach to an already-deployed contract
|
||||
const existing = await ethers.getContractAt("Counter", "0xabc...");
|
||||
```
|
||||
|
||||
Inside a `loadFixture` setup function (see the `hardhat` skill for the surrounding pattern), `ethers.deployContract` is the canonical deploy step:
|
||||
|
||||
```ts
|
||||
async function deployCounterFixture() {
|
||||
const counter = await ethers.deployContract("Counter");
|
||||
return { counter };
|
||||
}
|
||||
|
||||
const { counter } = await networkHelpers.loadFixture(deployCounterFixture);
|
||||
```
|
||||
|
||||
To send a transaction from an arbitrary address (not one of the default-funded accounts), fund it for gas and pull an impersonating signer with `ethers.getImpersonatedSigner` — it handles the impersonation internally, no separate `networkHelpers.impersonateAccount` call needed:
|
||||
|
||||
```ts
|
||||
const addr = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
await networkHelpers.setBalance(addr, ethers.parseEther("1.0"));
|
||||
const signer = await ethers.getImpersonatedSigner(addr);
|
||||
await counter.connect(signer).inc();
|
||||
```
|
||||
|
||||
## Chai matchers: Ethereum-specific assertions
|
||||
|
||||
The `hardhat-ethers-chai-matchers` plugin extends chai's `expect` with matchers for transactions, events, and balance changes. Pass the **transaction promise** (the result of a write call, unawaited) into `expect`:
|
||||
|
||||
```ts
|
||||
import { expect } from "chai";
|
||||
|
||||
// Reverts
|
||||
await expect(counter.connect(banned).inc()).to.revert(ethers);
|
||||
await expect(counter.connect(banned).inc()).to.be.revertedWith(
|
||||
"only the owner can increment the counter",
|
||||
);
|
||||
await expect(counter.connect(banned).inc()).to.be.revertedWithCustomError(
|
||||
counter,
|
||||
"Unauthorized",
|
||||
);
|
||||
await expect(counter.connect(banned).inc())
|
||||
.to.be.revertedWithCustomError(counter, "Unauthorized")
|
||||
.withArgs(banned.address);
|
||||
|
||||
// Events
|
||||
await expect(counter.inc()).to.emit(counter, "Increment");
|
||||
await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n);
|
||||
|
||||
// ETH balance changes (positive = received, negative = spent, before gas)
|
||||
await expect(game.claim()).to.changeEtherBalance(ethers, winner, PRIZE);
|
||||
await expect(game.claim()).to.changeEtherBalances(
|
||||
ethers,
|
||||
[winner, loser],
|
||||
[PRIZE, -STAKE],
|
||||
);
|
||||
```
|
||||
|
||||
For plain assertions (equality, arrays, types), use `expect` from chai directly without the Ethereum matchers.
|
||||
Reference in New Issue
Block a user