Skip to content

Commit 849652d

Browse files
CoveMBericglau
andauthored
Hardhat zip deploy with ignition (#556)
Co-authored-by: Eric Lau <[email protected]>
1 parent 9209725 commit 849652d

File tree

4 files changed

+119
-76
lines changed

4 files changed

+119
-76
lines changed

packages/core/solidity/src/zip-hardhat.test.ts

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ test.serial('erc20 full', async t => {
4747
flashmint: true,
4848
};
4949
const c = buildERC20(opts);
50-
await runTest(c, t, opts);
50+
await runIgnitionTest(c, t, opts);
5151
});
5252

5353
test.serial('erc721 upgradeable', async t => {
@@ -58,7 +58,7 @@ test.serial('erc721 upgradeable', async t => {
5858
upgradeable: 'uups',
5959
};
6060
const c = buildERC721(opts);
61-
await runTest(c, t, opts);
61+
await runDeployScriptTest(c, t, opts);
6262
});
6363

6464
test.serial('erc1155 basic', async t => {
@@ -68,13 +68,13 @@ test.serial('erc1155 basic', async t => {
6868
uri: 'https://myuri/{id}',
6969
};
7070
const c = buildERC1155(opts);
71-
await runTest(c, t, opts);
71+
await runIgnitionTest(c, t, opts);
7272
});
7373

7474
test.serial('custom basic', async t => {
7575
const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' };
7676
const c = buildCustom(opts);
77-
await runTest(c, t, opts);
77+
await runIgnitionTest(c, t, opts);
7878
});
7979

8080
test.serial('custom upgradeable', async t => {
@@ -84,18 +84,26 @@ test.serial('custom upgradeable', async t => {
8484
upgradeable: 'transparent',
8585
};
8686
const c = buildCustom(opts);
87-
await runTest(c, t, opts);
87+
await runDeployScriptTest(c, t, opts);
8888
});
8989

90-
async function runTest(c: Contract, t: ExecutionContext<Context>, opts: GenericOptions) {
90+
async function runDeployScriptTest(c: Contract, t: ExecutionContext<Context>, opts: GenericOptions) {
9191
const zip = await zipHardhat(c, opts);
9292

93-
assertLayout(zip, c, t);
94-
await extractAndRunPackage(zip, c, t);
95-
await assertContents(zip, c, t);
93+
assertDeployScriptLayout(zip, c, t);
94+
await extractAndRunDeployScriptPackage(zip, c, t);
95+
await assertDeployScriptContents(zip, c, t);
9696
}
9797

98-
function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
98+
async function runIgnitionTest(c: Contract, t: ExecutionContext<Context>, opts: GenericOptions) {
99+
const zip = await zipHardhat(c, opts);
100+
101+
assertIgnitionLayout(zip, c, t);
102+
await extractAndRunIgnitionPackage(zip, c, t);
103+
await assertIgnitionContents(zip, c, t);
104+
}
105+
106+
function assertDeployScriptLayout(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
99107
const sorted = Object.values(zip.files)
100108
.map(f => f.name)
101109
.sort();
@@ -115,36 +123,62 @@ function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
115123
]);
116124
}
117125

118-
async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
119-
const files = Object.values(zip.files);
126+
function assertIgnitionLayout(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
127+
const sorted = Object.values(zip.files)
128+
.map(f => f.name)
129+
.sort();
130+
t.deepEqual(sorted, [
131+
'.gitignore',
132+
'README.md',
133+
'contracts/',
134+
`contracts/${c.name}.sol`,
135+
'hardhat.config.ts',
136+
'ignition/',
137+
'ignition/modules/',
138+
`ignition/modules/${c.name}.ts`,
139+
'package-lock.json',
140+
'package.json',
141+
'test/',
142+
'test/test.ts',
143+
'tsconfig.json',
144+
]);
145+
}
146+
147+
function extractAndRun(makeDeployCommand: (c: Contract) => string | null) {
148+
return async (zip: JSZip, c: Contract, t: ExecutionContext<Context>) => {
149+
const files = Object.values(zip.files);
120150

121-
const tempFolder = t.context.tempFolder;
151+
const tempFolder = t.context.tempFolder;
122152

123-
const items = Object.values(files);
124-
for (const item of items) {
125-
if (item.dir) {
126-
await fs.mkdir(path.join(tempFolder, item.name));
127-
} else {
128-
await fs.writeFile(path.join(tempFolder, item.name), await asString(item));
153+
const items = Object.values(files);
154+
for (const item of items) {
155+
if (item.dir) {
156+
await fs.mkdir(path.join(tempFolder, item.name));
157+
} else {
158+
await fs.writeFile(path.join(tempFolder, item.name), await asString(item));
159+
}
129160
}
130-
}
131161

132-
let command = `cd "${tempFolder}" && npm install && npm test`;
133-
if (c.constructorArgs === undefined) {
134-
// only test deploying the contract if there are no constructor args needed
135-
command += ' && npx hardhat run scripts/deploy.ts';
136-
}
162+
let command = `cd "${tempFolder}" && npm install && npm test`;
163+
if (c.constructorArgs === undefined) {
164+
// only test deploying the contract if there are no constructor args needed
165+
command += ` && ${makeDeployCommand(c)}`;
166+
}
137167

138-
const exec = util.promisify(child.exec);
139-
const result = await exec(command);
168+
const exec = util.promisify(child.exec);
169+
const result = await exec(command);
140170

141-
t.regex(result.stdout, /1 passing/);
142-
if (c.constructorArgs === undefined) {
143-
t.regex(result.stdout, /deployed to/);
144-
}
171+
t.regex(result.stdout, /1 passing/);
172+
if (c.constructorArgs === undefined) {
173+
t.regex(result.stdout, /deployed to/);
174+
}
175+
};
145176
}
146177

147-
async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
178+
const extractAndRunDeployScriptPackage = extractAndRun(() => 'npx hardhat run scripts/deploy.ts');
179+
const extractAndRunIgnitionPackage = extractAndRun(c => `npx hardhat ignition deploy ignition/modules/${c.name}.ts`);
180+
181+
async function assertDeployScriptContents(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
148182
const contentComparison = [
149183
await getItemString(zip, `contracts/${c.name}.sol`),
150184
await getItemString(zip, 'hardhat.config.ts'),
@@ -156,6 +190,18 @@ async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext<Conte
156190
t.snapshot(contentComparison);
157191
}
158192

193+
async function assertIgnitionContents(zip: JSZip, c: Contract, t: ExecutionContext<Context>) {
194+
const contentComparison = [
195+
await getItemString(zip, `contracts/${c.name}.sol`),
196+
await getItemString(zip, 'hardhat.config.ts'),
197+
await getItemString(zip, 'package.json'),
198+
await getItemString(zip, `ignition/modules/${c.name}.ts`),
199+
await getItemString(zip, 'test/test.ts'),
200+
];
201+
202+
t.snapshot(contentComparison);
203+
}
204+
159205
async function getItemString(zip: JSZip, key: string) {
160206
const obj = zip.files[key];
161207
if (obj === undefined) {

packages/core/solidity/src/zip-hardhat.test.ts.md

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,14 @@ Generated by [AVA](https://avajs.dev).
100100
"hardhat": "^2.16.1"␊
101101
}␊
102102
}`,
103-
`import { ethers } from "hardhat";␊
103+
`import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";␊
104104
105-
async function main() {␊
106-
const ContractFactory = await ethers.getContractFactory("MyToken");␊
105+
export default buildModule("MyTokenModule", (m) => {␊
107106
108107
// TODO: Set addresses for the contract arguments below␊
109-
const instance = await ContractFactory.deploy(recipient, defaultAdmin, pauser, minter);␊
110-
await instance.waitForDeployment();␊
111-
112-
console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊
113-
}␊
108+
const myToken = m.contract("MyToken", [recipient, defaultAdmin, pauser, minter]);␊
114109
115-
// We recommend this pattern to be able to use async/await everywhere␊
116-
// and properly handle errors.␊
117-
main().catch((error) => {␊
118-
console.error(error);␊
119-
process.exitCode = 1;␊
110+
return { myToken };␊
120111
});␊
121112
`,
122113
`import { expect } from "chai";␊
@@ -301,23 +292,14 @@ Generated by [AVA](https://avajs.dev).
301292
"hardhat": "^2.16.1"␊
302293
}␊
303294
}`,
304-
`import { ethers } from "hardhat";␊
295+
`import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";␊
305296
306-
async function main() {␊
307-
const ContractFactory = await ethers.getContractFactory("MyToken");␊
297+
export default buildModule("MyTokenModule", (m) => {␊
308298
309299
// TODO: Set addresses for the contract arguments below␊
310-
const instance = await ContractFactory.deploy(initialOwner);␊
311-
await instance.waitForDeployment();␊
312-
313-
console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊
314-
}␊
300+
const myToken = m.contract("MyToken", [initialOwner]);␊
315301
316-
// We recommend this pattern to be able to use async/await everywhere␊
317-
// and properly handle errors.␊
318-
main().catch((error) => {␊
319-
console.error(error);␊
320-
process.exitCode = 1;␊
302+
return { myToken };␊
321303
});␊
322304
`,
323305
`import { expect } from "chai";␊
@@ -383,23 +365,14 @@ Generated by [AVA](https://avajs.dev).
383365
"hardhat": "^2.16.1"␊
384366
}␊
385367
}`,
386-
`import { ethers } from "hardhat";␊
368+
`import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";␊
387369
388-
async function main() {␊
389-
const ContractFactory = await ethers.getContractFactory("MyContract");␊
370+
export default buildModule("MyContractModule", (m) => {␊
390371
391372
392-
const instance = await ContractFactory.deploy();␊
393-
await instance.waitForDeployment();␊
373+
const myContract = m.contract("MyContract", []);␊
394374
395-
console.log(\`Contract deployed to ${await instance.getAddress()}\`);␊
396-
}␊
397-
398-
// We recommend this pattern to be able to use async/await everywhere␊
399-
// and properly handle errors.␊
400-
main().catch((error) => {␊
401-
console.error(error);␊
402-
process.exitCode = 1;␊
375+
return { myContract };␊
403376
});␊
404377
`,
405378
`import { expect } from "chai";␊
Binary file not shown.

packages/core/solidity/src/zip-hardhat.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,28 @@ main().catch((error) => {
147147
`;
148148
};
149149

150-
const readme = `\
150+
const lowerFirstCharacter = (str: string) => str.charAt(0).toLowerCase() + str.slice(1);
151+
152+
const ignitionModule = (c: Contract) => {
153+
const deployArguments = getAddressArgs(c);
154+
const contractVariableName = lowerFirstCharacter(c.name);
155+
156+
return `import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
157+
158+
export default buildModule("${c.name}Module", (m) => {
159+
160+
${deployArguments.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''}
161+
const ${contractVariableName} = m.contract("${c.name}", [${deployArguments.join(', ')}]);
162+
163+
return { ${contractVariableName} };
164+
});
165+
`;
166+
};
167+
168+
const readme = (c: Contract) => `\
151169
# Sample Hardhat Project
152170
153-
This project demonstrates a basic Hardhat use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a script that deploys that contract.
171+
This project demonstrates a basic Hardhat use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, ${c.upgradeable ? 'and a script that deploys that contract' : 'and a Hardhat Ignition module that deploys that contract'}.
154172
155173
## Installing dependencies
156174
@@ -169,7 +187,7 @@ npm test
169187
You can target any network from your Hardhat config using:
170188
171189
\`\`\`
172-
npx hardhat run --network <network-name> scripts/deploy.ts
190+
${c.upgradeable ? 'npx hardhat run --network <network-name> scripts/deploy.ts' : `npx hardhat ignition deploy ignition/modules/${c.name}.ts --network <network-name>`}
173191
\`\`\`
174192
`;
175193

@@ -196,12 +214,18 @@ export async function zipHardhat(c: Contract, opts?: GenericOptions) {
196214

197215
zip.file(`contracts/${c.name}.sol`, printContract(c));
198216
zip.file('test/test.ts', test(c, opts));
199-
zip.file('scripts/deploy.ts', script(c));
217+
218+
if (c.upgradeable) {
219+
zip.file('scripts/deploy.ts', script(c));
220+
} else {
221+
zip.file(`ignition/modules/${c.name}.ts`, ignitionModule(c));
222+
}
223+
200224
zip.file('.gitignore', gitIgnore);
201225
zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable));
202226
zip.file('package.json', JSON.stringify(packageJson, null, 2));
203227
zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2));
204-
zip.file('README.md', readme);
228+
zip.file('README.md', readme(c));
205229
zip.file('tsconfig.json', tsConfig);
206230

207231
return zip;

0 commit comments

Comments
 (0)