UNPKG

hardhat-deploy

Version:

Hardhat Plugin For Replicable Deployments And Tests

1,090 lines 49 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeploymentsManager = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const bignumber_1 = require("@ethersproject/bignumber"); const debug_1 = __importDefault(require("debug")); const log = (0, debug_1.default)('hardhat:wighawag:hardhat-deploy'); const utils_1 = require("./utils"); const helpers_1 = require("./helpers"); const globalStore_1 = require("./globalStore"); const utils_2 = require("./internal/utils"); class DeploymentsManager { constructor(env, network) { this.addressesToProtocol = {}; this.networkWasSetup = false; this.companionManagers = {}; log('constructing DeploymentsManager'); this.network = network; this.impersonateUnknownAccounts = true; this.impersonatedAccounts = []; this.db = { gasUsed: bignumber_1.BigNumber.from(0), accountsLoaded: false, namedAccounts: {}, unnamedAccounts: [], deploymentsLoaded: false, deployments: {}, migrations: {}, writeDeploymentsToFiles: true, fixtureCounter: 0, snapshotCounter: 0, pastFixtures: {}, logEnabled: process.env['HARDHAT_DEPLOY_LOG'] ? true : false, pendingTransactions: {}, savePendingTx: false, gasPrice: undefined, runAsNode: false, }; this.env = env; this.deploymentsPath = env.config.paths.deployments; // TODO // this.env.artifacts = new HardhatDeployArtifacts(this.env.artifacts); this.partialExtension = { readDotFile: async (name) => this.readDotFile(name), saveDotFile: async (name, content) => this.saveDotFile(name, content), deleteDotFile: async (name) => this.deleteDotFile(name), save: async (name, deployment) => { await this.saveDeployment(name, deployment); }, delete: async (name) => this.deleteDeployment(name), get: async (name) => { await this.setup(false); const deployment = this.db.deployments[name]; if (deployment === undefined) { throw new Error(`No deployment found for: ${name}`); } return deployment; }, getOrNull: async (name) => { await this.setup(false); return this.db.deployments[name]; }, getDeploymentsFromAddress: async (address) => { const deployments = []; for (const deployment of Object.values(this.db.deployments)) { if (deployment.address === address) { deployments.push(deployment); } } return deployments; }, all: async () => { await this.setup(false); return this.db.deployments; // TODO copy }, getArtifact: async (contractName) => { if (this.db.onlyArtifacts) { const artifactFromFolder = await (0, utils_1.getArtifactFromFolders)(contractName, this.db.onlyArtifacts); if (!artifactFromFolder) { throw new Error(`cannot find artifact "${contractName}" from folder ${this.db.onlyArtifacts}`); } return artifactFromFolder; } let artifact = await (0, utils_1.getArtifactFromFolders)(contractName, [ this.env.config.paths.artifacts, ]); if (artifact) { return artifact; } const importPaths = this.getImportPaths(); artifact = await (0, utils_1.getArtifactFromFolders)(contractName, importPaths); if (!artifact) { throw new Error(`cannot find artifact "${contractName}"`); } return artifact; }, getExtendedArtifact: async (contractName) => { if (this.db.onlyArtifacts) { const artifactFromFolder = await (0, utils_1.getExtendedArtifactFromFolders)(contractName, this.db.onlyArtifacts); if (!artifactFromFolder) { throw new Error(`cannot find artifact "${contractName}" from folder ${this.db.onlyArtifacts}`); } return artifactFromFolder; } let artifact = await (0, utils_1.getExtendedArtifactFromFolders)(contractName, [ this.env.config.paths.artifacts, ]); if (artifact) { return artifact; } const importPaths = this.getImportPaths(); artifact = await (0, utils_1.getExtendedArtifactFromFolders)(contractName, importPaths); if (artifact) { return artifact; } if (!artifact) { throw new Error(`cannot find artifact "${contractName}"`); } return artifact; }, run: (tags, options = { resetMemory: true, writeDeploymentsToFiles: false, deletePreviousDeployments: false, }) => { return this.runDeploy(tags, { resetMemory: options.resetMemory === undefined ? true : options.resetMemory, deletePreviousDeployments: options.deletePreviousDeployments === undefined ? false : options.deletePreviousDeployments, writeDeploymentsToFiles: options.writeDeploymentsToFiles === undefined ? false : options.writeDeploymentsToFiles, export: options.export, exportAll: options.exportAll, log: false, savePendingTx: false, }); }, fixture: async (tags, options) => { await this.setup(tags === undefined); options = Object.assign({ fallbackToGlobal: true }, options); if (typeof tags === 'string') { tags = [tags]; } const globalKey = '::global'; const globalFixture = this.db.pastFixtures[globalKey]; let fixtureKey = globalKey; if (tags !== undefined) { fixtureKey = '::' + tags.join('.'); } if (this.db.pastFixtures[fixtureKey]) { const pastFixture = this.db.pastFixtures[fixtureKey]; const success = await this.revertSnapshot(pastFixture); if (success) { return this.db.deployments; } else { delete this.db.pastFixtures[fixtureKey]; } } if (globalFixture && options.fallbackToGlobal) { const success = await this.revertSnapshot(globalFixture); if (success) { return this.db.deployments; } else { delete this.db.pastFixtures[globalKey]; } } await this.runDeploy(tags, { resetMemory: !options.keepExistingDeployments, writeDeploymentsToFiles: false, deletePreviousDeployments: false, log: false, savePendingTx: false, }); await this.saveSnapshot(fixtureKey); return this.db.deployments; }, createFixture: (func) => { const baseId = '' + ++this.db.fixtureCounter + '::'; return async (options) => { let id = baseId; if (options !== undefined) { id = id + JSON.stringify(options, utils_2.bnReplacer); } const saved = this.db.pastFixtures[id]; if (saved) { const success = await this.revertSnapshot(saved); if (success) { return saved.data; } } const data = await func(this.env, options); await this.saveSnapshot(id, data); return data; }; }, log: (...args) => { if (this.db.logEnabled) { console.log(...args); } }, getNetworkName: () => this.getNetworkName(), getGasUsed: () => this.db.gasUsed.toNumber(), }; const print = (msg) => { if (this.db.logEnabled) { process.stdout.write(msg); } }; log('adding helpers'); const helpers = (0, helpers_1.addHelpers)(this, this.partialExtension, this.network, this.partialExtension.getArtifact, async (name, deployment, artifactName) => { if (artifactName && this.db.writeDeploymentsToFiles && this.network.saveDeployments) { // toSave (see deployments.save function) const extendedArtifact = await this.partialExtension.getExtendedArtifact(artifactName); deployment = Object.assign(Object.assign({}, deployment), extendedArtifact); } await this.partialExtension.save(name, deployment); }, () => { return this.db.writeDeploymentsToFiles && this.network.saveDeployments; }, this.onPendingTx.bind(this), async () => { // TODO extraGasPrice ? let gasPrice; let maxFeePerGas; let maxPriorityFeePerGas; if (this.db.gasPrice) { gasPrice = bignumber_1.BigNumber.from(this.db.gasPrice); } else { if (this.db.maxFeePerGas) { maxFeePerGas = bignumber_1.BigNumber.from(this.db.maxFeePerGas); } if (this.db.maxPriorityFeePerGas) { maxPriorityFeePerGas = bignumber_1.BigNumber.from(this.db.maxPriorityFeePerGas); } } return { gasPrice, maxFeePerGas, maxPriorityFeePerGas }; }, this.partialExtension.log, print); this.deploymentsExtension = helpers.extension; this.utils = helpers.utils; } setupNetwork() { if (this.networkWasSetup) { return; } // reassign network variables based on fork name if any; const networkName = this.getNetworkName(); if (networkName !== this.network.name) { const networkObject = globalStore_1.store.networks[networkName]; if (networkObject) { this.env.network.live = networkObject.live; this.env.network.tags = networkObject.tags; this.env.network.deploy = networkObject.deploy; } } this.networkWasSetup = true; } async getChainId() { if (this._chainId) { return this._chainId; } this.setupNetwork(); try { this._chainId = await this.network.provider.send('eth_chainId'); } catch (e) { console.log('failed to get chainId, falling back on net_version...'); this._chainId = await this.network.provider.send('net_version'); } if (!this._chainId) { throw new Error(`could not get chainId from network`); } if (this._chainId.startsWith('0x')) { this._chainId = bignumber_1.BigNumber.from(this._chainId).toString(); } return this._chainId; } runAsNode(enabled) { this.db.runAsNode = enabled; } async dealWithPendingTransactions() { let pendingTxs = {}; const pendingTxPath = path_1.default.join(this.deploymentsPath, this.deploymentFolder(), '.pendingTransactions'); try { pendingTxs = JSON.parse(fs_extra_1.default.readFileSync(pendingTxPath).toString()); } catch (e) { } await this.utils.dealWithPendingTransactions(pendingTxs, pendingTxPath, this.db.gasPrice); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async onPendingTx(tx, name, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types deployment) { var _a, _b, _c; if (this.db.writeDeploymentsToFiles && this.network.saveDeployments && this.db.savePendingTx) { const deployFolderPath = path_1.default.join(this.deploymentsPath, this.deploymentFolder()); // console.log("tx", tx.hash); const pendingTxPath = path_1.default.join(deployFolderPath, '.pendingTransactions'); fs_extra_1.default.ensureDirSync(deployFolderPath); const rawTx = tx.raw; const decoded = tx.raw ? undefined : { from: tx.from, gasPrice: (_a = tx.gasPrice) === null || _a === void 0 ? void 0 : _a.toString(), maxFeePerGas: (_b = tx.maxFeePerGas) === null || _b === void 0 ? void 0 : _b.toString(), maxPriorityFeePerGas: (_c = tx.maxPriorityFeePerGas) === null || _c === void 0 ? void 0 : _c.toString(), gasLimit: tx.gasLimit.toString(), to: tx.to, value: tx.value.toString(), nonce: tx.nonce, data: tx.data, r: tx.r, s: tx.s, v: tx.v, // creates: tx.creates, // TODO test chainId: tx.chainId, }; this.db.pendingTransactions[tx.hash] = name ? { name, deployment, rawTx, decoded } : { rawTx, decoded }; fs_extra_1.default.writeFileSync(pendingTxPath, JSON.stringify(this.db.pendingTransactions, utils_2.bnReplacer, ' ')); // await new Promise(r => setTimeout(r, 20000)); const wait = tx.wait.bind(tx); tx.wait = async (confirmations) => { const receipt = await wait(confirmations); // console.log("checking pending tx..."); delete this.db.pendingTransactions[tx.hash]; if (Object.keys(this.db.pendingTransactions).length === 0) { fs_extra_1.default.removeSync(pendingTxPath); } else { fs_extra_1.default.writeFileSync(pendingTxPath, JSON.stringify(this.db.pendingTransactions, utils_2.bnReplacer, ' ')); } this.db.gasUsed = this.db.gasUsed.add(receipt.gasUsed); return receipt; }; } else { const wait = tx.wait.bind(tx); tx.wait = async (confirmations) => { const receipt = await wait(confirmations); this.db.gasUsed = this.db.gasUsed.add(receipt.gasUsed); return receipt; }; } return tx; } async getNamedAccounts() { await this.setupAccounts(); return this.db.namedAccounts; } async getUnnamedAccounts() { await this.setupAccounts(); return this.db.unnamedAccounts; } async getDeterminisityDeploymentInfo() { const chainId = await this.getChainId(); const config = this.env.config.deterministicDeployment; return typeof config == 'function' ? config(chainId) : config === null || config === void 0 ? void 0 : config[chainId]; } async getDeterministicDeploymentFactoryAddress() { const info = await this.getDeterminisityDeploymentInfo(); return (info === null || info === void 0 ? void 0 : info.factory) || '0x4e59b44847b379578588920ca78fbf26c0b4956c'; } async getDeterministicDeploymentFactoryDeployer() { const info = await this.getDeterminisityDeploymentInfo(); return (info === null || info === void 0 ? void 0 : info.deployer) || '0x3fab184622dc19b6109349b94811493bf2a45362'; } async getDeterministicDeploymentFactoryFunding() { const info = await this.getDeterminisityDeploymentInfo(); return bignumber_1.BigNumber.from((info === null || info === void 0 ? void 0 : info.funding) || '10000000000000000'); } async getDeterministicDeploymentFactoryDeploymentTx() { const info = await this.getDeterminisityDeploymentInfo(); return ((info === null || info === void 0 ? void 0 : info.signedTx) || '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222'); } async loadDeployments(chainIdExpected = true) { let chainId; if (chainIdExpected) { chainId = await this.getChainId(); } let migrations = {}; try { log('loading migrations'); migrations = JSON.parse(fs_extra_1.default .readFileSync(path_1.default.join(this.deploymentsPath, this.deploymentFolder(), '.migrations.json')) .toString()); } catch (e) { } this.db.migrations = migrations; // console.log({ migrations: this.db.migrations }); const networkName = this.getDeploymentNetworkName(); (0, utils_1.addDeployments)(this.db, this.deploymentsPath, this.deploymentFolder(), networkName === this.network.name ? chainId : undefined // fork mode, we do not care about chainId ? ); const extraDeploymentPaths = this.env.config.external && this.env.config.external.deployments && this.env.config.external.deployments[networkName]; if (extraDeploymentPaths) { for (const deploymentFolderPath of extraDeploymentPaths) { (0, utils_1.addDeployments)(this.db, deploymentFolderPath, '', undefined, chainId); } } this.db.deploymentsLoaded = true; return this.db.deployments; } async deletePreviousDeployments(folderPath) { folderPath = folderPath || this.deploymentFolder(); (0, utils_1.deleteDeployments)(this.deploymentsPath, folderPath); } getSolcInputPath() { return path_1.default.join(this.deploymentsPath, this.deploymentFolder(), 'solcInputs'); } async deleteDotFile(name) { const toSave = this.db.writeDeploymentsToFiles && this.network.saveDeployments; if (toSave) { // do not delete if not save mode const deployFolderpath = path_1.default.join(this.deploymentsPath, this.deploymentFolder()); const filepath = path_1.default.join(deployFolderpath, name); try { fs_extra_1.default.unlinkSync(filepath); } catch (e) { } } } async readDotFile(name) { if (!name.startsWith('.')) { throw new Error(`file to save need to start with a dot to ensure it is not considered a deployment`); } const deployFolderpath = path_1.default.join(this.deploymentsPath, this.deploymentFolder()); const filepath = path_1.default.join(deployFolderpath, name); return fs_extra_1.default.readFileSync(filepath).toString(); } async saveDotFile(name, content) { if (!name.startsWith('.')) { throw new Error(`file to save need to start with a dot to ensure it is not considered a deployment`); } const toSave = this.db.writeDeploymentsToFiles && this.network.saveDeployments; if (toSave) { const chainId = await this.getChainId(); const deployFolderpath = path_1.default.join(this.deploymentsPath, this.deploymentFolder()); const filepath = path_1.default.join(deployFolderpath, name); fs_extra_1.default.ensureDirSync(deployFolderpath); const chainIdFilepath = path_1.default.join(deployFolderpath, '.chainId'); if (!fs_extra_1.default.existsSync(chainIdFilepath)) { fs_extra_1.default.writeFileSync(chainIdFilepath, chainId); } const folderPath = path_1.default.dirname(filepath); fs_extra_1.default.ensureDirSync(folderPath); fs_extra_1.default.writeFileSync(filepath, content); } } async deleteDeployment(name) { delete this.db.deployments[name]; const toSave = this.db.writeDeploymentsToFiles && this.network.saveDeployments; if (toSave) { // do not delete if not save mode const filepath = path_1.default.join(this.deploymentsPath, this.deploymentFolder(), name + '.json'); try { fs_extra_1.default.unlinkSync(filepath); } catch (e) { } } } async saveDeployment(name, deployment) { var _a, _b, _c; if (name.includes('/') || name.includes(':')) { throw new Error(`deployment name must not be a path or Fully Qualified Name - for such purposes consider using the "contract" field of deployment options`); } if (typeof deployment.address === undefined && !((_a = deployment.receipt) === null || _a === void 0 ? void 0 : _a.contractAddress)) { throw new Error('deployment need a receipt with contractAddress or an address'); } if (typeof deployment.abi === undefined) { throw new Error('deployment need an ABI'); } if (name.includes('/') || name.includes(':')) { throw new Error(`deployment name must not be a path or Fully Qualified Name - for such purposes consider using the "contract" field of deployment options`); } const chainId = await this.getChainId(); const toSave = this.db.writeDeploymentsToFiles && this.network.saveDeployments; const filepath = path_1.default.join(this.deploymentsPath, this.deploymentFolder(), name + '.json'); // handle ethers receipt : const receipt = deployment.receipt; const actualReceipt = receipt ? { to: receipt.to, from: receipt.from, contractAddress: receipt.contractAddress, transactionIndex: receipt.transactionIndex, gasUsed: receipt.gasUsed && receipt.gasUsed._isBigNumber ? receipt.gasUsed.toString() : receipt.gasUsed, logsBloom: receipt.logsBloom, blockHash: receipt.blockHash, transactionHash: receipt.transactionHash, logs: receipt.logs, events: receipt.events, blockNumber: receipt.blockNumber, cumulativeGasUsed: receipt.cumulativeGasUsed && receipt.cumulativeGasUsed._isBigNumber ? receipt.cumulativeGasUsed.toString() : receipt.cumulativeGasUsed, status: receipt.status, byzantium: receipt.byzantium, } : undefined; // from : https://stackoverflow.com/a/14810722/1663971 function objectMap(object, mapFn) { return Object.keys(object).reduce(function (result, key) { result[key] = mapFn(object[key]); return result; }, {}); } // TODO can cause infinite loop function transform(v) { if (v._isBigNumber) { return v.toString(); } if (Array.isArray(v)) { return v.map(transform); } if (typeof v === 'object') { return objectMap(v, transform); } return v; } const actualArgs = (_b = deployment.args) === null || _b === void 0 ? void 0 : _b.map(transform); let numDeployments = 1; const oldDeployment = this.db.deployments[name] ? Object.assign({}, this.db.deployments[name]) : undefined; if (oldDeployment) { numDeployments = (oldDeployment.numDeployments || 1) + 1; if (!deployment.history) { delete oldDeployment.history; } } const obj = JSON.parse(JSON.stringify({ address: deployment.address || (actualReceipt === null || actualReceipt === void 0 ? void 0 : actualReceipt.contractAddress), abi: deployment.abi, transactionHash: deployment.transactionHash || (actualReceipt === null || actualReceipt === void 0 ? void 0 : actualReceipt.transactionHash), receipt: actualReceipt, args: actualArgs, numDeployments, linkedData: deployment.linkedData, solcInputHash: deployment.solcInputHash, metadata: deployment.metadata, bytecode: deployment.bytecode, deployedBytecode: deployment.deployedBytecode, libraries: deployment.libraries, facets: deployment.facets, execute: deployment.execute, history: deployment.history, implementation: deployment.implementation, devdoc: deployment.devdoc, userdoc: deployment.userdoc, storageLayout: deployment.storageLayout, methodIdentifiers: deployment.methodIdentifiers, gasEstimates: deployment.gasEstimates, // TODO double check : use evm field ? }, utils_2.bnReplacer)); if ((_c = deployment.factoryDeps) === null || _c === void 0 ? void 0 : _c.length) { obj.factoryDeps = deployment.factoryDeps; } this.db.deployments[name] = obj; if (obj.address === undefined && obj.transactionHash !== undefined) { let receiptFetched; try { receiptFetched = await (0, helpers_1.waitForTx)(this.network.provider, obj.transactionHash, true); // TODO add receipt ? obj.address = receiptFetched.contractAddress; if (!obj.address) { throw new Error('no contractAddress in receipt'); } } catch (e) { console.error(e); if (toSave) { console.log('deleting ' + filepath); fs_extra_1.default.unlinkSync(filepath); } delete this.db.deployments[name]; return false; // TODO throw error ? } } this.db.deployments[name] = obj; // console.log({chainId, typeOfChainId: typeof chainId}); if (toSave) { // console.log("writing " + filepath); // TODO remove try { fs_extra_1.default.mkdirSync(this.deploymentsPath); } catch (e) { } const deployFolderpath = path_1.default.join(this.deploymentsPath, this.deploymentFolder()); try { fs_extra_1.default.mkdirSync(deployFolderpath); } catch (e) { } const chainIdFilepath = path_1.default.join(deployFolderpath, '.chainId'); if (!fs_extra_1.default.existsSync(chainIdFilepath)) { fs_extra_1.default.writeFileSync(chainIdFilepath, chainId); } fs_extra_1.default.writeFileSync(filepath, JSON.stringify(obj, utils_2.bnReplacer, ' ')); if (deployment.solcInputHash && deployment.solcInput) { const solcInputsFolderpath = path_1.default.join(this.deploymentsPath, this.deploymentFolder(), 'solcInputs'); const solcInputFilepath = path_1.default.join(solcInputsFolderpath, deployment.solcInputHash + '.json'); if (!fs_extra_1.default.existsSync(solcInputFilepath)) { try { fs_extra_1.default.mkdirSync(solcInputsFolderpath); } catch (e) { } fs_extra_1.default.writeFileSync(solcInputFilepath, deployment.solcInput); } } } // this.spreadEvents(); return true; } addCompanionManager(name, networkDeploymentsManager) { this.companionManagers[name] = networkDeploymentsManager; } async runDeploy(tags, options = { log: false, resetMemory: true, deletePreviousDeployments: false, writeDeploymentsToFiles: true, savePendingTx: false, }) { var _a; log('runDeploy'); this.setupNetwork(); if (options.deletePreviousDeployments) { log('deleting previous deployments'); this.db.deployments = {}; this.db.migrations = {}; await this.deletePreviousDeployments(); for (const companionNetworkName of Object.keys(this.companionManagers)) { const companionManager = this.companionManagers[companionNetworkName]; companionManager.deletePreviousDeployments(); } } await this.loadDeployments(); this.db.gasUsed = bignumber_1.BigNumber.from(0); this.db.writeDeploymentsToFiles = options.writeDeploymentsToFiles; this.db.savePendingTx = options.savePendingTx; this.db.logEnabled = options.log; this.db.gasPrice = options.gasPrice; this.db.maxFeePerGas = options.maxFeePerGas; this.db.maxPriorityFeePerGas = options.maxPriorityFeePerGas; if (options.resetMemory) { log('reseting memory'); this.db.deployments = {}; this.db.migrations = {}; } if (!options.deletePreviousDeployments && options.savePendingTx) { await this.dealWithPendingTransactions(); // TODO deal with reset ? } for (const companionNetworkName of Object.keys(this.companionManagers)) { const companionManager = this.companionManagers[companionNetworkName]; await companionManager.loadDeployments(); companionManager.db.writeDeploymentsToFiles = options.writeDeploymentsToFiles; companionManager.db.savePendingTx = options.savePendingTx; companionManager.db.logEnabled = options.log; // companionManager.db.gasPrice = options.gasPrice; if (options.resetMemory) { log('reseting memory'); companionManager.db.deployments = {}; companionManager.db.migrations = {}; } if (!options.deletePreviousDeployments && options.savePendingTx) { await companionManager.dealWithPendingTransactions(); // TODO deal with reset ? } } if (tags !== undefined && typeof tags === 'string') { tags = [tags]; } if ((_a = this.env.config.external) === null || _a === void 0 ? void 0 : _a.contracts) { for (const externalContracts of this.env.config.external.contracts) { if (externalContracts.deploy) { this.db.onlyArtifacts = externalContracts.artifacts; try { await this.executeDeployScripts([externalContracts.deploy], tags, options.tagsRequireAll); } finally { this.db.onlyArtifacts = undefined; } } } } const deployPaths = (0, utils_1.getDeployPaths)(this.network); await this.executeDeployScripts(deployPaths, tags, options.tagsRequireAll); await this.export(options); return this.db.deployments; } async executeDeployScripts(deployScriptsPaths, tags = [], tagsRequireAll = false) { const wasWrittingToFiles = this.db.writeDeploymentsToFiles; // TODO loop over companion networks ? // This is currently posing problem for network like optimism which require a different set of artifact and hardhat currently only expose one set at a time let filepaths; try { filepaths = (0, utils_1.traverseMultipleDirectory)(deployScriptsPaths); } catch (e) { return; } filepaths = filepaths.sort((a, b) => { if (a < b) { return -1; } if (a > b) { return 1; } return 0; }); log('deploy script folder parsed'); const funcByFilePath = {}; const scriptPathBags = {}; const scriptFilePaths = []; for (const filepath of filepaths) { const scriptFilePath = path_1.default.resolve(filepath); let deployFunc; // console.log("fetching " + scriptFilePath); try { delete require.cache[scriptFilePath]; // ensure we reload it every time, so changes are taken in consideration deployFunc = require(scriptFilePath); if (deployFunc.default) { deployFunc = deployFunc.default; } funcByFilePath[scriptFilePath] = deployFunc; } catch (e) { // console.error("require failed", e); throw new Error('ERROR processing skip func of ' + filepath + ':\n' + (e.stack || e)); } // console.log("get tags if any for " + scriptFilePath); let scriptTags = deployFunc.tags || []; if (typeof scriptTags === 'string') { scriptTags = [scriptTags]; } for (const tag of scriptTags) { if (tag.indexOf(',') >= 0) { throw new Error('Tag cannot contain commas'); } const bag = scriptPathBags[tag] || []; scriptPathBags[tag] = bag; bag.push(scriptFilePath); } // console.log("tags found " + scriptFilePath, scriptTags); if (tagsRequireAll && tags.every(tag => scriptTags.includes(tag)) || !tagsRequireAll && (tags.length == 0 || tags.some(tag => scriptTags.includes(tag)))) { scriptFilePaths.push(scriptFilePath); } } log('tag collected'); // console.log({ scriptFilePaths }); const scriptsRegisteredToRun = {}; const scriptsToRun = []; const scriptsToRunAtTheEnd = []; function recurseDependencies(scriptFilePath) { if (scriptsRegisteredToRun[scriptFilePath]) { return; } const deployFunc = funcByFilePath[scriptFilePath]; if (deployFunc.dependencies) { for (const dependency of deployFunc.dependencies) { const scriptFilePathsToAdd = scriptPathBags[dependency]; if (scriptFilePathsToAdd) { for (const scriptFilenameToAdd of scriptFilePathsToAdd) { recurseDependencies(scriptFilenameToAdd); } } } } if (!scriptsRegisteredToRun[scriptFilePath]) { if (deployFunc.runAtTheEnd) { scriptsToRunAtTheEnd.push({ filePath: scriptFilePath, func: deployFunc, }); } else { scriptsToRun.push({ filePath: scriptFilePath, func: deployFunc, }); } scriptsRegisteredToRun[scriptFilePath] = true; } } for (const scriptFilePath of scriptFilePaths) { recurseDependencies(scriptFilePath); } log('dependencies collected'); try { for (const deployScript of scriptsToRun.concat(scriptsToRunAtTheEnd)) { const filename = path_1.default.basename(deployScript.filePath); if (deployScript.func.id && this.db.migrations[deployScript.func.id]) { log(`skipping ${filename} as migrations already executed and complete`); continue; } let skip = false; if (deployScript.func.skip) { log(`should we skip ${deployScript.filePath} ?`); try { skip = await deployScript.func.skip(this.env); } catch (e) { // console.error("skip failed", e); throw new Error('ERROR processing skip func of ' + deployScript.filePath + ':\n' + (e.stack || e)); } log(`checking skip for ${deployScript.filePath} complete`); } if (!skip) { log(`executing ${deployScript.filePath}`); let result; try { result = await deployScript.func(this.env); } catch (e) { // console.error("execution failed", e); throw new Error('ERROR processing ' + deployScript.filePath + ':\n' + (e.stack || e)); } log(`executing ${deployScript.filePath} complete`); if (result && typeof result === 'boolean') { if (!deployScript.func.id) { throw new Error(`${deployScript.filePath} return true to not be executed again, but does not provide an id. the script function needs to have the field "id" to be set`); } this.db.migrations[deployScript.func.id] = Math.floor(Date.now() / 1000); const deploymentFolderPath = this.deploymentFolder(); // TODO refactor to extract this whole path and folder existence stuff const toSave = this.db.writeDeploymentsToFiles && this.network.saveDeployments; if (toSave) { try { fs_extra_1.default.mkdirSync(this.deploymentsPath); } catch (e) { } try { fs_extra_1.default.mkdirSync(path_1.default.join(this.deploymentsPath, deploymentFolderPath)); } catch (e) { } fs_extra_1.default.writeFileSync(path_1.default.join(this.deploymentsPath, deploymentFolderPath, '.migrations.json'), JSON.stringify(this.db.migrations, utils_2.bnReplacer, ' ')); } } } } } catch (e) { this.db.writeDeploymentsToFiles = wasWrittingToFiles; throw e; } this.db.writeDeploymentsToFiles = wasWrittingToFiles; log('deploy scripts complete'); } async export(options) { let chainId; try { chainId = fs_extra_1.default .readFileSync(path_1.default.join(this.deploymentsPath, this.deploymentFolder(), '.chainId')) .toString() .trim(); } catch (e) { } if (!chainId) { chainId = await this.getChainId(); } if (options.exportAll !== undefined) { log('load all deployments for export-all'); const all = (0, utils_1.loadAllDeployments)(this.env, this.deploymentsPath, true, this.env.config.external && this.env.config.external.deployments); const currentNetworkDeployments = {}; const currentDeployments = this.db.deployments; for (const contractName of Object.keys(currentDeployments)) { const deployment = currentDeployments[contractName]; currentNetworkDeployments[contractName] = { address: deployment.address, abi: deployment.abi, linkedData: deployment.linkedData, }; } const currentNetwork = this.getDeploymentNetworkName(); if (all[chainId] === undefined) { all[chainId] = []; } else { all[chainId] = all[chainId].filter((v) => v.name !== currentNetwork); } all[chainId].push({ name: currentNetwork, chainId, contracts: currentNetworkDeployments, }); this._writeExports(options.exportAll, all); log('export-all complete'); } if (options.export !== undefined) { log('single export...'); const currentNetworkDeployments = {}; if (chainId !== undefined) { const currentDeployments = this.db.deployments; for (const contractName of Object.keys(currentDeployments)) { const deployment = currentDeployments[contractName]; currentNetworkDeployments[contractName] = { address: deployment.address, abi: deployment.abi, linkedData: deployment.linkedData, }; } } else { throw new Error('chainId is undefined'); } const singleExport = { name: this.getDeploymentNetworkName(), chainId, contracts: currentNetworkDeployments, }; this._writeExports(options.export, singleExport); log('single export complete'); } } _writeExports(dests, outputObject) { const output = JSON.stringify(outputObject, utils_2.bnReplacer, ' '); // TODO remove bytecode ? const splitted = dests.split(','); for (const split of splitted) { if (!split) { continue; } if (split === '-') { process.stdout.write(output); } else { fs_extra_1.default.ensureDirSync(path_1.default.dirname(split)); if (split.endsWith('.ts')) { fs_extra_1.default.writeFileSync(split, `export default ${output} as const;`); } else { fs_extra_1.default.writeFileSync(split, output); } } } } getImportPaths() { const importPaths = [this.env.config.paths.imports]; if (this.env.config.external && this.env.config.external.contracts) { for (const externalContracts of this.env.config.external.contracts) { importPaths.push(...externalContracts.artifacts); } } return importPaths; } async setup(isRunningGlobalFixture) { this.setupNetwork(); if (!this.db.deploymentsLoaded && !isRunningGlobalFixture) { if (process.env.HARDHAT_DEPLOY_FIXTURE) { if (process.env.HARDHAT_COMPILE) { // console.log("compiling..."); await this.env.run('compile'); } this.db.deploymentsLoaded = true; // console.log("running global fixture...."); await this.partialExtension.fixture(undefined, { keepExistingDeployments: true, // by default reuse the existing deployments (useful for fork testing) }); } else { if (process.env.HARDHAT_COMPILE) { // console.log("compiling..."); await this.env.run('compile'); } await this.loadDeployments(); } } } async saveSnapshot(key, data) { const latestBlock = await this.network.provider.send('eth_getBlockByNumber', ['latest', false]); try { const snapshot = await this.network.provider.send('evm_snapshot', []); this.db.pastFixtures[key] = { index: ++this.db.snapshotCounter, snapshot, data, blockHash: latestBlock.hash, deployments: Object.assign({}, this.db.deployments), }; } catch (err) { log(`failed to create snapshot`); } } async revertSnapshot(saved) { const snapshotToRevertIndex = saved.index; for (const fixtureKey of Object.keys(this.db.pastFixtures)) { const snapshotIndex = this.db.pastFixtures[fixtureKey].index; if (snapshotIndex > snapshotToRevertIndex) { delete this.db.pastFixtures[fixtureKey]; } } let success; try { success = await this.network.provider.send('evm_revert', [ saved.snapshot, ]); } catch (_a) { log(`failed to revert to snapshot`); success = false; } if (success) { const blockRetrieved = await this.network.provider.send('eth_getBlockByHash', [saved.blockHash, false]); if (blockRetrieved) { saved.snapshot = await this.network.provider.send('evm_snapshot', []); // it is necessary to re-snapshot it this.db.deployments = Object.assign({}, saved.deployments); } else { // TODO or should we throw ? return false; } } return success; } disableAutomaticImpersonation() { this.impersonateUnknownAccounts = false; } getNetworkName() { return (0, utils_1.getNetworkName)(this.network); } getDeploymentNetworkName() { if (this.db.runAsNode) { return 'localhost'; } return (0, utils_1.getNetworkName)(this.network); } deploymentFolder() { return this.getDeploymentNetworkName(); } async impersonateAccounts(unknownAccounts) { if (!this.impersonateUnknownAccounts || process.env.HARDHAT_DEPLOY_NO_IMPERSONATION) { return; } if (this.network.autoImpersonate) { for (const address of unknownAccounts) { if (this.network.name === 'hardhat') { await this.network.provider.request({ method: 'hardhat_impersonateAccount', params: [address], }); } this.impersonatedAccounts.push(address); } } } async setupAccounts() { if (!this.db.accountsLoaded) { const chainId = await this.getChainId(); const accounts = await this.network.provider.send('eth_accounts'); const { namedAccounts, unnamedAccounts, unknownAccounts, addressesToProtocol, } = (0, utils_1.processNamedAccounts)(this.network, this.env.config.namedAccounts, accounts, chainId); // TODO pass in network name await this.impersonateAccounts(unknownAccounts); this.db.namedAccounts = namedAccounts; this.db.unnamedAccounts = unnamedAccounts; this.db.accountsLoaded = true; this.addressesToProtocol = addressesToProtocol; } return { namedAccounts: this.db.namedAccounts, unnamedAccounts: this.db.unnamedAccounts, }; } } exports.DeploymentsManager = DeploymentsManager; //# sourceMappingURL=DeploymentsManager.js.map