UNPKG

hardhat-contract-sizer

Version:

Output Solidity contract sizes with Hardhat

210 lines (174 loc) 6.19 kB
const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); const stripAnsi = require('strip-ansi'); const Table = require('cli-table3'); const { HardhatPluginError } = require('hardhat/plugins'); const { TASK_COMPILE, } = require('hardhat/builtin-tasks/task-names'); // see EIPs 170 and 3860 for more information // https://eips.ethereum.org/EIPS/eip-170 // https://eips.ethereum.org/EIPS/eip-3860 const DEPLOYED_SIZE_LIMIT = 24576; const INIT_SIZE_LIMIT = 49152; const UNITS = { 'B': 1, 'kB': 1000, 'KiB': 1024 }; task( 'size-contracts', 'Output the size of compiled contracts' ).addFlag( 'noCompile', 'Don\'t compile before running this task' ).setAction(async function (args, hre) { if (!args.noCompile) { await hre.run(TASK_COMPILE, { noSizeContracts: true }); } const config = hre.config.contractSizer; if (!UNITS[config.unit]) { throw new HardhatPluginError(`Invalid unit: ${ config.unit }`); } const formatSize = function (size) { const divisor = UNITS[config.unit]; return (size / divisor).toFixed(3); }; const outputData = []; const fullNames = await hre.artifacts.getAllFullyQualifiedNames(); const outputPath = path.resolve( hre.config.paths.cache, '.hardhat_contract_sizer_output.json' ); const previousSizes = {}; const previousInitSizes = {}; if (fs.existsSync(outputPath)) { const previousOutput = await fs.promises.readFile(outputPath); JSON.parse(previousOutput).forEach(function (el) { previousSizes[el.fullName] = el.deploySize; previousInitSizes[el.fullName] = el.initSize; }); } await Promise.all(fullNames.map(async function (fullName) { if (config.only.length && !config.only.some(m => fullName.match(m))) return; if (config.except.length && config.except.some(m => fullName.match(m))) return; const { deployedBytecode, bytecode } = await hre.artifacts.readArtifact(fullName); const deploySize = Buffer.from( deployedBytecode.replace(/__\$\w*\$__/g, '0'.repeat(40)).slice(2), 'hex' ).length; const initSize = Buffer.from( bytecode.replace(/__\$\w*\$__/g, '0'.repeat(40)).slice(2), 'hex' ).length; outputData.push({ fullName, displayName: config.disambiguatePaths ? fullName : fullName.split(':').pop(), deploySize, previousDeploySize: previousSizes[fullName] || null, initSize, previousInitSize: previousInitSizes[fullName] || null, }); })); if (config.alphaSort) { outputData.sort((a, b) => a.displayName.toUpperCase() > b.displayName.toUpperCase() ? 1 : -1); } else { outputData.sort((a, b) => a.deploySize - b.deploySize); } await fs.promises.writeFile(outputPath, JSON.stringify(outputData), { flag: 'w' }); const table = new Table({ style: { head: [], border: [], 'padding-left': 2, 'padding-right': 2 }, chars: { mid: '·', 'top-mid': '|', 'left-mid': ' ·', 'mid-mid': '|', 'right-mid': '·', left: ' |', 'top-left': ' ·', 'top-right': '·', 'bottom-left': ' ·', 'bottom-right': '·', middle: '·', top: '-', bottom: '-', 'bottom-mid': '|', }, }); const compiler = hre.config.solidity.compilers[0]; table.push([ { content: chalk.gray(`Solc version: ${compiler.version}`), }, { content: chalk.gray(`Optimizer enabled: ${compiler.settings.optimizer.enabled}`), }, { content: chalk.gray(`Runs: ${compiler.settings.optimizer.runs}`), }, ]); table.push([ { content: chalk.bold('Contract Name'), }, { content: chalk.bold(`Deployed size (${config.unit}) (change)`), }, { content: chalk.bold(`Initcode size (${config.unit}) (change)`), }, ]); let oversizedContracts = 0; for (let item of outputData) { if (item.deploySize === 0 && item.initSize === 0) { continue; } let deploySize = formatSize(item.deploySize); let initSize = formatSize(item.initSize); if (item.deploySize > DEPLOYED_SIZE_LIMIT || item.initSize > INIT_SIZE_LIMIT) { oversizedContracts++; } if (item.deploySize > DEPLOYED_SIZE_LIMIT) { deploySize = chalk.red.bold(deploySize); } else if (item.deploySize > DEPLOYED_SIZE_LIMIT * 0.9) { deploySize = chalk.yellow.bold(deploySize); } if (item.initSize > INIT_SIZE_LIMIT) { initSize = chalk.red.bold(initSize); } else if (item.initSize > INIT_SIZE_LIMIT * 0.9) { initSize = chalk.yellow.bold(initSize); } let deployDiff = ''; let initDiff = ''; if (item.previousDeploySize) { if (item.deploySize < item.previousDeploySize) { deployDiff = chalk.green(`-${formatSize(item.previousDeploySize - item.deploySize)}`); } else if (item.deploySize > item.previousDeploySize) { deployDiff = chalk.red(`+${formatSize(item.deploySize - item.previousDeploySize)}`); } else { deployDiff = chalk.gray(formatSize(0)); } } if (item.previousInitSize) { if (item.initSize < item.previousInitSize) { initDiff = chalk.green(`-${formatSize(item.previousInitSize - item.initSize)}`); } else if (item.initSize > item.previousInitSize) { initDiff = chalk.red(`+${formatSize(item.initSize - item.previousInitSize)}`); } else { initDiff = chalk.gray(formatSize(0)); } } table.push([ { content: item.displayName }, { content: `${deploySize} (${deployDiff})`, hAlign: 'right' }, { content: `${initSize} (${initDiff})`, hAlign: 'right' }, ]); } console.log(table.toString()); if (config.outputFile) fs.writeFileSync(config.outputFile, `${stripAnsi(table.toString())}\n`); if (oversizedContracts > 0) { console.log(); const message = `Warning: ${oversizedContracts} contracts exceed the size limit for mainnet deployment (${formatSize(DEPLOYED_SIZE_LIMIT)} ${config.unit} deployed, ${formatSize(INIT_SIZE_LIMIT)} ${config.unit} init).`; if (config.strict) { throw new HardhatPluginError(message); } else { console.log(chalk.red(message)); } } });