@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
327 lines • 13.5 kB
JavaScript
import { isAbsolute } from "path";
import { validateUserConfigZodType } from "@nomicfoundation/hardhat-zod-utils";
import { z } from "zod";
import { PLUGIN_ID } from "./constants.js";
export default async () => ({
validateUserConfig,
resolveUserConfig,
});
const userConfigType = z.object({
migrate: z
.object({
filter: z
.object({
from: z.number().int().optional(),
to: z.number().int().optional(),
only: z.number().int().optional(),
skip: z.number().int().optional(),
})
.optional(),
verification: z
.object({
verify: z.boolean().optional(),
verificationDelay: z.number().int().optional(),
verifyParallel: z.number().int().optional(),
verifyAttempts: z.number().int().optional(),
})
.optional(),
paths: z
.object({
pathToMigrations: z
.string()
.refine((p) => !isAbsolute(p), "Expected a relative path")
.optional(),
namespace: z.string().optional(),
reportPath: z
.string()
.refine((p) => !isAbsolute(p), "Expected a relative path")
.optional(),
reportFormat: z.enum(["json", "md"]).optional(),
})
.optional(),
execution: z
.object({
force: z.boolean().optional(),
continue: z.boolean().optional(),
wait: z.number().int().optional(),
transactionStatusCheckInterval: z.number().int().optional(),
withoutCLIReporting: z.boolean().optional(),
})
.optional(),
castWallet: z
.object({
passwordFile: z.string().optional(),
keystore: z.string().optional(),
account: z.string().optional(),
})
.optional(),
trezorWallet: z
.object({
enabled: z.boolean().optional(),
mnemonicIndex: z.number().int().optional(),
})
.optional(),
})
.optional(),
});
export async function validateUserConfig(userConfig) {
return validateUserConfigZodType(userConfig, userConfigType);
}
export async function resolveUserConfig(userConfig, resolveConfigurationVariable, next) {
const resolvedConfig = await next(userConfig, resolveConfigurationVariable);
const migrate = await resolveMigrateConfig(userConfig.migrate, resolveConfigurationVariable);
return {
...resolvedConfig,
migrate,
};
}
async function resolveMigrateConfig(externalConfig, _resolveConfigurationVariable) {
const defaults = {
filter: {
from: -1,
to: -1,
only: -1,
skip: -1,
},
verification: {
verify: false,
verificationDelay: 5000,
verifyParallel: 1,
verifyAttempts: 3,
},
paths: {
pathToMigrations: "./deploy",
namespace: "",
reportPath: "cache",
reportFormat: "md",
},
execution: {
force: false,
continue: false,
wait: 1,
transactionStatusCheckInterval: 2000,
withoutCLIReporting: false,
},
castWallet: {},
trezorWallet: {
enabled: false,
mnemonicIndex: 0,
},
};
if (externalConfig === undefined) {
return defaults;
}
const resolved = JSON.parse(JSON.stringify(defaults));
// Filter
if (externalConfig.filter) {
if (typeof externalConfig.filter.from === "number")
resolved.filter.from = externalConfig.filter.from;
if (typeof externalConfig.filter.to === "number")
resolved.filter.to = externalConfig.filter.to;
if (typeof externalConfig.filter.only === "number")
resolved.filter.only = externalConfig.filter.only;
if (typeof externalConfig.filter.skip === "number")
resolved.filter.skip = externalConfig.filter.skip;
}
// Verification
if (externalConfig.verification) {
const v = externalConfig.verification;
if (typeof v.verify === "boolean")
resolved.verification.verify = v.verify;
if (typeof v.verificationDelay === "number")
resolved.verification.verificationDelay = v.verificationDelay;
if (typeof v.verifyParallel === "number")
resolved.verification.verifyParallel = v.verifyParallel;
if (typeof v.verifyAttempts === "number")
resolved.verification.verifyAttempts = v.verifyAttempts;
}
// Paths
if (externalConfig.paths) {
const p = externalConfig.paths;
if (typeof p.pathToMigrations === "string")
resolved.paths.pathToMigrations = p.pathToMigrations;
if (typeof p.namespace === "string")
resolved.paths.namespace = p.namespace;
if (typeof p.reportPath === "string")
resolved.paths.reportPath = p.reportPath;
if (typeof p.reportFormat === "string") {
resolved.paths.reportFormat = p.reportFormat;
}
}
// Execution
if (externalConfig.execution) {
const e = externalConfig.execution;
if (typeof e.force === "boolean")
resolved.execution.force = e.force;
if (typeof e.continue === "boolean")
resolved.execution.continue = e.continue;
if (typeof e.wait === "number")
resolved.execution.wait = e.wait;
if (typeof e.transactionStatusCheckInterval === "number")
resolved.execution.transactionStatusCheckInterval = e.transactionStatusCheckInterval;
if (typeof e.withoutCLIReporting === "boolean")
resolved.execution.withoutCLIReporting = e.withoutCLIReporting;
}
// Cast wallet
if (externalConfig.castWallet) {
if (typeof externalConfig.castWallet.passwordFile === "string") {
resolved.castWallet.passwordFile = externalConfig.castWallet.passwordFile;
}
if (typeof externalConfig.castWallet.keystore === "string") {
resolved.castWallet.keystore = externalConfig.castWallet.keystore;
}
if (typeof externalConfig.castWallet.account === "string") {
resolved.castWallet.account = externalConfig.castWallet.account;
}
}
// Trezor wallet
if (externalConfig.trezorWallet) {
const t = externalConfig.trezorWallet;
if (typeof t.enabled === "boolean")
resolved.trezorWallet.enabled = t.enabled;
if (typeof t.mnemonicIndex === "number")
resolved.trezorWallet.mnemonicIndex = t.mnemonicIndex;
}
return resolved;
}
export const mergeConfigs = (externalConfig, migrateConfig) => {
if (externalConfig) {
return deepMerge(migrateConfig, externalConfig);
}
return migrateConfig;
};
export function convertFlatToNested(flatConfig) {
const result = {};
// NOTE: Using truthy checks here means CLI flags that explicitly set `0` (e.g. `--from 0`)
// will be ignored because `0` is falsy. If zero should be a valid override we need
// to switch to `"key" in flatConfig` style checks.
if (flatConfig.from || flatConfig.to || flatConfig.only || flatConfig.skip) {
result.filter = {};
}
if (flatConfig.from)
result.filter.from = flatConfig.from;
if (flatConfig.to)
result.filter.to = flatConfig.to;
if (flatConfig.only)
result.filter.only = flatConfig.only;
if (flatConfig.skip)
result.filter.skip = flatConfig.skip;
if (flatConfig.verify || flatConfig.verificationDelay || flatConfig.verifyParallel || flatConfig.verifyAttempts) {
result.verification = {};
}
if (flatConfig.verify)
result.verification.verify = flatConfig.verify;
if (flatConfig.verificationDelay)
result.verification.verificationDelay = flatConfig.verificationDelay;
if (flatConfig.verifyParallel)
result.verification.verifyParallel = flatConfig.verifyParallel;
if (flatConfig.verifyAttempts)
result.verification.verifyAttempts = flatConfig.verifyAttempts;
if (flatConfig.pathToMigrations || flatConfig.namespace || flatConfig.reportPath || flatConfig.reportFormat) {
result.paths = {};
}
if (flatConfig.pathToMigrations)
result.paths.pathToMigrations = flatConfig.pathToMigrations;
if (flatConfig.namespace)
result.paths.namespace = flatConfig.namespace;
if (flatConfig.reportPath)
result.paths.reportPath = flatConfig.reportPath;
if (flatConfig.reportFormat)
result.paths.reportFormat = flatConfig.reportFormat;
if (flatConfig.force || flatConfig.continue || flatConfig.wait || flatConfig.transactionStatusCheckInterval) {
result.execution = {};
}
if (flatConfig.force)
result.execution.force = flatConfig.force;
if (flatConfig.continue)
result.execution.continue = flatConfig.continue;
if (flatConfig.wait)
result.execution.wait = flatConfig.wait;
if (flatConfig.transactionStatusCheckInterval)
result.execution.transactionStatusCheckInterval = flatConfig.transactionStatusCheckInterval;
if (flatConfig.passwordFile || flatConfig.keystore || flatConfig.account) {
result.castWallet = {};
}
if (flatConfig.passwordFile)
result.castWallet.passwordFile = flatConfig.passwordFile;
if (flatConfig.keystore)
result.castWallet.keystore = flatConfig.keystore;
if (flatConfig.account)
result.castWallet.account = flatConfig.account;
if (flatConfig.trezorEnabled || flatConfig.trezorMnemonicIndex) {
result.trezorWallet = {};
}
if (flatConfig.trezorEnabled)
result.trezorWallet.enabled = flatConfig.trezorEnabled;
if (flatConfig.trezorMnemonicIndex)
result.trezorWallet.mnemonicIndex = flatConfig.trezorMnemonicIndex;
return result;
}
// Deep merge utility for nested objects
function deepMerge(target, source) {
const output = { ...target };
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach((key) => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
}
else {
output[key] = deepMerge(target[key], source[key]);
}
}
else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
function isObject(item) {
return item && typeof item === "object" && !Array.isArray(item);
}
export const validateConfig = (config) => {
if (config.execution.wait !== undefined && config.execution.wait < 1) {
throw new Error(`[${PLUGIN_ID}] config.migrate.execution.wait must be greater than 0`);
}
if (config.verification.verifyParallel !== undefined && config.verification.verifyParallel < 1) {
throw new Error(`[${PLUGIN_ID}] config.migrate.verification.verifyParallel must be greater than 0`);
}
if (config.verification.verifyAttempts !== undefined && config.verification.verifyAttempts < 1) {
throw new Error(`[${PLUGIN_ID}] config.migrate.verification.verifyAttempts must be greater than 0`);
}
if (config.paths.pathToMigrations !== undefined && !isRelativePath(config.paths.pathToMigrations)) {
throw new Error(`[${PLUGIN_ID}] config.migrate.paths.pathToMigrations must be a relative path`);
}
if (config.paths.reportFormat !== undefined && !["json", "md"].includes(config.paths.reportFormat)) {
throw new Error(`[${PLUGIN_ID}] config.migrate.paths.reportFormat must be either 'json' or 'md'`);
}
if (config.trezorWallet.enabled && (config.castWallet.account || config.castWallet.keystore)) {
throw new Error(`[${PLUGIN_ID}] config.migrate.trezorWallet.enabled cannot be used with config.migrate.castWallet.account or config.migrate.castWallet.keystore at the same time`);
}
if (config.castWallet.account && config.castWallet.keystore) {
throw new Error(`[${PLUGIN_ID}] config.migrate.castWallet.account and config.migrate.castWallet.keystore cannot be enabled at the same time`);
}
if (config.execution.transactionStatusCheckInterval !== undefined &&
config.execution.transactionStatusCheckInterval < 1000) {
throw new Error(`[${PLUGIN_ID}] config.migrate.execution.transactionStatusCheckInterval must be greater or equal to 1000`);
}
};
export const extendVerifyConfigs = (cliArgs) => {
const defaultVerifyConfig = {
inputFile: undefined,
parallel: 1,
attempts: 3,
};
const config = cliArgs === undefined ? defaultVerifyConfig : { ...defaultVerifyConfig, ...definedProps(cliArgs) };
if (config.parallel !== undefined && config.parallel < 1) {
throw new Error(`[${PLUGIN_ID}] parallel must be greater than 0`);
}
if (config.attempts !== undefined && config.attempts < 1) {
throw new Error(`[${PLUGIN_ID}] attempts must be greater than 0`);
}
return config;
};
const definedProps = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
const isRelativePath = (path) => path === undefined || !isAbsolute(path);
//# sourceMappingURL=config.js.map