Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Centralize error descriptions (PR 1) #479

Merged
merged 8 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions packages/core/src/build-module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IgnitionError } from "./errors";
import { ERRORS } from "./errors-list";
import { ModuleConstructor } from "./internal/module-builder";
import { isValidIgnitionIdentifier } from "./internal/utils/identifier-validators";
import { IgnitionModule, IgnitionModuleResult } from "./types/module";
Expand All @@ -23,17 +24,17 @@ export function buildModule<
moduleDefintionFunction: (m: IgnitionModuleBuilder) => IgnitionModuleResultsT
): IgnitionModule<ModuleIdT, ContractNameT, IgnitionModuleResultsT> {
if (typeof moduleId !== "string") {
throw new IgnitionError(`\`moduleId\` must be a string`);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_ID);
}

if (!isValidIgnitionIdentifier(moduleId)) {
throw new IgnitionError(
`The moduleId "${moduleId}" contains banned characters, ids can only contain alphanumerics or underscores`
);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_ID_CHARACTERS, {
moduleId,
});
}

if (typeof moduleDefintionFunction !== "function") {
throw new IgnitionError(`\`moduleDefintionFunction\` must be a function`);
throw new IgnitionError(ERRORS.MODULE.INVALID_MODULE_DEFINITION_FUNCTION);
}

