diff --git a/packages/hardhat-core/src/internal/core/config/config-validation.ts b/packages/hardhat-core/src/internal/core/config/config-validation.ts index 2ff610e474..f8fa00767b 100644 --- a/packages/hardhat-core/src/internal/core/config/config-validation.ts +++ b/packages/hardhat-core/src/internal/core/config/config-validation.ts @@ -2,6 +2,11 @@ import * as t from "io-ts"; import { Context, getFunctionName, ValidationError } from "io-ts/lib"; import { Reporter } from "io-ts/lib/Reporter"; +import { + HardhatConfig, + HardhatNetworkAccountsUserConfig, + HardhatNetworkAccountUserConfig, +} from "../../../types"; import { HARDHAT_NETWORK_NAME, HARDHAT_NETWORK_SUPPORTED_HARDFORKS, @@ -53,6 +58,58 @@ function getErrorMessage(path: string, value: any, expectedType: string) { )} for ${path} - Expected a value of type ${expectedType}.`; } +function getPrivateKeyError(account: any, network: string, message: string) { + return `Invalid account: ${account} for network: ${network} - ${message}`; +} + +function validatePrivateKey( + privateKey: any, + network: string, + errors: string[] +) { + if (typeof privateKey !== "string") { + errors.push( + getPrivateKeyError( + privateKey, + network, + `Expected string, received ${typeof privateKey}` + ) + ); + } else { + // private key validation + const pkWithPrefix = /^0x/.test(privateKey) + ? privateKey + : `0x${privateKey}`; + + // 32 bytes = 64 characters + 2 char prefix = 66 + if (pkWithPrefix.length < 66) { + errors.push( + getPrivateKeyError( + privateKey, + network, + "privateKey too short, expected 32 bytes" + ) + ); + } else if (pkWithPrefix.length > 66) { + errors.push( + getPrivateKeyError( + privateKey, + network, + "privateKey too long, expected 32 bytes" + ) + ); + } else if (hexString.decode(pkWithPrefix).isLeft()) { + errors.push( + getPrivateKeyError( + privateKey, + network, + "invalid hex character(s) found in string" + ) + ); + } + } +} + export function failure(es: ValidationError[]): string[] { return es.map(getMessage); } @@ -224,7 +281,7 @@ export function validateConfig(config: any) { } export function getValidationErrors(config: any): string[] { - const errors = []; + const errors: string[] = []; // These can't be validated with io-ts if (config !== undefined && typeof config.networks === "object") { @@ -237,8 +294,7 @@ export function getValidationErrors(config: any): string[] { } // Validating the accounts with io-ts leads to very confusing errors messages - const configExceptAccounts = { ...hardhatNetwork }; - delete configExceptAccounts.accounts; + const { accounts, ...configExceptAccounts } = hardhatNetwork; const netConfigResult = HardhatNetworkConfig.decode(configExceptAccounts); if (netConfigResult.isLeft()) { @@ -251,31 +307,37 @@ export function getValidationErrors(config: any): string[] { ); } - if (Array.isArray(hardhatNetwork.accounts)) { - for (const account of hardhatNetwork.accounts) { - if (typeof account.privateKey !== "string") { + // manual validation of accounts + if (Array.isArray(accounts)) { + for (const account of accounts) { + if (typeof account !== "object") { errors.push( - getErrorMessage( - `HardhatConfig.networks.${HARDHAT_NETWORK_NAME}.accounts[].privateKey`, - account.privateKey, - "string" + getPrivateKeyError( + account, + HARDHAT_NETWORK_NAME, + `Expected object, received ${typeof account}` ) ); + continue; } - if (typeof account.balance !== "string") { + const { privateKey, balance } = account; + + validatePrivateKey(privateKey, HARDHAT_NETWORK_NAME, errors); + + if (typeof balance !== "string") { errors.push( getErrorMessage( `HardhatConfig.networks.${HARDHAT_NETWORK_NAME}.accounts[].balance`, - account.balance, + balance, "string" ) ); - } else if (decimalString.decode(account.balance).isLeft()) { + } else if (decimalString.decode(balance).isLeft()) { errors.push( getErrorMessage( `HardhatConfig.networks.${HARDHAT_NETWORK_NAME}.accounts[].balance`, - account.balance, + balance, "decimal(wei)" ) ); @@ -340,7 +402,9 @@ export function getValidationErrors(config: any): string[] { } } - const netConfigResult = HttpNetworkConfig.decode(netConfig); + const { accounts, ...configExceptAccounts } = netConfig; + + const netConfigResult = HttpNetworkConfig.decode(configExceptAccounts); if (netConfigResult.isLeft()) { errors.push( getErrorMessage( @@ -350,6 +414,40 @@ export function getValidationErrors(config: any): string[] { ) ); } + + // manual validation of accounts + if (typeof accounts === "string" && accounts !== "remote") { + errors.push( + getPrivateKeyError( + accounts, + networkName, + `Expected "remote", received ${accounts}` + ) + ); + } else if (Array.isArray(accounts)) { + accounts.forEach((privateKey) => + validatePrivateKey(privateKey, networkName, errors) + ); + } else if (typeof accounts === "object") { + const hdConfigResult = HDAccountsConfig.decode(accounts); + if (hdConfigResult.isLeft()) { + errors.push( + getErrorMessage( + `HardhatConfig.networks.${networkName}`, + accounts, + "HttpNetworkHDAccountsConfig" + ) + ); + } + } else if (accounts !== undefined) { + errors.push( + getErrorMessage( + `HardhatConfig.networks.${networkName}.accounts`, + accounts, + '"remote" | string[] | HttpNetworkHDAccountsConfig | undefined' + ) + ); + } } } diff --git a/packages/hardhat-core/src/internal/core/errors-list.ts b/packages/hardhat-core/src/internal/core/errors-list.ts index a77466ad82..15bfe4474a 100644 --- a/packages/hardhat-core/src/internal/core/errors-list.ts +++ b/packages/hardhat-core/src/internal/core/errors-list.ts @@ -201,19 +201,6 @@ Please run: npm install --save-dev typescript`, Please run this and try again: \`npm install --save-dev typescript\``, shouldBeReported: false, }, - INVALID_PRIVATE_KEY: { - number: 15, - message: `There's one or more invalid private keys in your config file: - -%keys% - -To learn more about Hardhat's configuration, please go to https://hardhat.org/config/`, - title: "Invalid private key", - description: `You have one or more errors in your config file. - -Check the error message for details, or go to the [documentation](https://hardhat.org/config/) to learn more.`, - shouldBeReported: false, - }, }, NETWORK: { CONFIG_NOT_FOUND: { diff --git a/packages/hardhat-core/test/internal/core/config/config-validation.ts b/packages/hardhat-core/test/internal/core/config/config-validation.ts index 41992bf8cb..6276434e9e 100644 --- a/packages/hardhat-core/test/internal/core/config/config-validation.ts +++ b/packages/hardhat-core/test/internal/core/config/config-validation.ts @@ -257,18 +257,6 @@ describe("Config validation", function () { }); }); - /** - * networkConfig.accounts - if string => must === "remote" - if object => ~ - if array => - if network === hardhat => - each => isValidHexString(each.privateKey) - && isValidBalance?(each.balance) - else (network === http) => - each => isValidHexString(each) - */ - /** * This describe block will encompass all private key tests * for both Hardhat and HTTP networks @@ -316,7 +304,7 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG ); }); @@ -331,7 +319,7 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG ); expectHardhatError( @@ -346,7 +334,24 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG + ); + }); + + it("Should not allow invalid private keys", function () { + expectHardhatError( + () => + validateConfig({ + networks: { + custom: { + url: "http://localhost", + accounts: [ + "0xgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", + ], + }, + }, + }), + ERRORS.GENERAL.INVALID_CONFIG ); }); }); @@ -409,7 +414,7 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG ); }); @@ -428,7 +433,7 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG ); expectHardhatError( @@ -446,7 +451,27 @@ describe("Config validation", function () { }, }, }), - ERRORS.GENERAL.INVALID_PRIVATE_KEY + ERRORS.GENERAL.INVALID_CONFIG + ); + }); + + it("Should not allow invalid private keys", function () { + expectHardhatError( + () => + validateConfig({ + networks: { + [HARDHAT_NETWORK_NAME]: { + accounts: [ + { + balance: "123", + privateKey: + "0xgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", + }, + ], + }, + }, + }), + ERRORS.GENERAL.INVALID_CONFIG ); }); });