Skip to content

Commit df48f26

Browse files
authored
Bring hardhat-deploy to Ignition migration guide to docs (#5211)
1 parent 6213c9e commit df48f26

File tree

2 files changed

+377
-0
lines changed

2 files changed

+377
-0
lines changed

docs/src/content/ignition/docs/advanced/_dirinfo.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ order:
55
- /deployment-artifacts
66
- /reconciliation
77
- /versioning
8+
- /migrating
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
# Migrating from hardhat-deploy
2+
3+
This guide will walk you through migrating your Hardhat project to Hardhat Ignition from `hardhat-deploy`, a community plugin for deploying smart contracts used within the Hardhat community.
4+
5+
## Installing Hardhat Ignition
6+
7+
To get started, we’ll uninstall the `hardhat-deploy` plugin and install the Hardhat Ignition one by executing the following steps:
8+
9+
1. Remove the `hardhat-deploy` packages from your project:
10+
11+
::::tabsgroup{options="npm,yarn"}
12+
13+
:::tab{value="npm"}
14+
15+
```sh
16+
npm uninstall hardhat-deploy hardhat-deploy-ethers
17+
```
18+
19+
:::
20+
21+
:::tab{value=yarn}
22+
23+
```sh
24+
yarn remove hardhat-deploy hardhat-deploy-ethers
25+
```
26+
27+
:::
28+
29+
::::
30+
31+
2. Install the Hardhat Ignition package and `hardhat-network-helpers` to provide additional testing support as a replacement for `hardhat-deploy` functionality like EVM snapshots:
32+
33+
::::tabsgroup{options="npm,yarn"}
34+
35+
:::tab{value="npm"}
36+
37+
```sh
38+
npm install --save-dev @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-network-helpers
39+
```
40+
41+
:::
42+
43+
:::tab{value=yarn}
44+
45+
```sh
46+
yarn add --dev @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-network-helpers
47+
```
48+
49+
:::
50+
51+
::::
52+
53+
3. Update the project’s `hardhat.config` file to remove `hardhat-deploy` and `hardhat-deploy-ethers` and instead import Hardhat Ignition:
54+
55+
::::tabsgroup{options="typescript,javascript"}
56+
57+
:::tab{value="typescript"}
58+
59+
```git
60+
- import "hardhat-deploy";
61+
- import "hardhat-deploy-ethers";
62+
+ import "@nomicfoundation/hardhat-ignition-ethers";
63+
```
64+
65+
:::
66+
67+
:::tab{value=javascript}
68+
69+
```git
70+
- require("hardhat-deploy");
71+
- require("hardhat-deploy-ethers");
72+
+ require("@nomicfoundation/hardhat-ignition-ethers");
73+
```
74+
75+
:::
76+
77+
::::
78+
79+
## Convert deployment scripts to Ignition Modules
80+
81+
`hardhat-deploy` represents contract deployments as JavaScript or TypeScript files under the `./deploy/` folder. Hardhat Ignition follows a similar pattern with deployments encapsulated as modules; these are JS/TS files stored under the `./ignition/modules directory`. Each `hardhat-deploy` deploy file will be converted or merged into a Hardhat Ignition module.
82+
83+
Let’s first create the required folder structure under the root of your project:
84+
85+
```sh
86+
mkdir ignition
87+
mkdir ignition/modules
88+
```
89+
90+
Now, let’s work through converting a simple `hardhat-deploy` script for this example `Token` contract:
91+
92+
```solidity
93+
// SPDX-License-Identifier: MIT
94+
pragma solidity ^0.8.19;
95+
96+
contract Token {
97+
uint256 public totalSupply = 1000000;
98+
address public owner;
99+
mapping(address => uint256) balances;
100+
constructor(address _owner) {
101+
balances[_owner] = totalSupply;
102+
owner = _owner;
103+
}
104+
function balanceOf(address account) external view returns (uint256) {
105+
return balances[account];
106+
}
107+
function transfer(address to, uint256 amount) external {
108+
require(balances[msg.sender] >= amount, "Not enough tokens");
109+
balances[msg.sender] -= amount;
110+
balances[to] += amount;
111+
}
112+
}
113+
```
114+
115+
A `hardhat-deploy` deploy function for the Token contract might look like this:
116+
117+
```typescript
118+
// ./deploy/001_deploy_token.ts
119+
import { HardhatRuntimeEnvironment } from "hardhat/types";
120+
import { DeployFunction } from "hardhat-deploy/types";
121+
122+
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
123+
const { deployments, getNamedAccounts } = hre;
124+
const { deploy } = deployments;
125+
/*
126+
The deploy function uses the hardhat-deploy named accounts feature
127+
to set the deployment's `from` and `args` parameters.
128+
*/
129+
const { deployer, tokenOwner } = await getNamedAccounts();
130+
await deploy("Token", {
131+
from: deployer,
132+
args: [tokenOwner],
133+
log: true,
134+
});
135+
};
136+
export default func;
137+
```
138+
139+
Using an Ignition Module, the equivalent account access code would look like this:
140+
141+
::::tabsgroup{options="typescript,javascript"}
142+
143+
:::tab{value="typescript"}
144+
145+
```typescript
146+
// ./ignition/modules/Token.ts
147+
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
148+
149+
/*
150+
The callback passed to `buildModule()` provides a module builder object `m`
151+
as a parameter. Through this builder object, you access the Module API.
152+
For instance, you can deploy contracts via `m.contract()`.
153+
*/
154+
export default buildModule("TokenModule", (m) => {
155+
/*
156+
Instead of named accounts, you get access to the configured accounts
157+
through the `getAccount()` method.
158+
*/
159+
const deployer = m.getAccount(0);
160+
const tokenOwner = m.getAccount(1);
161+
162+
/*
163+
Deploy `Token` by calling `contract()` with the constructor arguments
164+
as the second argument. The account to use for the deployment transaction
165+
is set through `from` in the third argument, which is an options object.
166+
*/
167+
const token = m.contract("Token", [tokenOwner], {
168+
from: deployer,
169+
});
170+
171+
/*
172+
The call to `m.contract()` returns a future that can be used in other `m.contract()`
173+
calls (e.g. as a constructor argument, where the future will resolve to the
174+
deployed address), but it can also be returned from the module. Contract
175+
futures that are returned from the module can be leveraged in Hardhat tests
176+
and scripts, as will be shown later.
177+
*/
178+
return { token };
179+
});
180+
```
181+
182+
:::
183+
184+
:::tab{value=javascript}
185+
186+
```javascript
187+
// ./ignition/modules/Token.js
188+
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
189+
190+
/*
191+
The callback passed to `buildModule()` provides a module builder object `m`
192+
as a parameter. Through this builder object, you access the Module API.
193+
For instance, you can deploy contracts via `m.contract()`.
194+
*/
195+
module.exports = buildModule("TokenModule", (m) => {
196+
/*
197+
Instead of named accounts, you get access to the configured accounts
198+
through the `getAccount()` method.
199+
*/
200+
const deployer = m.getAccount(0);
201+
const tokenOwner = m.getAccount(1);
202+
203+
/*
204+
Deploy `Token` by calling `contract()` with the constructor arguments
205+
as the second argument. The account to use for the deployment transaction
206+
is set through `from` in the third argument, which is an options object.
207+
*/
208+
const token = m.contract("Token", [tokenOwner], {
209+
from: deployer,
210+
});
211+
212+
/*
213+
The call to `m.contract()` returns a future that can be used in other `m.contract()`
214+
calls (e.g. as a constructor argument, where the future will resolve to the
215+
deployed address), but it can also be returned from the module. Contract
216+
futures that are returned from the module can be leveraged in Hardhat tests
217+
and scripts, as will be shown later.
218+
*/
219+
return { token };
220+
});
221+
```
222+
223+
:::
224+
225+
::::
226+
227+
The conversion to an Ignition module can be tested by running the module against Hardhat Network:
228+
229+
```sh
230+
npx hardhat ignition deploy ./ignition/modules/Token.ts
231+
```
232+
233+
Which, if working correctly, will output the contract’s deployed address:
234+
235+
```sh
236+
You are running Hardhat Ignition against an in-process instance of Hardhat Network.
237+
This will execute the deployment, but the results will be lost.
238+
You can use --network <network-name> to deploy to a different network.
239+
240+
Hardhat Ignition 🚀
241+
242+
Deploying [ TokenModule ]
243+
244+
Batch #1
245+
Executed Token#Token
246+
247+
[ Token ] successfully deployed 🚀
248+
249+
Deployed Addresses
250+
Token#Token - 0x5FbDB2315678afecb367f032d93F642f64180aa3
251+
```
252+
253+
To learn more, check out the detailed [guide on writing Hardhat Ignition modules](/ignition/docs/guides/creating-modules), which showcases all available features.
254+
255+
## Migrating tests that rely on hardhat-deploy fixtures
256+
257+
Let’s go over the process of rewriting Hardhat tests that rely on `hardhat-deploy` fixture functionality. Using `hardhat-deploy`, calls to `fixture()` deploy everything under the `./deploy` and create a snapshot in the in-memory Hardhat node at the end of the first run. Subsequent calls to `fixture()` revert to the saved snapshot, avoiding rerunning the deployment transactions and thus saving time.
258+
259+
To do this, `hardhat-deploy-ethers` enhances the Hardhat `ethers` object with a `getContract` method that will return contract instances from the fixture snapshot.
260+
261+
```typescript
262+
import { expect } from "chai";
263+
import { Contract } from "ethers";
264+
import { ethers, deployments, getNamedAccounts } from "hardhat";
265+
266+
describe("Token contract", function () {
267+
it("should assign the total supply of tokens to the owner", async function () {
268+
// Create fixture snapshot
269+
await deployments.fixture();
270+
271+
// This will get an instance from the snapshot
272+
const token: Contract = await ethers.getContract("Token");
273+
274+
const { tokenOwner } = await getNamedAccounts();
275+
expect(await token.balanceOf(tokenOwner)).to.equal(
276+
await token.totalSupply()
277+
);
278+
});
279+
});
280+
```
281+
282+
Hardhat Ignition, in conjunction with the `hardhat-network-helpers` plugin, also allows you to use fixtures:
283+
284+
::::tabsgroup{options="typescript,javascript"}
285+
286+
:::tab{value="typescript"}
287+
288+
```typescript
289+
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
290+
import { expect } from "chai";
291+
import { ethers, ignition } from "hardhat";
292+
import TokenModule from "../ignition/modules/Token";
293+
294+
describe("Token contract", function () {
295+
async function deployTokenFixture() {
296+
/*
297+
Hardhat Ignition adds an `ignition` object to the Hardhat Runtime Environment
298+
that exposes a `deploy()` method. The `deploy()` method takes an Ignition
299+
module and returns the results of the Ignition module, where each
300+
returned future has been converted into an *ethers* contract instance.
301+
*/
302+
const { token } = await ignition.deploy(TokenModule);
303+
304+
return { token };
305+
}
306+
307+
it("should assign the total supply of tokens to the owner", async function () {
308+
/*
309+
The snapshot feature of `hardhat-deploy` fixtures is replicated
310+
by the call to the `hardhat-network-helpers` function `loadFixture()`.
311+
For a given fixture function, `loadFixture()` will snapshot the in-memory
312+
Hardhat node, and will revert to the snapshot if called with the same
313+
function again.
314+
*/
315+
const { token } = await loadFixture(deployTokenFixture);
316+
317+
const [, tokenOwner] = await ethers.getSigners();
318+
expect(await token.balanceOf(tokenOwner)).to.equal(
319+
await token.totalSupply()
320+
);
321+
});
322+
});
323+
```
324+
325+
:::
326+
327+
:::tab{value=javascript}
328+
329+
```javascript
330+
const {
331+
loadFixture,
332+
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
333+
const { expect } = require("chai");
334+
const { ethers, ignition } = require("hardhat");
335+
const TokenModule = require("../ignition/modules/Token");
336+
337+
describe("Token contract", function () {
338+
async function deployTokenFixture() {
339+
/*
340+
Hardhat Ignition adds an `ignition` object to the Hardhat Runtime Environment
341+
that exposes a `deploy()` method. The `deploy()` method takes an Ignition
342+
module and returns the results of the Ignition module, where each
343+
returned future has been converted into an *ethers* contract instance.
344+
*/
345+
const { token } = await ignition.deploy(TokenModule);
346+
347+
return { token };
348+
}
349+
350+
it("should assign the total supply of tokens to the owner", async function () {
351+
/*
352+
The snapshot feature of `hardhat-deploy` fixtures is replicated
353+
by the call to the `hardhat-network-helpers` function `loadFixture()`.
354+
For a given fixture function, `loadFixture()` will snapshot the in-memory
355+
Hardhat node, and will revert to the snapshot if called with the same
356+
function again.
357+
*/
358+
const { token } = await loadFixture(deployTokenFixture);
359+
360+
const [, tokenOwner] = await ethers.getSigners();
361+
expect(await token.balanceOf(tokenOwner)).to.equal(
362+
await token.totalSupply()
363+
);
364+
});
365+
});
366+
```
367+
368+
:::
369+
370+
::::
371+
372+
Once converted, tests can be checked in the standard way:
373+
374+
```sh
375+
npx hardhat test
376+
```

0 commit comments

Comments
 (0)