const constructor = new ModuleConstructor();
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IgnitionValidationError } from "./errors";
import { IgnitionError } from "./errors";
import { ERRORS } from "./errors-list";
import {
DEFAULT_AUTOMINE_REQUIRED_CONFIRMATIONS,
defaultConfig,
Expand Down Expand Up @@ -83,9 +84,9 @@ export async function deploy<

if (defaultSender !== undefined) {
if (!accounts.includes(defaultSender)) {
throw new IgnitionValidationError(
`Default sender ${defaultSender} is not part of the provided accounts`
);
throw new IgnitionError(ERRORS.VALIDATION.INVALID_DEFAULT_SENDER, {
defaultSender,
});
}
} else {
defaultSender = getDefaultSender(accounts);
Expand Down
322 changes: 322 additions & 0 deletions packages/core/src/errors-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
export const ERROR_PREFIX = "IGN";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this file is basically copied structurally from the similar files in Hardhat.


/**
* ErrorDescriptor is a type that describes an error.
* It's used to generate error codes and messages.
*
* @beta
*/
export interface ErrorDescriptor {
number: number;
// Message can use templates. See applyErrorMessageTemplate
message: string;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Hardhat has extra fields here, but I figured we start with the minimum and can always add more later.


export function getErrorCode(error: ErrorDescriptor): string {
return `${ERROR_PREFIX}${error.number}`;
}

export const ERROR_RANGES: {
[category in keyof typeof ERRORS]: {
min: number;
max: number;
title: string;
};
} = {
GENERAL: {
min: 1,
max: 99,
title: "General errors",
},
INTERNAL: {
min: 100,
max: 199,
title: "Internal Ignition errors",
},
MODULE: {
min: 200,
max: 299,
title: "Module related errors",
},
SERIALIZATION: {
min: 300,
max: 399,
title: "Serialization errors",
},
EXECUTION: {
min: 400,
max: 499,
title: "Execution errors",
},
RECONCILIATION: {
min: 500,
max: 599,
title: "Reconciliation errors",
},
WIPE: {
min: 600,
max: 699,
title: "Wipe errors",
},
VALIDATION: {
min: 700,
max: 799,
title: "Validation errors",
},
};

export const ERRORS = {
GENERAL: {
ASSERTION_ERROR: {
number: 1,
message: "Internal Ignition invariant was violated: %description%",
},
UNSUPPORTED_DECODE: {
number: 2,
message: "Ignition can't decode ethers.js value of type %type%: %value%",
},
},
INTERNAL: {
TEMPLATE_INVALID_VARIABLE_NAME: {
number: 100,
message:
"Variable names can only include ascii letters and numbers, and start with a letter, but got %variable%",
},
TEMPLATE_VARIABLE_NOT_FOUND: {
number: 101,
message: "Variable %variable%'s tag not present in the template",
},
TEMPLATE_VALUE_CONTAINS_VARIABLE_TAG: {
number: 102,
message:
"Template values can't include variable tags, but %variable%'s value includes one",
},
},
MODULE: {
INVALID_MODULE_ID: {
number: 200,
message: "Module id must be a string",
},
INVALID_MODULE_ID_CHARACTERS: {
number: 201,
message:
'The moduleId "%moduleId%" contains banned characters, ids can only contain alphanumerics or underscores',
},
INVALID_MODULE_DEFINITION_FUNCTION: {
number: 202,
message: "Module definition function must be a function",
},
ASYNC_MODULE_DEFINITION_FUNCTION: {
number: 203,
message:
"The callback passed to 'buildModule' for %moduleDefinitionId% returns a Promise; async callbacks are not allowed in 'buildModule'.",
},
},
SERIALIZATION: {
INVALID_FUTURE_ID: {
number: 300,
message: "Unable to lookup future during deserialization: %futureId%",
},
INVALID_FUTURE_TYPE: {
number: 301,
message: "Invalid FutureType %type% as serialized argument",
},
LOOKAHEAD_NOT_FOUND: {
number: 302,
message: "Lookahead value %key% missing",
},
},
EXECUTION: {
FUTURE_NOT_FOUND: {
number: 400,
message: "Could not locate future id from batching",
},
DROPPED_TRANSACTION: {
number: 401,
message:
"Error while executing %futureId%: all the transactions of its network interaction %networkInteractionId% were dropped. Please try rerunning Ignition.",
},
INVALID_JSON_RPC_RESPONSE: {
number: 402,
message: "Invalid JSON-RPC response for %method%: %response%",
},
WAITING_FOR_CONFIRMATIONS: {
number: 403,
message:
"You have sent transactions from %sender%. Please wait until they get %requiredConfirmations% confirmations before running Ignition again.",
},
WAITING_FOR_NONCE: {
number: 404,
message:
"You have sent transactions from %sender% with nonce %nonce%. Please wait until they get %requiredConfirmations% confirmations before running Ignition again.",
},
INVALID_NONCE: {
number: 405,
message:
"The next nonce for %sender% should be %expectedNonce%, but is %pendingCount%. Please make sure not to send transactions from %sender% while running this deployment and try again.",
},
},
RECONCILIATION: {
INVALID_EXECUTION_STATUS: {
number: 500,
message: "Unsupported execution status: %status%",
},
},
WIPE: {
UNINITIALIZED_DEPLOYMENT: {
number: 600,
message:
"Cannot wipe %futureId% as the deployment hasn't been intialialized yet",
},
NO_STATE_FOR_FUTURE: {
number: 601,
message: "Cannot wipe %futureId% as no state recorded against it",
},
DEPENDENT_FUTURES: {
number: 602,
message: `Cannot wipe %futureId% as there are dependent futures that have already started: %dependents%`,
},
},
VALIDATION: {
INVALID_DEFAULT_SENDER: {
number: 700,
message:
"Default sender %defaultSender% is not part of the provided accounts",
},
MISSING_EMITTER: {
number: 701,
message:
"`options.emitter` must be provided when reading an event from a SendDataFuture",
},
INVALID_MODULE: {
number: 702,
message: "Module validation failed with reason: %message%",
},
INVALID_CONSTRUCTOR_ARGS_LENGTH: {
number: 703,
message:
"The constructor of the contract '%contractName%' expects %expectedArgsLength% arguments but %argsLength% were given",
},
INVALID_FUNCTION_ARGS_LENGTH: {
number: 704,
message:
"Function %functionName% in contract %contractName% expects %expectedLength% arguments but %argsLength% were given",
},
INVALID_STATIC_CALL: {
number: 705,
message:
"Function %functionName% in contract %contractName% is not 'pure' or 'view' and cannot be statically called",
},
INDEXED_EVENT_ARG: {
number: 706,
message:
"Indexed argument %argument% of event %eventName% of contract %contractName% is not stored in the receipt, but its hash is, so you can't read it.",
},
INVALID_OVERLOAD_NAME: {
number: 707,
message: "Invalid %eventOrFunction% name '%name%'",
},
OVERLOAD_NOT_FOUND: {
number: 708,
message:
"%eventOrFunction% '%name%' not found in contract %contractName%",
},
REQUIRE_BARE_NAME: {
number: 709,
message:
"%eventOrFunction% name '%name%' used for contract %contractName%, but it's not overloaded. Use '%bareName%' instead.",
},
OVERLOAD_NAME_REQUIRED: {
number: 710,
message: `%eventOrFunction% '%name%' is overloaded in contract %contractName%. Please use one of these names instead:

%normalizedNameList%`,
},
INVALID_OVERLOAD_GIVEN: {
number: 711,
message: `%eventOrFunction% '%name%' is not a valid overload of '%bareName%' in contract %contractName%. Please use one of these names instead:

%normalizedNameList%`,
},
EVENT_ARG_NOT_FOUND: {
number: 712,
message:
"Event %eventName% of contract %contractName% has no argument named %argument%",
},
INVALID_EVENT_ARG_INDEX: {
number: 713,
message:
"Event %eventName% of contract %contractName% has only %expectedLength% arguments, but argument %argument% was requested",
},
FUNCTION_ARG_NOT_FOUND: {
number: 714,
message:
"Function %functionName% of contract %contractName% has no return value named %argument%",
},
INVALID_FUNCTION_ARG_INDEX: {
number: 715,
message:
"Function %functionName% of contract %contractName% has only %expectedLength% return values, but value %argument% was requested",
},
MISSING_LIBRARIES: {
number: 716,
message:
"Invalid libraries for contract %contractName%: The following libraries are missing: %fullyQualifiedNames%",
},
CONFLICTING_LIBRARY_NAMES: {
number: 717,
message:
"Invalid libraries for contract %contractName%: The names '%inputName%' and '%libName%' clash with each other, please use qualified names for both.",
},
INVALID_LIBRARY_NAME: {
number: 718,
message: "Invalid library name %libraryName% for contract %contractName%",
},
LIBRARY_NOT_NEEDED: {
number: 719,
message:
"Invalid library %libraryName% for contract %contractName%: this library is not needed by this contract.",
},
AMBIGUOUS_LIBRARY_NAME: {
number: 720,
message: `Invalid libraries for contract %contractName%: The name "%libraryName%" is ambiguous, please use one of the following fully qualified names: %fullyQualifiedNames%`,
},
INVALID_LIBRARY_ADDRESS: {
number: 721,
message: `Invalid address %address% for library %libraryName% of contract %contractName%`,
},
NEGATIVE_ACCOUNT_INDEX: {
number: 722,
message: "Account index cannot be a negative number",
},
ACCOUNT_INDEX_TOO_HIGH: {
number: 723,
message:
"Requested account index '%accountIndex%' is greater than the total number of available accounts '%accountsLength%'",
},
INVALID_ARTIFACT: {
number: 724,
message: "Artifact for contract '%contractName%' is invalid",
},
MISSING_MODULE_PARAMETER: {
number: 725,
message: "Module parameter '%name%' requires a value but was given none",
},
INVALID_MODULE_PARAMETER_TYPE: {
number: 726,
message: `Module parameter '%name%' must be of type '%expectedType%' but is '%actualType%'`,
},
},
};

/**
* Setting the type of ERRORS to a map let us access undefined ones. Letting it
* be a literal doesn't enforce that its values are of type ErrorDescriptor.
*
* We let it be a literal, and use this variable to enforce the types
*/
const _PHONY_VARIABLE_TO_FORCE_ERRORS_TO_BE_OF_TYPE_ERROR_DESCRIPTOR: {
[category: string]: {
[name: string]: ErrorDescriptor;
};
} = ERRORS;
Loading
Loading