zip-hardhat.ts•6.84 kB
import JSZip from 'jszip';
import type { GenericOptions } from './build-generic';
import type { Contract } from './contract';
import { printContract } from './print';
import SOLIDITY_VERSION from './solidity-version.json';
import type { Lines } from './utils/format-lines';
import { formatLinesWithSpaces, spaceBetween } from './utils/format-lines';
const hardhatConfig = (upgradeable: boolean) => `\
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ''}
const config: HardhatUserConfig = {
solidity: {
version: "${SOLIDITY_VERSION}",
settings: {
optimizer: {
enabled: true,
},
},
},
};
export default config;
`;
const tsConfig = `\
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
`;
const gitIgnore = `\
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts
`;
const test = (c: Contract, opts?: GenericOptions) => {
return formatLinesWithSpaces(2, ...spaceBetween(getImports(c), getTestCase(c)));
function getTestCase(c: Contract) {
const args = getAddressArgs(c);
return [
`describe("${c.name}", function () {`,
[
'it("Test contract", async function () {',
spaceBetween(
[`const ContractFactory = await ethers.getContractFactory("${c.name}");`],
getAddressVariables(args),
[`const instance = await ${getDeploymentCall(c, args)};`, 'await instance.waitForDeployment();'],
getExpects(),
),
'});',
],
'});',
];
}
function getImports(c: Contract) {
return ['import { expect } from "chai";', `import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";`];
}
function getExpects(): Lines[] {
if (opts !== undefined) {
switch (opts.kind) {
case 'ERC20':
case 'ERC721':
return [`expect(await instance.name()).to.equal("${opts.name}");`];
case 'ERC1155':
return [`expect(await instance.uri(0)).to.equal("${opts.uri}");`];
case 'Account':
case 'Governor':
case 'Custom':
break;
default:
throw new Error('Unknown ERC');
}
}
return [];
}
function getAddressVariables(args: string[]): Lines[] {
const vars = [];
for (let i = 0; i < args.length; i++) {
vars.push(`const ${args[i]} = (await ethers.getSigners())[${i}].address;`);
}
return vars;
}
};
function getAddressArgs(c: Contract): string[] {
const args = [];
for (const constructorArg of c.constructorArgs) {
if (constructorArg.type === 'address') {
args.push(constructorArg.name);
}
}
return args;
}
function getDeploymentCall(c: Contract, args: string[]): string {
// TODO: remove that selector when the upgrades plugin supports @custom:oz-upgrades-unsafe-allow-reachable
const unsafeAllowConstructor = c.parents.find(p => ['EIP712'].includes(p.contract.name)) !== undefined;
return !c.upgradeable
? `ContractFactory.deploy(${args.join(', ')})`
: unsafeAllowConstructor
? `upgrades.deployProxy(ContractFactory, [${args.join(', ')}], { unsafeAllow: 'constructor' })`
: `upgrades.deployProxy(ContractFactory, [${args.join(', ')}])`;
}
const script = (c: Contract) => {
const args = getAddressArgs(c);
return `\
import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";
async function main() {
const ContractFactory = await ethers.getContractFactory("${c.name}");
${args.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''}
const instance = await ${getDeploymentCall(c, args)};
await instance.waitForDeployment();
console.log(\`${c.upgradeable ? 'Proxy' : 'Contract'} deployed to \${await instance.getAddress()}\`);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
`;
};
const lowerFirstCharacter = (str: string) => str.charAt(0).toLowerCase() + str.slice(1);
const ignitionModule = (c: Contract) => {
const deployArguments = getAddressArgs(c);
const contractVariableName = lowerFirstCharacter(c.name);
return `import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("${c.name}Module", (m) => {
${deployArguments.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''}
const ${contractVariableName} = m.contract("${c.name}", [${deployArguments.join(', ')}]);
return { ${contractVariableName} };
});
`;
};
const readme = (c: Contract) => `\
# Sample Hardhat Project
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'}.
## Installing dependencies
\`\`\`
npm install
\`\`\`
## Testing the contract
\`\`\`
npm test
\`\`\`
## Deploying the contract
You can target any network from your Hardhat config using:
\`\`\`
${c.upgradeable ? 'npx hardhat run --network <network-name> scripts/deploy.ts' : `npx hardhat ignition deploy ignition/modules/${c.name}.ts --network <network-name>`}
\`\`\`
`;
function getHardhatPlugins(c: Contract) {
const plugins = ['ethers'];
if (c.upgradeable) {
plugins.push('upgrades');
}
return plugins;
}
export async function zipHardhat(c: Contract, opts?: GenericOptions) {
const zip = new JSZip();
const { default: packageJson } = c.upgradeable
? await import('./environments/hardhat/upgradeable/package.json')
: await import('./environments/hardhat/package.json');
packageJson.license = c.license;
const { default: packageLock } = c.upgradeable
? await import('./environments/hardhat/upgradeable/package-lock.json')
: await import('./environments/hardhat/package-lock.json');
packageLock.packages[''].license = c.license;
zip.file(`contracts/${c.name}.sol`, printContract(c));
zip.file('test/test.ts', test(c, opts));
if (c.upgradeable) {
zip.file('scripts/deploy.ts', script(c));
} else {
zip.file(`ignition/modules/${c.name}.ts`, ignitionModule(c));
}
zip.file('.gitignore', gitIgnore);
zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable));
zip.file('package.json', JSON.stringify(packageJson, null, 2));
zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2));
zip.file('README.md', readme(c));
zip.file('tsconfig.json', tsConfig);
return zip;
}