Skip to content

Commit 2f6b063

Browse files
zoeyTMalcuadrado
authored andcommitted
bring migration guide to docs
1 parent 221f640 commit 2f6b063

File tree

2 files changed

+383
-0
lines changed

2 files changed

+383
-0
lines changed

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

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

0 commit comments

Comments
 (0)