@nomiclabs/buidler
Version:
Buidler is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
387 lines (335 loc) • 10.6 kB
text/typescript
import * as t from "io-ts";
import { Context, getFunctionName, ValidationError } from "io-ts/lib";
import { Reporter } from "io-ts/lib/Reporter";
import {
BUIDLEREVM_NETWORK_NAME,
BUIDLEREVM_SUPPORTED_HARDFORKS,
} from "../../constants";
import { BuidlerError } from "../errors";
import { ERRORS } from "../errors-list";
function stringify(v: any): string {
if (typeof v === "function") {
return getFunctionName(v);
}
if (typeof v === "number" && !isFinite(v)) {
if (isNaN(v)) {
return "NaN";
}
return v > 0 ? "Infinity" : "-Infinity";
}
return JSON.stringify(v);
}
function getContextPath(context: Context): string {
const keysPath = context
.slice(1)
.map((c) => c.key)
.join(".");
return `${context[0].type.name}.${keysPath}`;
}
function getMessage(e: ValidationError): string {
const lastContext = e.context[e.context.length - 1];
return e.message !== undefined
? e.message
: getErrorMessage(
getContextPath(e.context),
e.value,
lastContext.type.name
);
}
function getErrorMessage(path: string, value: any, expectedType: string) {
return `Invalid value ${stringify(
value
)} for ${path} - Expected a value of type ${expectedType}.`;
}
export function failure(es: ValidationError[]): string[] {
return es.map(getMessage);
}
export function success(): string[] {
return [];
}
export const DotPathReporter: Reporter<string[]> = {
report: (validation) => validation.fold(failure, success),
};
function optional<TypeT, OutputT>(
codec: t.Type<TypeT, OutputT, unknown>,
name: string = `${codec.name} | undefined`
): t.Type<TypeT | undefined, OutputT | undefined, unknown> {
return new t.Type(
name,
(u: unknown): u is TypeT | undefined => u === undefined || codec.is(u),
(u, c) => (u === undefined ? t.success(u) : codec.validate(u, c)),
(a) => (a === undefined ? undefined : codec.encode(a))
);
}
// IMPORTANT: This t.types MUST be kept in sync with the actual types.
const BuidlerNetworkAccount = t.type({
privateKey: t.string,
balance: t.string,
});
const BuidlerNetworkConfig = t.type({
hardfork: optional(t.string),
chainId: optional(t.number),
from: optional(t.string),
gas: optional(t.union([t.literal("auto"), t.number])),
gasPrice: optional(t.union([t.literal("auto"), t.number])),
gasMultiplier: optional(t.number),
accounts: optional(t.array(BuidlerNetworkAccount)),
blockGasLimit: optional(t.number),
throwOnTransactionFailures: optional(t.boolean),
throwOnCallFailures: optional(t.boolean),
loggingEnabled: optional(t.boolean),
allowUnlimitedContractSize: optional(t.boolean),
initialDate: optional(t.string),
});
const HDAccountsConfig = t.type({
mnemonic: t.string,
initialIndex: optional(t.number),
count: optional(t.number),
path: optional(t.string),
});
const OtherAccountsConfig = t.type({
type: t.string,
});
const NetworkConfigAccounts = t.union([
t.literal("remote"),
t.array(t.string),
HDAccountsConfig,
OtherAccountsConfig,
]);
const HttpHeaders = t.record(t.string, t.string, "httpHeaders");
const HttpNetworkConfig = t.type({
chainId: optional(t.number),
from: optional(t.string),
gas: optional(t.union([t.literal("auto"), t.number])),
gasPrice: optional(t.union([t.literal("auto"), t.number])),
gasMultiplier: optional(t.number),
url: optional(t.string),
accounts: optional(NetworkConfigAccounts),
httpHeaders: optional(HttpHeaders),
});
const NetworkConfig = t.union([BuidlerNetworkConfig, HttpNetworkConfig]);
const Networks = t.record(t.string, NetworkConfig);
const ProjectPaths = t.type({
root: optional(t.string),
cache: optional(t.string),
artifacts: optional(t.string),
sources: optional(t.string),
tests: optional(t.string),
});
const EVMVersion = t.string;
const SolcOptimizerConfig = t.type({
enabled: optional(t.boolean),
runs: optional(t.number),
});
const SolcConfig = t.type({
version: optional(t.string),
optimizer: optional(SolcOptimizerConfig),
evmVersion: optional(EVMVersion),
});
const AnalyticsConfig = t.type({
enabled: optional(t.boolean),
});
const BuidlerConfig = t.type(
{
defaultNetwork: optional(t.string),
networks: optional(Networks),
paths: optional(ProjectPaths),
solc: optional(SolcConfig),
analytics: optional(AnalyticsConfig),
},
"BuidlerConfig"
);
/**
* Validates the config, throwing a BuidlerError if invalid.
* @param config
*/
export function validateConfig(config: any) {
const errors = getValidationErrors(config);
if (errors.length === 0) {
return;
}
let errorList = errors.join("\n * ");
errorList = ` * ${errorList}`;
throw new BuidlerError(ERRORS.GENERAL.INVALID_CONFIG, { errors: errorList });
}
export function getValidationErrors(config: any): string[] {
const errors = [];
// These can't be validated with io-ts
if (config !== undefined && typeof config.networks === "object") {
const buidlerNetwork = config.networks[BUIDLEREVM_NETWORK_NAME];
if (buidlerNetwork !== undefined) {
if (
buidlerNetwork.hardfork !== undefined &&
!BUIDLEREVM_SUPPORTED_HARDFORKS.includes(buidlerNetwork.hardfork)
) {
errors.push(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.hardfork is not supported. Use one of ${BUIDLEREVM_SUPPORTED_HARDFORKS.join(
", "
)}`
);
}
if (
buidlerNetwork.allowUnlimitedContractSize !== undefined &&
typeof buidlerNetwork.allowUnlimitedContractSize !== "boolean"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.allowUnlimitedContractSize`,
buidlerNetwork.allowUnlimitedContractSize,
"boolean | undefined"
)
);
}
if (
buidlerNetwork.initialDate !== undefined &&
typeof buidlerNetwork.initialDate !== "string"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.initialDate`,
buidlerNetwork.initialDate,
"string | undefined"
)
);
}
if (
buidlerNetwork.throwOnTransactionFailures !== undefined &&
typeof buidlerNetwork.throwOnTransactionFailures !== "boolean"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.throwOnTransactionFailures`,
buidlerNetwork.throwOnTransactionFailures,
"boolean | undefined"
)
);
}
if (
buidlerNetwork.throwOnCallFailures !== undefined &&
typeof buidlerNetwork.throwOnCallFailures !== "boolean"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.throwOnCallFailures`,
buidlerNetwork.throwOnCallFailures,
"boolean | undefined"
)
);
}
if (buidlerNetwork.url !== undefined) {
errors.push(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME} can't have an url`
);
}
if (
buidlerNetwork.blockGasLimit !== undefined &&
typeof buidlerNetwork.blockGasLimit !== "number"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.blockGasLimit`,
buidlerNetwork.blockGasLimit,
"number | undefined"
)
);
}
if (
buidlerNetwork.chainId !== undefined &&
typeof buidlerNetwork.chainId !== "number"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.chainId`,
buidlerNetwork.chainId,
"number | undefined"
)
);
}
if (
buidlerNetwork.loggingEnabled !== undefined &&
typeof buidlerNetwork.loggingEnabled !== "boolean"
) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.loggingEnabled`,
buidlerNetwork.loggingEnabled,
"boolean | undefined"
)
);
}
if (buidlerNetwork.accounts !== undefined) {
if (Array.isArray(buidlerNetwork.accounts)) {
for (const account of buidlerNetwork.accounts) {
if (typeof account.privateKey !== "string") {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.accounts[].privateKey`,
account.privateKey,
"string"
)
);
}
if (typeof account.balance !== "string") {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.accounts[].balance`,
account.balance,
"string"
)
);
}
}
} else {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.accounts`,
buidlerNetwork.accounts,
"[{privateKey: string, balance: string}] | undefined"
)
);
}
}
}
for (const [networkName, netConfig] of Object.entries<any>(
config.networks
)) {
if (networkName === BUIDLEREVM_NETWORK_NAME) {
continue;
}
if (networkName === "localhost" && netConfig.url === undefined) {
continue;
}
if (typeof netConfig.url !== "string") {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${networkName}.url`,
netConfig.url,
"string"
)
);
}
const netConfigResult = HttpNetworkConfig.decode(netConfig);
if (netConfigResult.isLeft()) {
errors.push(
getErrorMessage(
`BuidlerConfig.networks.${networkName}`,
netConfig,
"HttpNetworkConfig"
)
);
}
}
}
// io-ts can get confused if there are errors that it can't understand.
// Especially around BuidlerEVM's config. It will treat it as an HTTPConfig,
// and may give a loot of errors.
if (errors.length > 0) {
return errors;
}
const result = BuidlerConfig.decode(config);
if (result.isRight()) {
return errors;
}
const ioTsErrors = DotPathReporter.report(result);
return [...errors, ...ioTsErrors];
}