diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 887800965..265bc0880 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,7 +7,7 @@ export { wipe } from "./new-api/wipe"; export { StoredDeploymentSerializer } from "./new-api/stored-deployment-serializer"; export * from "./new-api/type-guards"; export * from "./new-api/types/artifact"; -export * from "./new-api/types/deployer"; +export * from "./new-api/types/deploy"; export * from "./new-api/types/module"; export * from "./new-api/types/module-builder"; export * from "./new-api/types/provider"; diff --git a/packages/core/src/new-api/deploy.ts b/packages/core/src/new-api/deploy.ts index 21cf74593..db81a5957 100644 --- a/packages/core/src/new-api/deploy.ts +++ b/packages/core/src/new-api/deploy.ts @@ -17,8 +17,8 @@ import { DeployConfig, DeploymentParameters, DeploymentResult, -} from "./types/deployer"; -import { IgnitionModule } from "./types/module"; +} from "./types/deploy"; +import { IgnitionModule, IgnitionModuleResult } from "./types/module"; import { EIP1193Provider } from "./types/provider"; /** @@ -26,7 +26,11 @@ import { EIP1193Provider } from "./types/provider"; * * @beta */ -export async function deploy({ +export async function deploy< + ModuleIdT extends string, + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult +>({ config = {}, artifactResolver, provider, @@ -41,12 +45,16 @@ export async function deploy({ artifactResolver: ArtifactResolver; provider: EIP1193Provider; deploymentDir?: string; - ignitionModule: IgnitionModule; + ignitionModule: IgnitionModule< + ModuleIdT, + ContractNameT, + IgnitionModuleResultsT + >; deploymentParameters: DeploymentParameters; accounts: string[]; verbose: boolean; defaultSender?: string; -}): Promise { +}): Promise> { await validateStageOne(ignitionModule, artifactResolver); if (defaultSender !== undefined) { diff --git a/packages/core/src/new-api/internal/defaultConfig.ts b/packages/core/src/new-api/internal/defaultConfig.ts index a0fa08040..e357867e4 100644 --- a/packages/core/src/new-api/internal/defaultConfig.ts +++ b/packages/core/src/new-api/internal/defaultConfig.ts @@ -1,4 +1,4 @@ -import { DeployConfig } from "../types/deployer"; +import { DeployConfig } from "../types/deploy"; /** * Ignitions default deployment configuration values. diff --git a/packages/core/src/new-api/internal/deployer.ts b/packages/core/src/new-api/internal/deployer.ts index a3daaea20..479435f8c 100644 --- a/packages/core/src/new-api/internal/deployer.ts +++ b/packages/core/src/new-api/internal/deployer.ts @@ -1,14 +1,16 @@ -import type { IgnitionModule } from "../types/module"; +import type { IgnitionModule, IgnitionModuleResult } from "../types/module"; -import { IgnitionError } from "../../errors"; import { isContractFuture } from "../type-guards"; import { ArtifactResolver } from "../types/artifact"; import { DeployConfig, DeploymentParameters, DeploymentResult, - DeploymentResultContracts, -} from "../types/deployer"; + DeploymentResultType, + ExecutionErrorDeploymentResult, + ReconciliationErrorDeploymentResult, + SuccessfulDeploymentResult, +} from "../types/deploy"; import { Batcher } from "./batcher"; import { DeploymentLoader } from "./deployment-loader/types"; @@ -19,15 +21,18 @@ import { import { ExecutionEngine } from "./new-execution/execution-engine"; import { JsonRpcClient } from "./new-execution/jsonrpc-client"; import { DeploymentState } from "./new-execution/types/deployment-state"; +import { ExecutionResultType } from "./new-execution/types/execution-result"; import { + CallExecutionState, ContractAtExecutionState, DeploymentExecutionState, ExecutionSateType, ExecutionState, ExecutionStatus, + SendDataExecutionState, + StaticCallExecutionState, } from "./new-execution/types/execution-state"; import { ExecutionStrategy } from "./new-execution/types/execution-strategy"; -import { findDeployedContracts } from "./new-execution/views/find-deployed-contracts"; import { Reconciler } from "./reconciliation/reconciler"; import { assertIgnitionInvariant } from "./utils/assertions"; import { getFuturesFromModule } from "./utils/get-futures-from-module"; @@ -52,19 +57,31 @@ export class Deployer { ); } - public async deploy( - ignitionModule: IgnitionModule, + public async deploy< + ModuleIdT extends string, + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult + >( + ignitionModule: IgnitionModule< + ModuleIdT, + ContractNameT, + IgnitionModuleResultsT + >, deploymentParameters: DeploymentParameters, accounts: string[], defaultSender: string - ): Promise { - await validateStageTwo( + ): Promise> { + const validationResult = await validateStageTwo( ignitionModule, this._artifactResolver, deploymentParameters, accounts ); + if (validationResult !== null) { + return validationResult; + } + let deploymentState = await this._getOrInitializeDeploymentState(); const contracts = @@ -98,11 +115,23 @@ export class Deployer { ); if (reconciliationResult.reconciliationFailures.length > 0) { - const failures = reconciliationResult.reconciliationFailures - .map((rf) => ` ${rf.futureId} - ${rf.failure}`) - .join("\n"); + const errors: ReconciliationErrorDeploymentResult["errors"] = {}; + + for (const { + futureId, + failure, + } of reconciliationResult.reconciliationFailures) { + if (errors[futureId] === undefined) { + errors[futureId] = []; + } + + errors[futureId].push(failure); + } - throw new IgnitionError(`Reconciliation failed\n\n${failures}`); + return { + type: DeploymentResultType.RECONCILIATION_ERROR, + errors, + }; } if (reconciliationResult.missingExecutedFutures.length > 0) { @@ -131,47 +160,38 @@ export class Deployer { defaultSender ); - return this._getDeploymentResult( - deploymentState, - this._deploymentLoader, - ignitionModule - ); + return this._getDeploymentResult(deploymentState, ignitionModule); } - private async _getDeploymentResult( + private async _getDeploymentResult< + ModuleIdT extends string, + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult + >( deploymentState: DeploymentState, - deploymentLoader: DeploymentLoader, - module: IgnitionModule - ): Promise { - if ( - Object.values(deploymentState.executionStates).some( - (ex) => ex.status !== ExecutionStatus.SUCCESS - ) - ) { - // TODO: deal with failure cases and error cases - throw new Error("TBD: error result against future"); - } - - const deployedContracts: DeploymentResultContracts = {}; - - for (const { - futureId, - contractName, - contractAddress, - } of findDeployedContracts(deploymentState)) { - const artifact = await deploymentLoader.loadArtifact(futureId); - - deployedContracts[futureId] = { - contractName, - contractAddress, - artifact, - }; + module: IgnitionModule + ): Promise> { + if (!this._isSuccessful(deploymentState)) { + return this._getExecutionErrorResult(deploymentState); } return { - status: "success", - contracts: deployedContracts, - module, + type: DeploymentResultType.SUCCESSFUL_DEPLOYMENT, + contracts: Object.fromEntries( + Object.entries(module.results).map(([name, contractFuture]) => [ + name, + { + id: contractFuture.id, + contractName: contractFuture.contractName, + address: getContractAddress( + deploymentState.executionStates[contractFuture.id] + ), + }, + ]) + ) as SuccessfulDeploymentResult< + ContractNameT, + IgnitionModuleResultsT + >["contracts"], }; } @@ -190,4 +210,94 @@ export class Deployer { return deploymentState; } + + private _isSuccessful(deploymentState: DeploymentState): boolean { + return Object.values(deploymentState.executionStates).every( + (ex) => ex.status === ExecutionStatus.SUCCESS + ); + } + + private _getExecutionErrorResult( + deploymentState: DeploymentState + ): ExecutionErrorDeploymentResult { + return { + type: DeploymentResultType.EXECUTION_ERROR, + started: Object.values(deploymentState.executionStates) + .filter((ex) => ex.status === ExecutionStatus.STARTED) + .map((ex) => ex.id), + successful: Object.values(deploymentState.executionStates) + .filter((ex) => ex.status === ExecutionStatus.SUCCESS) + .map((ex) => ex.id), + timedOut: Object.values(deploymentState.executionStates) + .filter(canTimeout) + .filter((ex) => ex.status === ExecutionStatus.TIMEOUT) + .map((ex) => ({ + futureId: ex.id, + executionId: ex.networkInteractions.at(-1)!.id, + })), + failed: Object.values(deploymentState.executionStates) + .filter(canFail) + .filter((ex) => ex.status === ExecutionStatus.FAILED) + .map((ex) => ({ + futureId: ex.id, + executionId: ex.networkInteractions.at(-1)!.id, + error: "TODO: format the execution result into a string", + })), + }; + } +} + +// TODO: Does this exist anywhere else? It's in fact just checking if it sends txs +function canTimeout( + exState: ExecutionState +): exState is + | DeploymentExecutionState + | CallExecutionState + | SendDataExecutionState { + return ( + exState.type === ExecutionSateType.DEPLOYMENT_EXECUTION_STATE || + exState.type === ExecutionSateType.CALL_EXECUTION_STATE || + exState.type === ExecutionSateType.SEND_DATA_EXECUTION_STATE + ); +} + +// TODO: Does this exist anywhere else? It's in fact just checking if has network interactions +function canFail( + exState: ExecutionState +): exState is + | DeploymentExecutionState + | CallExecutionState + | SendDataExecutionState + | StaticCallExecutionState { + return ( + exState.type === ExecutionSateType.DEPLOYMENT_EXECUTION_STATE || + exState.type === ExecutionSateType.CALL_EXECUTION_STATE || + exState.type === ExecutionSateType.SEND_DATA_EXECUTION_STATE || + exState.type === ExecutionSateType.STATIC_CALL_EXECUTION_STATE + ); +} + +// TODO: Does this exist somewhere else? +function getContractAddress(exState: ExecutionState): string { + assertIgnitionInvariant( + exState.type === ExecutionSateType.DEPLOYMENT_EXECUTION_STATE || + exState.type === ExecutionSateType.CONTRACT_AT_EXECUTION_STATE, + `Execution state ${exState.id} should be a deployment or contract at execution state` + ); + + assertIgnitionInvariant( + exState.status === ExecutionStatus.SUCCESS, + `Cannot get contract address from execution state ${exState.id} because it is not successful` + ); + + if (exState.type === ExecutionSateType.CONTRACT_AT_EXECUTION_STATE) { + return exState.contractAddress; + } + + assertIgnitionInvariant( + exState.result?.type === ExecutionResultType.SUCCESS, + `Cannot get contract address from execution state ${exState.id} because it is not successful` + ); + + return exState.result.address; } diff --git a/packages/core/src/new-api/internal/new-execution/execution-engine.ts b/packages/core/src/new-api/internal/new-execution/execution-engine.ts index 1eecc6195..6afb29537 100644 --- a/packages/core/src/new-api/internal/new-execution/execution-engine.ts +++ b/packages/core/src/new-api/internal/new-execution/execution-engine.ts @@ -1,6 +1,6 @@ import { IgnitionError } from "../../../errors"; import { ArtifactResolver } from "../../types/artifact"; -import { DeploymentParameters } from "../../types/deployer"; +import { DeploymentParameters } from "../../types/deploy"; import { Future, IgnitionModule, diff --git a/packages/core/src/new-api/internal/new-execution/future-processor/future-processor.ts b/packages/core/src/new-api/internal/new-execution/future-processor/future-processor.ts index 5264b961e..c77a784ca 100644 --- a/packages/core/src/new-api/internal/new-execution/future-processor/future-processor.ts +++ b/packages/core/src/new-api/internal/new-execution/future-processor/future-processor.ts @@ -1,5 +1,5 @@ import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { Future } from "../../../types/module"; import { DeploymentLoader } from "../../deployment-loader/types"; import { assertIgnitionInvariant } from "../../utils/assertions"; diff --git a/packages/core/src/new-api/internal/new-execution/future-processor/helpers/build-initialize-message-for.ts b/packages/core/src/new-api/internal/new-execution/future-processor/helpers/build-initialize-message-for.ts index e7f090b8d..9679a73a6 100644 --- a/packages/core/src/new-api/internal/new-execution/future-processor/helpers/build-initialize-message-for.ts +++ b/packages/core/src/new-api/internal/new-execution/future-processor/helpers/build-initialize-message-for.ts @@ -1,4 +1,4 @@ -import { DeploymentParameters } from "../../../../types/deployer"; +import { DeploymentParameters } from "../../../../types/deploy"; import { Future, FutureType } from "../../../../types/module"; import { DeploymentLoader } from "../../../deployment-loader/types"; import { DeploymentState } from "../../types/deployment-state"; diff --git a/packages/core/src/new-api/internal/new-execution/future-processor/helpers/future-resolvers.ts b/packages/core/src/new-api/internal/new-execution/future-processor/helpers/future-resolvers.ts index 8a0ccbd57..8477edc5f 100644 --- a/packages/core/src/new-api/internal/new-execution/future-processor/helpers/future-resolvers.ts +++ b/packages/core/src/new-api/internal/new-execution/future-processor/helpers/future-resolvers.ts @@ -1,7 +1,7 @@ import { isAddress } from "ethers"; import { isModuleParameterRuntimeValue } from "../../../../type-guards"; -import { DeploymentParameters } from "../../../../types/deployer"; +import { DeploymentParameters } from "../../../../types/deploy"; import { AccountRuntimeValue, AddressResolvableFuture, diff --git a/packages/core/src/new-api/internal/reconciliation/reconciler.ts b/packages/core/src/new-api/internal/reconciliation/reconciler.ts index b31a4a4bc..a493b0588 100644 --- a/packages/core/src/new-api/internal/reconciliation/reconciler.ts +++ b/packages/core/src/new-api/internal/reconciliation/reconciler.ts @@ -1,5 +1,5 @@ import { ArtifactResolver } from "../../types/artifact"; -import { DeploymentParameters } from "../../types/deployer"; +import { DeploymentParameters } from "../../types/deploy"; import { Future, IgnitionModule } from "../../types/module"; import { DeploymentLoader } from "../deployment-loader/types"; import { DeploymentState } from "../new-execution/types/deployment-state"; diff --git a/packages/core/src/new-api/internal/reconciliation/types.ts b/packages/core/src/new-api/internal/reconciliation/types.ts index 177e6b5a4..7b9a24ab7 100644 --- a/packages/core/src/new-api/internal/reconciliation/types.ts +++ b/packages/core/src/new-api/internal/reconciliation/types.ts @@ -1,5 +1,5 @@ import { ArtifactResolver } from "../../types/artifact"; -import { DeploymentParameters } from "../../types/deployer"; +import { DeploymentParameters } from "../../types/deploy"; import { Future } from "../../types/module"; import { DeploymentLoader } from "../deployment-loader/types"; import { DeploymentState } from "../new-execution/types/deployment-state"; diff --git a/packages/core/src/new-api/internal/utils/resolve-module-parameter.ts b/packages/core/src/new-api/internal/utils/resolve-module-parameter.ts index 326e3a6c2..be216af46 100644 --- a/packages/core/src/new-api/internal/utils/resolve-module-parameter.ts +++ b/packages/core/src/new-api/internal/utils/resolve-module-parameter.ts @@ -1,4 +1,4 @@ -import { DeploymentParameters } from "../../types/deployer"; +import { DeploymentParameters } from "../../types/deploy"; import { ModuleParameterRuntimeValue, ModuleParameterType, diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractAt.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractAt.ts index 7daa890cd..ef973ed47 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractAt.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractAt.ts @@ -1,7 +1,7 @@ import { IgnitionValidationError } from "../../../../errors"; import { isModuleParameterRuntimeValue } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { ArtifactContractAtFuture } from "../../../types/module"; export async function validateArtifactContractAt( diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractDeployment.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractDeployment.ts index 5d96b5cb7..d2189ed14 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractDeployment.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactContractDeployment.ts @@ -4,7 +4,7 @@ import { isModuleParameterRuntimeValue, } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { ArtifactContractDeploymentFuture } from "../../../types/module"; import { retrieveNestedRuntimeValues, diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactLibraryDeployment.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactLibraryDeployment.ts index d3569ca0d..fd1ebe1a9 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactLibraryDeployment.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateArtifactLibraryDeployment.ts @@ -1,6 +1,6 @@ import { isAccountRuntimeValue } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { ArtifactLibraryDeploymentFuture } from "../../../types/module"; import { validateLibraryNames } from "../../new-execution/libraries"; import { validateAccountRuntimeValue } from "../utils"; diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractAt.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractAt.ts index b1c56cfa8..75e295d81 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractAt.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractAt.ts @@ -1,7 +1,7 @@ import { IgnitionValidationError } from "../../../../errors"; import { isModuleParameterRuntimeValue } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { NamedContractAtFuture } from "../../../types/module"; export async function validateNamedContractAt( diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractCall.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractCall.ts index cc09f99bd..b561b4c6d 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractCall.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractCall.ts @@ -4,7 +4,7 @@ import { isModuleParameterRuntimeValue, } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { NamedContractCallFuture } from "../../../types/module"; import { retrieveNestedRuntimeValues, diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractDeployment.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractDeployment.ts index b6915eedd..0585f8f43 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractDeployment.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedContractDeployment.ts @@ -4,7 +4,7 @@ import { isModuleParameterRuntimeValue, } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { NamedContractDeploymentFuture } from "../../../types/module"; import { retrieveNestedRuntimeValues, diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedLibraryDeployment.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedLibraryDeployment.ts index 507936164..8787fd30d 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedLibraryDeployment.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedLibraryDeployment.ts @@ -1,6 +1,6 @@ import { isAccountRuntimeValue } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { NamedLibraryDeploymentFuture } from "../../../types/module"; import { validateAccountRuntimeValue } from "../utils"; diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedStaticCall.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedStaticCall.ts index e535ac83c..e2a219514 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateNamedStaticCall.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateNamedStaticCall.ts @@ -4,7 +4,7 @@ import { isModuleParameterRuntimeValue, } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { NamedStaticCallFuture } from "../../../types/module"; import { retrieveNestedRuntimeValues, diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateReadEventArgument.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateReadEventArgument.ts index 4a0dc4d8c..e6ad4067a 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateReadEventArgument.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateReadEventArgument.ts @@ -1,5 +1,5 @@ import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { ReadEventArgumentFuture } from "../../../types/module"; export async function validateReadEventArgument( diff --git a/packages/core/src/new-api/internal/validation/stageTwo/validateSendData.ts b/packages/core/src/new-api/internal/validation/stageTwo/validateSendData.ts index e321d00ee..a2d05e914 100644 --- a/packages/core/src/new-api/internal/validation/stageTwo/validateSendData.ts +++ b/packages/core/src/new-api/internal/validation/stageTwo/validateSendData.ts @@ -4,7 +4,7 @@ import { isModuleParameterRuntimeValue, } from "../../../type-guards"; import { ArtifactResolver } from "../../../types/artifact"; -import { DeploymentParameters } from "../../../types/deployer"; +import { DeploymentParameters } from "../../../types/deploy"; import { SendDataFuture } from "../../../types/module"; import { validateAccountRuntimeValue } from "../utils"; diff --git a/packages/core/src/new-api/internal/validation/validateStageTwo.ts b/packages/core/src/new-api/internal/validation/validateStageTwo.ts index c16719301..a6b6d8e27 100644 --- a/packages/core/src/new-api/internal/validation/validateStageTwo.ts +++ b/packages/core/src/new-api/internal/validation/validateStageTwo.ts @@ -1,6 +1,12 @@ +import { IgnitionValidationError } from "../../../errors"; import { ArtifactResolver } from "../../types/artifact"; -import { DeploymentParameters } from "../../types/deployer"; -import { FutureType, IgnitionModule } from "../../types/module"; +import { + DeploymentParameters, + DeploymentResultType, + ValidationErrorDeploymentResult, +} from "../../types/deploy"; +import { Future, FutureType, IgnitionModule } from "../../types/module"; +import { assertIgnitionInvariant } from "../utils/assertions"; import { getFuturesFromModule } from "../utils/get-futures-from-module"; import { validateArtifactContractAt } from "./stageTwo/validateArtifactContractAt"; @@ -19,105 +25,122 @@ export async function validateStageTwo( artifactLoader: ArtifactResolver, deploymentParameters: DeploymentParameters, accounts: string[] -): Promise { +): Promise { const futures = getFuturesFromModule(module); - // originally, I wrote a getSubmodulesFromModule function similar to the one above - // that recursively retrieved all submodules regardless of how deeply nested they were. - // however, by taking only the top level submodules of the current depth and recursively - // validating each of those, we achieve the same effect. - const submodules = module.submodules; - for (const submodule of submodules) { - await validateStageTwo( - submodule, - artifactLoader, - deploymentParameters, - accounts - ); - } - for (const future of futures) { - switch (future.type) { - case FutureType.ARTIFACT_CONTRACT_DEPLOYMENT: - await validateArtifactContractDeployment( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.ARTIFACT_LIBRARY_DEPLOYMENT: - await validateArtifactLibraryDeployment( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.ARTIFACT_CONTRACT_AT: - await validateArtifactContractAt( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.NAMED_CONTRACT_DEPLOYMENT: - await validateNamedContractDeployment( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.NAMED_LIBRARY_DEPLOYMENT: - await validateNamedLibraryDeployment( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.NAMED_CONTRACT_AT: - await validateNamedContractAt( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.NAMED_CONTRACT_CALL: - await validateNamedContractCall( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.NAMED_STATIC_CALL: - await validateNamedStaticCall( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.READ_EVENT_ARGUMENT: - await validateReadEventArgument( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; - case FutureType.SEND_DATA: - await validateSendData( - future, - artifactLoader, - deploymentParameters, - accounts - ); - break; + try { + await _validateFuture( + future, + artifactLoader, + deploymentParameters, + accounts + ); + } catch (err) { + assertIgnitionInvariant( + err instanceof IgnitionValidationError, + `Expected an IgnitionValidationError when validating the future ${future.id}` + ); + + return { + type: DeploymentResultType.VALIDATION_ERROR, + errors: { + [future.id]: [err.message], + }, + }; } } + + // No validation errors + return null; +} + +async function _validateFuture( + future: Future, + artifactLoader: ArtifactResolver, + deploymentParameters: DeploymentParameters, + accounts: string[] +) { + switch (future.type) { + case FutureType.ARTIFACT_CONTRACT_DEPLOYMENT: + await validateArtifactContractDeployment( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.ARTIFACT_LIBRARY_DEPLOYMENT: + await validateArtifactLibraryDeployment( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.ARTIFACT_CONTRACT_AT: + await validateArtifactContractAt( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.NAMED_CONTRACT_DEPLOYMENT: + await validateNamedContractDeployment( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.NAMED_LIBRARY_DEPLOYMENT: + await validateNamedLibraryDeployment( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.NAMED_CONTRACT_AT: + await validateNamedContractAt( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.NAMED_CONTRACT_CALL: + await validateNamedContractCall( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.NAMED_STATIC_CALL: + await validateNamedStaticCall( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.READ_EVENT_ARGUMENT: + await validateReadEventArgument( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + case FutureType.SEND_DATA: + await validateSendData( + future, + artifactLoader, + deploymentParameters, + accounts + ); + break; + } } diff --git a/packages/core/src/new-api/types/deploy.ts b/packages/core/src/new-api/types/deploy.ts new file mode 100644 index 000000000..4178ab76e --- /dev/null +++ b/packages/core/src/new-api/types/deploy.ts @@ -0,0 +1,172 @@ +import { IgnitionModuleResult, ModuleParameters } from "./module"; + +/** + * Configuration options for the deployment. + * + * @beta + */ +export interface DeployConfig { + /** + * The interval, in milliseconds, between checks to see if a new block + * has been created + */ + blockPollingInterval: number; + + /** + * The amount of time, in milliseconds, to wait on a transaction before + * bumping its fees. + */ + timeBeforeBumpingFees: number; + + /** + * The maximum amount of times a transaction is bumped. + */ + maxFeeBumps: number; + + /** + * The number of block confirmations to wait before considering + * a transaction to be confirmed during Ignition execution. + */ + requiredConfirmations: number; +} + +/** + * The result of running a deployment. + * + * @beta + */ +export type DeploymentResult< + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult +> = + | ValidationErrorDeploymentResult + | ReconciliationErrorDeploymentResult + | ExecutionErrorDeploymentResult + | SuccessfulDeploymentResult; + +/** + * The different kinds of results that a deployment can produce. + * + * @beta + */ +export enum DeploymentResultType { + /** + * One or more futures failed validation. + */ + VALIDATION_ERROR = "VALIDATION_ERROR", + + /** + * One or more futures failed the reconciliation process with + * the previous state of the deployment. + */ + RECONCILIATION_ERROR = "RECONCILIATION_ERROR", + + /** + * One or more future's execution failed or timed out. + */ + EXECUTION_ERROR = "EXECUTION_ERROR", + + /** + * The entire deployment was successful. + */ + SUCCESSFUL_DEPLOYMENT = "SUCCESSFUL_DEPLOYMENT", +} + +/** + * A deployment result where one or more futures failed validation. + * + * @beta + */ +export interface ValidationErrorDeploymentResult { + type: DeploymentResultType.VALIDATION_ERROR; + + /** + * A map form future id to a list of all of its validation errors. + */ + errors: { + [futureId: string]: string[]; + }; +} + +/** + * A deployment result where one or more futures failed reconciliation. + * + * @beta + */ +export interface ReconciliationErrorDeploymentResult { + type: DeploymentResultType.RECONCILIATION_ERROR; + + /** + * A map form future id to a list of all of its reconciliation errors. + */ + errors: { + [futureId: string]: string[]; + }; +} + +/** + * A deployment result where one or more futures errored during execution or + * timed out. + * + * @beta + */ +export interface ExecutionErrorDeploymentResult { + type: DeploymentResultType.EXECUTION_ERROR; + + /** + * A list of all the future that have started executed but have not + * finished, neither successfully nor unsuccessfully. + */ + started: string[]; + + /** + * A list of all the future that have timed out and the id of the execution + * that timed out. + */ + timedOut: Array<{ futureId: string; executionId: number }>; + + /** + * A list of all the future that have failed and the id of the execution + * that failed, and a string explaining the failure. + */ + failed: Array<{ futureId: string; executionId: number; error: string }>; + + /** + * A list with the id of all the future that have successfully executed. + */ + successful: string[]; +} + +/** + * A deployment result where all of the futures of the module have completed + * successfully. + * + * @beta + */ +export interface SuccessfulDeploymentResult< + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult +> { + type: DeploymentResultType.SUCCESSFUL_DEPLOYMENT; + /** + * A map with the contracts returned by the deployed module. + * + * The contracts can be the result of a deployment or a contractAt call. + */ + contracts: { + [key in keyof IgnitionModuleResultsT]: { + id: string; + contractName: IgnitionModuleResultsT[key]["contractName"]; + address: string; + }; + }; +} + +/** + * An object containing a map of module ID's to their respective ModuleParameters. + * + * @beta + */ +export interface DeploymentParameters { + [moduleId: string]: ModuleParameters; +} diff --git a/packages/core/src/new-api/types/deployer.ts b/packages/core/src/new-api/types/deployer.ts deleted file mode 100644 index b9a26b9aa..000000000 --- a/packages/core/src/new-api/types/deployer.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Artifact } from "./artifact"; -import { - IgnitionModule, - IgnitionModuleResult, - ModuleParameters, -} from "./module"; - -/** - * Configuration options for the deployment. - * - * @beta - */ -export interface DeployConfig { - /** - * The interval, in milliseconds, between checks to see if a new block - * has been created - */ - blockPollingInterval: number; - - /** - * The amount of time, in milliseconds, to wait on a transaction before - * bumping its fees. - */ - timeBeforeBumpingFees: number; - - /** - * The maximum amount of times a transaction is bumped. - */ - maxFeeBumps: number; - - /** - * The number of block confirmations to wait before considering - * a transaction to be confirmed during Ignition execution. - */ - requiredConfirmations: number; -} - -/** - * The result of a deployment run. - * - * @beta - */ -export type DeploymentResult = - | DeploymentResultSuccess - | DeploymentResultFailure - | DeploymentResultTimeout - | { - status: "hold"; - }; - -/** - * The result of a successful deployment run. - * - * @beta - */ -export interface DeploymentResultSuccess { - status: "success"; - contracts: DeploymentResultContracts; - module: IgnitionModule>; -} - -/** - * The result of a failed deployment run where at least one future had a - * failed transaction. - * - * @beta - */ -export interface DeploymentResultFailure { - status: "failure"; - errors: { [key: string]: Error }; -} - -/** - * The result a deployment run where at least one transaction has timed - * out and is still pending. - * - * @beta - */ -export interface DeploymentResultTimeout { - status: "timeout"; - timeouts: Array<{ futureId: string; executionId: number; txHash: string }>; -} - -/** - * A successfully deployed contract from the deployment run. - * - * @beta - */ -export interface DeploymentResultContract { - contractName: string; - contractAddress: string; - artifact: Artifact; -} - -/** - * The successfully deployed contracts from the deployment run. - * - * @beta - */ -export type DeploymentResultContracts = Record< - string, - DeploymentResultContract ->; - -/** - * An object containing a map of module ID's to their respective ModuleParameters. - * - * @beta - */ -export interface DeploymentParameters { - [moduleId: string]: ModuleParameters; -} diff --git a/packages/core/test/new-api/useModule.ts b/packages/core/test/new-api/useModule.ts index 1c0059d7f..e6cfda56e 100644 --- a/packages/core/test/new-api/useModule.ts +++ b/packages/core/test/new-api/useModule.ts @@ -1,7 +1,7 @@ /* eslint-disable import/no-unused-modules */ import { assert } from "chai"; -import { Artifact } from "../../src"; +import { Artifact, DeploymentResultType } from "../../src"; import { buildModule } from "../../src/new-api/build-module"; import { validateStageTwo } from "../../src/new-api/internal/validation/validateStageTwo"; @@ -197,19 +197,25 @@ describe("useModule", () => { }, }; - await assert.isRejected( - validateStageTwo( - moduleWithSubmodule, - setupMockArtifactResolver({ - Contract1: fakeArtifact, - Contract2: fakeArtifact, - Contract3: fakeArtifact, - }), - moduleParams, - [] - ), - /Module parameter 'param1' requires a value but was given none/ + const result = await validateStageTwo( + moduleWithSubmodule, + setupMockArtifactResolver({ + Contract1: fakeArtifact, + Contract2: fakeArtifact, + Contract3: fakeArtifact, + }), + moduleParams, + [] ); + + assert.deepStrictEqual(result, { + type: DeploymentResultType.VALIDATION_ERROR, + errors: { + "Submodule1:Contract1": [ + "Module parameter 'param1' requires a value but was given none", + ], + }, + }); }); }); }); diff --git a/packages/hardhat-plugin/src/ignition-helper.ts b/packages/hardhat-plugin/src/ignition-helper.ts index 382be194b..8b24125bc 100644 --- a/packages/hardhat-plugin/src/ignition-helper.ts +++ b/packages/hardhat-plugin/src/ignition-helper.ts @@ -1,16 +1,29 @@ +import type { Contract } from "ethers"; + import { deploy, DeployConfig, - DeploymentResultSuccess, + DeploymentParameters, + DeploymentResultType, EIP1193Provider, + Future, IgnitionError, IgnitionModule, - ModuleParameters, + IgnitionModuleResult, + isContractFuture, + NamedContractAtFuture, + NamedContractDeploymentFuture, + SuccessfulDeploymentResult, } from "@ignored/ignition-core"; -import { Contract } from "ethers"; +import { HardhatPluginError } from "hardhat/plugins"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { HardhatArtifactResolver } from "./hardhat-artifact-resolver.ts"; +import { errorDeploymentResultToExceptionMessage } from "./utils/error-deployment-result-to-exception-message"; + +export type DeployedContract = { + [contractName in ContractNameT]: Contract; +}; export class IgnitionHelper { private _provider: EIP1193Provider; @@ -26,19 +39,32 @@ export class IgnitionHelper { this._deploymentDir = deploymentDir; } - public async deploy( - ignitionModuleDefinition: IgnitionModule, + public async deploy< + ModuleIdT extends string, + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult + >( + ignitionModule: IgnitionModule< + ModuleIdT, + ContractNameT, + IgnitionModuleResultsT + >, { parameters = {}, config: perDeployConfig, }: { - parameters: { [key: string]: ModuleParameters }; + parameters: DeploymentParameters; config: Partial; } = { parameters: {}, config: {}, } - ): Promise> { + ): Promise< + IgnitionModuleResultsTToEthersContracts< + ContractNameT, + IgnitionModuleResultsT + > + > { const accounts = (await this._hre.network.provider.request({ method: "eth_accounts", })) as string[]; @@ -55,52 +81,89 @@ export class IgnitionHelper { provider: this._provider, deploymentDir: this._deploymentDir, artifactResolver, - ignitionModule: ignitionModuleDefinition, + ignitionModule, deploymentParameters: parameters, accounts, verbose: false, }); - if (result.status === "timeout") { - throw new IgnitionError( - `The deployment has been halted due to transaction timeouts:\n ${result.timeouts - .map((t) => `${t.txHash} (${t.futureId}/${t.executionId})`) - .join("\n ")}` - ); - } + if (result.type !== DeploymentResultType.SUCCESSFUL_DEPLOYMENT) { + const message = errorDeploymentResultToExceptionMessage(result); - if (result.status !== "success") { - // TODO: Show more information about why it failed - throw new IgnitionError("Failed deployment"); + throw new IgnitionError(message); } - return this._toEthersContracts(result); + return this._toEthersContracts(ignitionModule, result); } - private async _toEthersContracts( - result: DeploymentResultSuccess - ): Promise> { - const resolvedOutput: { [k: string]: Contract } = {}; - - for (const [key, future] of Object.entries(result.module.results)) { - const deployedContract = result.contracts[future.id]; - - if (deployedContract === undefined) { - throw new IgnitionError( - `Contract not among deployed results ${future.id}` - ); - } - - const { contractAddress, artifact } = deployedContract; + private async _toEthersContracts< + ModuleIdT extends string, + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult + >( + ignitionModule: IgnitionModule< + ModuleIdT, + ContractNameT, + IgnitionModuleResultsT + >, + result: SuccessfulDeploymentResult + ): Promise< + IgnitionModuleResultsTToEthersContracts< + ContractNameT, + IgnitionModuleResultsT + > + > { + return Object.fromEntries( + await Promise.all( + Object.entries(result.contracts).map( + async ([name, deployedContract]) => [ + name, + await this._getContract( + ignitionModule.results[name], + deployedContract + ), + ] + ) + ) + ); + } - const abi: any[] = artifact.abi; + private async _getContract( + future: Future, + deployedContract: { address: string } + ): Promise { + if (!isContractFuture(future)) { + throw new HardhatPluginError( + "@ignored/hardhat-ignition", + `Expected contract future but got ${future.id} with type ${future.type} instead` + ); + } - resolvedOutput[key] = await this._hre.ethers.getContractAt( - abi, - contractAddress + if ("artifact" in future) { + return this._hre.ethers.getContractAt( + future.artifact.abi, + deployedContract.address ); } - return resolvedOutput; + return this._hre.ethers.getContractAt( + future.contractName, + deployedContract.address + ); } } + +export type IgnitionModuleResultsTToEthersContracts< + ContractNameT extends string, + IgnitionModuleResultsT extends IgnitionModuleResult +> = { + [contract in keyof IgnitionModuleResultsT]: IgnitionModuleResultsT[contract] extends + | NamedContractDeploymentFuture + | NamedContractAtFuture + ? TypeChainEthersContractByName + : Contract; +}; + +// TODO: Make this work to have support for TypeChain +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type TypeChainEthersContractByName = Contract; diff --git a/packages/hardhat-plugin/src/index.ts b/packages/hardhat-plugin/src/index.ts index db9519027..387d8785d 100644 --- a/packages/hardhat-plugin/src/index.ts +++ b/packages/hardhat-plugin/src/index.ts @@ -1,7 +1,11 @@ import { deploy, DeploymentParameters, + DeploymentResult, + DeploymentResultType, + IgnitionModuleResult, plan, + SuccessfulDeploymentResult, wipe, } from "@ignored/ignition-core"; import "@nomicfoundation/hardhat-ethers"; @@ -15,6 +19,7 @@ import { HardhatArtifactResolver } from "./hardhat-artifact-resolver.ts"; import { IgnitionHelper } from "./ignition-helper"; import { loadModule } from "./load-module"; import { writePlan } from "./plan/write-plan"; +import { errorDeploymentResultToExceptionMessage } from "./utils/error-deployment-result-to-exception-message"; import { open } from "./utils/open"; import "./type-extensions"; @@ -125,70 +130,27 @@ task("deploy") method: "eth_accounts", })) as string[]; - try { - const deploymentId = givenDeploymentId ?? `network-${chainId}`; - - const deploymentDir = - hre.network.name === "hardhat" - ? undefined - : path.join(hre.config.paths.ignition, "deployments", deploymentId); - - const artifactResolver = new HardhatArtifactResolver(hre); - - const result = await deploy({ - config: hre.config.ignition, - provider: hre.network.provider, - artifactResolver, - deploymentDir, - ignitionModule: userModule, - deploymentParameters: parameters ?? {}, - accounts, - verbose: logs, - }); + const deploymentId = givenDeploymentId ?? `network-${chainId}`; - if (result.status === "success") { - console.log("Deployment complete"); - console.log(""); - - for (const [ - futureId, - { contractName, contractAddress }, - ] of Object.entries(result.contracts)) { - console.log(`${contractName} (${futureId}) - ${contractAddress}`); - } - } else if (result.status === "failure") { - console.log("Deployment failed"); - console.log(""); - - for (const [futureId, error] of Object.entries(result.errors)) { - const errorMessage = - "reason" in error ? (error.reason as string) : error.message; - - console.log(`Future ${futureId} failed: ${errorMessage}`); - } - } else if (result.status === "timeout") { - console.log("Deployment halted due to timeout"); - console.log(""); - - for (const { futureId, executionId, txHash } of result.timeouts) { - console.log(` ${txHash} (${futureId}/${executionId})`); - } - } else if (result.status === "hold") { - console.log("Deployment held"); - } else { - assertNeverResult(result); - } - } catch (err) { - // TODO: bring back cli ui - // if (DISPLAY_UI) { - // // display of error or on hold is done - // // based on state, thrown error display - // // can be ignored - // process.exit(1); - // } else { - throw err; - // } - } + const deploymentDir = + hre.network.name === "hardhat" + ? undefined + : path.join(hre.config.paths.ignition, "deployments", deploymentId); + + const artifactResolver = new HardhatArtifactResolver(hre); + + const result = await deploy({ + config: hre.config.ignition, + provider: hre.network.provider, + artifactResolver, + deploymentDir, + ignitionModule: userModule, + deploymentParameters: parameters ?? {}, + accounts, + verbose: logs, + }); + + displayDeploymentResult(result); } ); @@ -360,6 +322,28 @@ function resolveParametersString(paramString: string): DeploymentParameters { } } -function assertNeverResult(result: never) { - throw new Error(`Unknown result from deploy: ${JSON.stringify(result)}`); +function displayDeploymentResult( + result: DeploymentResult> +): void { + switch (result.type) { + case DeploymentResultType.VALIDATION_ERROR: + case DeploymentResultType.RECONCILIATION_ERROR: + case DeploymentResultType.EXECUTION_ERROR: + return console.log(errorDeploymentResultToExceptionMessage(result)); + case DeploymentResultType.SUCCESSFUL_DEPLOYMENT: + return _displaySuccessfulDeployment(result); + } +} + +function _displaySuccessfulDeployment( + result: SuccessfulDeploymentResult> +): void { + console.log("Deployment complete"); + console.log(""); + + for (const [futureId, { contractName, address }] of Object.entries( + result.contracts + )) { + console.log(`${contractName} (${futureId}) - ${address}`); + } } diff --git a/packages/hardhat-plugin/src/utils/error-deployment-result-to-exception-message.ts b/packages/hardhat-plugin/src/utils/error-deployment-result-to-exception-message.ts new file mode 100644 index 000000000..a0e5eed05 --- /dev/null +++ b/packages/hardhat-plugin/src/utils/error-deployment-result-to-exception-message.ts @@ -0,0 +1,107 @@ +import { + DeploymentResultType, + ExecutionErrorDeploymentResult, + ReconciliationErrorDeploymentResult, + ValidationErrorDeploymentResult, +} from "@ignored/ignition-core"; +import { HardhatPluginError } from "hardhat/plugins"; + +/** + * Converts the result of an errored deployment into a message that can + * be shown to the user in an exception. + * + * @param result - the errored deployment's result + * @returns the text of the message + */ +export function errorDeploymentResultToExceptionMessage( + result: + | ValidationErrorDeploymentResult + | ReconciliationErrorDeploymentResult + | ExecutionErrorDeploymentResult +): string { + switch (result.type) { + case DeploymentResultType.VALIDATION_ERROR: + return _convertValidationError(result); + case DeploymentResultType.RECONCILIATION_ERROR: + return _convertReconciliationError(result); + case DeploymentResultType.EXECUTION_ERROR: + return _convertExecutionError(result); + } +} + +function _convertValidationError( + result: ValidationErrorDeploymentResult +): string { + const errorsList = Object.entries(result.errors).flatMap( + ([futureId, errors]) => errors.map((err) => ` * ${futureId}: ${err}`) + ); + + return `The deployment wasn't run because of the following validation errors: + +${errorsList.join("\n")}`; +} + +function _convertReconciliationError( + result: ReconciliationErrorDeploymentResult +) { + const errorsList = Object.entries(result.errors).flatMap( + ([futureId, errors]) => errors.map((err) => ` * ${futureId}: ${err}`) + ); + + return `The deployment wasn't run because of the following reconciliation errors: + +${errorsList.join("\n")}`; +} + +function _convertExecutionError(result: ExecutionErrorDeploymentResult) { + const sections: string[] = []; + + const messageDetails = { + timeouts: result.timedOut.length > 0, + failures: result.failed.length > 0, + }; + + if (messageDetails.timeouts) { + const timeoutList = result.timedOut.map( + ({ futureId, executionId }) => ` * ${futureId}/${executionId}` + ); + + sections.push(`Timed out:\n\n${timeoutList.join("\n")}`); + } + + if (messageDetails.failures) { + const errorList = result.failed.map( + ({ futureId, executionId, error }) => + ` * ${futureId}/${executionId}: ${error}` + ); + + sections.push(`Failures:\n\n${errorList.join("\n")}`); + } + + return `The deployment wasn't successful, there were ${_toText( + messageDetails + )}: + +${sections.join("\n\n")}`; +} + +function _toText({ + timeouts, + failures, +}: { + timeouts: boolean; + failures: boolean; +}): string { + if (timeouts && failures) { + return "timeouts and failures"; + } else if (timeouts) { + return "timeouts"; + } else if (failures) { + return "failures"; + } + + throw new HardhatPluginError( + "@ignored/hardhat-ignition", + "Invariant violated: neither timeouts or failures" + ); +} diff --git a/packages/hardhat-plugin/test/calls.ts b/packages/hardhat-plugin/test/calls.ts index 8d855c083..87d8a04c7 100644 --- a/packages/hardhat-plugin/test/calls.ts +++ b/packages/hardhat-plugin/test/calls.ts @@ -155,9 +155,12 @@ describe("calls", () => { }); const result = await this.deploy(moduleDefinition, { - Submodule: { - depositValue: BigInt(this.hre.ethers.parseEther("1")), + parameters: { + Submodule: { + depositValue: BigInt(this.hre.ethers.parseEther("1")), + }, }, + config: {}, }); assert.isDefined(result.passingValue); diff --git a/packages/hardhat-plugin/test/config.ts b/packages/hardhat-plugin/test/config.ts index 632f01e33..5c9948064 100644 --- a/packages/hardhat-plugin/test/config.ts +++ b/packages/hardhat-plugin/test/config.ts @@ -10,7 +10,7 @@ describe("config", () => { describe("loading", () => { useEphemeralIgnitionProject("with-config"); - let loadedOptions: DeployConfig; + let loadedOptions: Partial; beforeEach(function () { loadedOptions = this.hre.config.ignition; @@ -55,7 +55,12 @@ describe("config", () => { }); await assert.isRejected( - this.deploy(moduleDefinition), + this.deploy(moduleDefinition, { + parameters: {}, + config: { + requiredConfirmations: 0, + }, + }), `Configured value 'requiredConfirmations' cannot be less than 1. Value given: '0'` ); }); diff --git a/packages/hardhat-plugin/test/execution/deploy-contract-at-from-artifact.ts b/packages/hardhat-plugin/test/execution/deploy-contract-at-from-artifact.ts index 456d1e931..4079b17be 100644 --- a/packages/hardhat-plugin/test/execution/deploy-contract-at-from-artifact.ts +++ b/packages/hardhat-plugin/test/execution/deploy-contract-at-from-artifact.ts @@ -25,14 +25,14 @@ describe.skip("execution - deploy contractAt from artifact", function () { return { foo }; }); - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { await c.mineBlock(1); } ); - const fooAddress = result.foo.address; + const fooAddress = await result.foo.getAddress(); assert.equal(fooAddress, "0x5FbDB2315678afecb367f032d93F642f64180aa3"); // Act @@ -44,7 +44,7 @@ describe.skip("execution - deploy contractAt from artifact", function () { return { atFoo }; }); - const contractAtFromArtifactResult = await this.deploy( + const contractAtFromArtifactResult = await this.runControlledDeploy( contractAtModuleDefinition, async (c: TestChainHelper) => { await c.mineBlock(1); diff --git a/packages/hardhat-plugin/test/execution/deploy-contract-at.ts b/packages/hardhat-plugin/test/execution/deploy-contract-at.ts index e343b3c81..d71a0c4f0 100644 --- a/packages/hardhat-plugin/test/execution/deploy-contract-at.ts +++ b/packages/hardhat-plugin/test/execution/deploy-contract-at.ts @@ -23,14 +23,14 @@ describe("execution - deploy contract at", function () { return { foo }; }); - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { await c.mineBlock(1); } ); - const fooAddress = result.foo.address; + const fooAddress = await result.foo.getAddress(); assert.equal(fooAddress, "0x5FbDB2315678afecb367f032d93F642f64180aa3"); @@ -40,7 +40,7 @@ describe("execution - deploy contract at", function () { return { foo }; }); - const contractAtResult = await this.deploy( + const contractAtResult = await this.runControlledDeploy( contractAtModuleDefinition, async (c: TestChainHelper) => { await c.mineBlock(1); diff --git a/packages/hardhat-plugin/test/execution/deploy-contract-from-artifact.ts b/packages/hardhat-plugin/test/execution/deploy-contract-from-artifact.ts index 6a87ca9c5..12cb7daf7 100644 --- a/packages/hardhat-plugin/test/execution/deploy-contract-from-artifact.ts +++ b/packages/hardhat-plugin/test/execution/deploy-contract-from-artifact.ts @@ -2,10 +2,10 @@ import { buildModule } from "@ignored/ignition-core"; import { assert } from "chai"; -import { - TestChainHelper, - useEphemeralIgnitionProject, -} from "../use-ignition-project"; +import { waitForPendingTxs } from "../helpers"; +import { useEphemeralIgnitionProject } from "../use-ignition-project"; + +import { mineBlock } from "./helpers"; /** * Deploy a contract from an artifact. @@ -23,12 +23,12 @@ describe("execution - deploy contract from artifact", function () { return { foo }; }); - const result = await this.deploy( - moduleDefinition, - async (c: TestChainHelper) => { - await c.mineBlock(1); - } - ); + const deployPromise = this.deploy(moduleDefinition); + + await waitForPendingTxs(this.hre, 1, deployPromise); + await mineBlock(this.hre); + + const result = await deployPromise; assert.equal(await result.foo.x(), Number(1)); }); diff --git a/packages/hardhat-plugin/test/execution/deploy-contract.ts b/packages/hardhat-plugin/test/execution/deploy-contract.ts index 5127079f6..a6467571b 100644 --- a/packages/hardhat-plugin/test/execution/deploy-contract.ts +++ b/packages/hardhat-plugin/test/execution/deploy-contract.ts @@ -2,10 +2,10 @@ import { buildModule } from "@ignored/ignition-core"; import { assert } from "chai"; -import { - TestChainHelper, - useEphemeralIgnitionProject, -} from "../use-ignition-project"; +import { waitForPendingTxs } from "../helpers"; +import { useEphemeralIgnitionProject } from "../use-ignition-project"; + +import { mineBlock } from "./helpers"; /** * This is the simplest contract deploy case. @@ -23,12 +23,12 @@ describe("execution - deploy contract", function () { return { foo }; }); - const result = await this.deploy( - moduleDefinition, - async (c: TestChainHelper) => { - await c.mineBlock(1); - } - ); + const deployPromise = this.deploy(moduleDefinition); + + await waitForPendingTxs(this.hre, 1, deployPromise); + await mineBlock(this.hre); + + const result = await deployPromise; assert.equal(await result.foo.x(), Number(1)); }); diff --git a/packages/hardhat-plugin/test/execution/multiple-batch-contract-deploy.ts b/packages/hardhat-plugin/test/execution/multiple-batch-contract-deploy.ts index f4d541e7a..ff7e6b877 100644 --- a/packages/hardhat-plugin/test/execution/multiple-batch-contract-deploy.ts +++ b/packages/hardhat-plugin/test/execution/multiple-batch-contract-deploy.ts @@ -50,6 +50,7 @@ describe("execution - multiple batch contract deploy", function () { const deployPromise = this.deploy(moduleDefinition, { parameters: {}, + config: {}, }); await sleep(300); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-pending-user-transactions.ts b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-pending-user-transactions.ts index a0bed4155..0c72f426a 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-pending-user-transactions.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-pending-user-transactions.ts @@ -40,7 +40,10 @@ describe("execution - error on pending user transactions", () => { // Deploying the module that uses accounts[2] throws with a warning await assert.isRejected( - this.deploy(moduleDefinition, async (_c: TestChainHelper) => {}), + this.runControlledDeploy( + moduleDefinition, + async (_c: TestChainHelper) => {} + ), "Pending transactions for account: 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc, please wait for transactions to complete before running a deploy" ); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-rerun-with-replaced-pending-user-transaction.ts b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-rerun-with-replaced-pending-user-transaction.ts index 1f311160b..5fbde58b9 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-rerun-with-replaced-pending-user-transaction.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-rerun-with-replaced-pending-user-transaction.ts @@ -37,9 +37,8 @@ describe("execution - error on rerun with replaced pending user transaction", () // Start the deployment, but exit before processing a block, // so transactions are in memory pool but not confirmed - await this.deploy( + await this.runControlledDeploy( moduleDefinition, - async (c: TestChainHelper) => { // Wait for the submission of foo1 foo2 and foo3 to mempool await c.waitForPendingTxs(3); @@ -54,7 +53,8 @@ describe("execution - error on rerun with replaced pending user transaction", () // transaction const [, , signer2] = await this.hre.ethers.getSigners(); const FooFactory = await this.hre.ethers.getContractFactory("Foo"); - FooFactory.connect(signer2).deploy({ + + void FooFactory.connect(signer2).deploy({ gasPrice: this.hre.ethers.parseUnits("500", "gwei"), nonce: 2, // same nonce as foo3 }); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-transaction-dropped.ts b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-transaction-dropped.ts index 65f1397f0..2430a1c85 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-transaction-dropped.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-transaction-dropped.ts @@ -39,7 +39,7 @@ describe("execution - error on transaction dropped", () => { // is detected await assert.isRejected( - this.deploy(moduleDefinition, async (c: TestChainHelper) => { + this.runControlledDeploy(moduleDefinition, async (c: TestChainHelper) => { // Process block 1 confirming foo1 await c.mineBlock(1); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-user-transaction-sent.ts b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-user-transaction-sent.ts index 922ba51db..d3851599a 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/error-on-user-transaction-sent.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/error-on-user-transaction-sent.ts @@ -38,25 +38,22 @@ describe("execution - error on user transaction sent", () => { // The deploy should exception when the additional user interfering // transaction is detected await assert.isRejected( - this.deploy( - moduleDefinition, - - async (c: TestChainHelper) => { - // wait for foo1 to be submitted - await c.waitForPendingTxs(1); - - // Submit user interference transaction to mempool (note a fresh - // nonce is used, so no replacement) - const [, , signer2] = await this.hre.ethers.getSigners(); - const FooFactory = await this.hre.ethers.getContractFactory("Foo"); - FooFactory.connect(signer2).deploy({ - gasPrice: this.hre.ethers.parseUnits("500", "gwei"), - }); - - // Process block 1 with foo1 - await c.mineBlock(2); - } - ), + this.runControlledDeploy(moduleDefinition, async (c: TestChainHelper) => { + // wait for foo1 to be submitted + await c.waitForPendingTxs(1); + + // Submit user interference transaction to mempool (note a fresh + // nonce is used, so no replacement) + const [, , signer2] = await this.hre.ethers.getSigners(); + const FooFactory = await this.hre.ethers.getContractFactory("Foo"); + + void FooFactory.connect(signer2).deploy({ + gasPrice: this.hre.ethers.parseUnits("500", "gwei"), + }); + + // Process block 1 with foo1 + await c.mineBlock(2); + }), "A transaction has been submitted on the account 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc outside of the deployment" ); }); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-dropped-ignition-transaction.ts b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-dropped-ignition-transaction.ts index ace61e885..93fc7a21c 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-dropped-ignition-transaction.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-dropped-ignition-transaction.ts @@ -29,13 +29,16 @@ describe("execution - rerun with dropped ignition transactions", () => { }); // Start the deploy - await this.deploy(moduleDefinition, async (c: TestChainHelper) => { - // Wait for the submission of foo to mempool - await c.waitForPendingTxs(1); + await this.runControlledDeploy( + moduleDefinition, + async (c: TestChainHelper) => { + // Wait for the submission of foo to mempool + await c.waitForPendingTxs(1); - // kill the deployment before confirming foo - c.exitDeploy(); - }); + // kill the deployment before confirming foo + c.exitDeploy(); + } + ); // remove the submitted foo deploy from mempool await clearPendingTransactionsFromMemoryPool(this.hre); @@ -45,7 +48,7 @@ describe("execution - rerun with dropped ignition transactions", () => { await mineBlock(this.hre); // Rerun the deployment - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { // this block shound include deployment of foo via resend diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-now-complete-ignition-transactions.ts b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-now-complete-ignition-transactions.ts index 78a75df64..2153ff2be 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-now-complete-ignition-transactions.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-now-complete-ignition-transactions.ts @@ -62,21 +62,24 @@ describe("execution - rerun with now complete ignition transactions", () => { }; }); - await this.deploy(moduleDefinition, async (c: TestChainHelper) => { - // Process the first block, include foo1 and foo2 - await c.mineBlock(2); + await this.runControlledDeploy( + moduleDefinition, + async (c: TestChainHelper) => { + // Process the first block, include foo1 and foo2 + await c.mineBlock(2); - // Kill the deployment, after foo3 and foo4 are submitted to mempool - await c.waitForPendingTxs(2); - c.exitDeploy(); - }); + // Kill the deployment, after foo3 and foo4 are submitted to mempool + await c.waitForPendingTxs(2); + c.exitDeploy(); + } + ); // Further blocks are processed confirming foo3 and foo4 await mineBlock(this.hre); await mineBlock(this.hre); // Rerun the deployment, with foo3 and foo3 now confirmed - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { await c.mineBlock(2); diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-pending-ignition-transactions.ts b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-pending-ignition-transactions.ts index 2d411f2d6..55a7f154e 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-pending-ignition-transactions.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-pending-ignition-transactions.ts @@ -60,19 +60,22 @@ describe("execution - rerun with pending ignition transactions", () => { }; }); - await this.deploy(moduleDefinition, async (c: TestChainHelper) => { - // Process the first block, include foo1 and foo2 - await c.mineBlock(2); + await this.runControlledDeploy( + moduleDefinition, + async (c: TestChainHelper) => { + // Process the first block, include foo1 and foo2 + await c.mineBlock(2); - // Kill the deployment, with foo3 and foo4 submitted to mempool - await c.waitForPendingTxs(2); - c.exitDeploy(); - }); + // Kill the deployment, with foo3 and foo4 submitted to mempool + await c.waitForPendingTxs(2); + c.exitDeploy(); + } + ); // NOTE: no blocks mined between previous run and this run // there should two deploy contract transactions for foo3 and foo4 // in the mempool - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { // this block should confirm foo3 and foo4 diff --git a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-replaced-confirmed-user-transaction.ts b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-replaced-confirmed-user-transaction.ts index fabd3ecc2..79644a2b2 100644 --- a/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-replaced-confirmed-user-transaction.ts +++ b/packages/hardhat-plugin/test/execution/nonce-checks/rerun-with-replaced-confirmed-user-transaction.ts @@ -38,12 +38,15 @@ describe("execution - rerun with replaced confirmed user transaction", () => { }); // First run fo the deploy - await this.deploy(moduleDefinition, async (c: TestChainHelper) => { - // Wait for the submission of foo1 foo2 and foo3 to mempool, - // then kill the deploy process - await c.waitForPendingTxs(3); - c.exitDeploy(); - }); + await this.runControlledDeploy( + moduleDefinition, + async (c: TestChainHelper) => { + // Wait for the submission of foo1 foo2 and foo3 to mempool, + // then kill the deploy process + await c.waitForPendingTxs(3); + c.exitDeploy(); + } + ); // Submit a user interfering deploy transaction // to the mempool reusing nonce 2 @@ -61,7 +64,7 @@ describe("execution - rerun with replaced confirmed user transaction", () => { // Rerun the deployment with foo3 replaced, causing it to // be resubmitted - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { // this block should confirm foo3 diff --git a/packages/hardhat-plugin/test/execution/timeouts/deploy-run-times-out.ts b/packages/hardhat-plugin/test/execution/timeouts/deploy-run-times-out.ts index 1bc4d9849..121fb91a0 100644 --- a/packages/hardhat-plugin/test/execution/timeouts/deploy-run-times-out.ts +++ b/packages/hardhat-plugin/test/execution/timeouts/deploy-run-times-out.ts @@ -29,7 +29,7 @@ describe.skip("execution - deploy run times out", () => { // Deploying the module that uses accounts[2] throws with a warning await assert.isRejected( - this.deploy(moduleDefinition, async (c: TestChainHelper) => { + this.runControlledDeploy(moduleDefinition, async (c: TestChainHelper) => { // wait for the deploy transaction to hit the memory pool, // but then never mine the block that will complete it. await c.waitForPendingTxs(1); diff --git a/packages/hardhat-plugin/test/execution/timeouts/rerun-a-deploy-that-timed-out.ts b/packages/hardhat-plugin/test/execution/timeouts/rerun-a-deploy-that-timed-out.ts index 901fd657b..a74b57e53 100644 --- a/packages/hardhat-plugin/test/execution/timeouts/rerun-a-deploy-that-timed-out.ts +++ b/packages/hardhat-plugin/test/execution/timeouts/rerun-a-deploy-that-timed-out.ts @@ -13,11 +13,7 @@ import { * TODO: Needs to be updated to deal with fee bumps */ describe.skip("execution - rerun a deploy that timed out", () => { - useFileIgnitionProject( - "minimal-new-api", - "rerun-a-deploy-that-timed-out", - {} - ); + useFileIgnitionProject("minimal-new-api", "rerun-a-deploy-that-timed-out"); it("should error naming timed out transactions", async function () { // Setup a module with a contract deploy on accounts[2] @@ -34,14 +30,14 @@ describe.skip("execution - rerun a deploy that timed out", () => { // Deploying the module that uses accounts[2], but force timeout, // by not processing any blocks await assert.isRejected( - this.deploy(moduleDefinition, async (c: TestChainHelper) => { + this.runControlledDeploy(moduleDefinition, async (c: TestChainHelper) => { // wait for the deploy transaction to hit the memory pool, // but then never mine the block that will complete it. await c.waitForPendingTxs(1); }) ); - const result = await this.deploy( + const result = await this.runControlledDeploy( moduleDefinition, async (c: TestChainHelper) => { // Mine the block, confirming foo diff --git a/packages/hardhat-plugin/test/existing-contract.ts b/packages/hardhat-plugin/test/existing-contract.ts index 2a01b99ee..1041c43f8 100644 --- a/packages/hardhat-plugin/test/existing-contract.ts +++ b/packages/hardhat-plugin/test/existing-contract.ts @@ -31,8 +31,8 @@ describe("existing contract", () => { await firstResult.usesContract.getAddress(); const secondModuleDefinition = buildModule("SecondModule", (m) => { - const bar = m.contractAt("Bar", barAddress, barArtifact); - const usesContract = m.contractAt( + const bar = m.contractAtFromArtifact("Bar", barAddress, barArtifact); + const usesContract = m.contractAtFromArtifact( "UsesContract", usesContractAddress, usesContractArtifact diff --git a/packages/hardhat-plugin/test/params.ts b/packages/hardhat-plugin/test/params.ts index f841fe5ed..b3d9af750 100644 --- a/packages/hardhat-plugin/test/params.ts +++ b/packages/hardhat-plugin/test/params.ts @@ -37,9 +37,12 @@ describe("module parameters", () => { }); const result = await this.deploy(moduleDefinition, { - WithDefaultModule: { - MyNumber: 20, + parameters: { + WithDefaultModule: { + MyNumber: 20, + }, }, + config: {}, }); assert.equal(await result.foo.x(), Number(21)); @@ -71,9 +74,12 @@ describe("module parameters", () => { }); const result = await this.deploy(moduleDefinition, { - WithDefaultStringModule: { - MyString: "NotExample", + parameters: { + WithDefaultStringModule: { + MyString: "NotExample", + }, }, + config: {}, }); assert.equal(await result.greeter.getGreeting(), "NotExample"); @@ -123,6 +129,7 @@ describe("params validation", () => { NotMyNumber: 11, }, }, + config: {}, }); await assert.isRejected( diff --git a/packages/hardhat-plugin/test/use-ignition-project.ts b/packages/hardhat-plugin/test/use-ignition-project.ts index abd1e9558..1cf985975 100644 --- a/packages/hardhat-plugin/test/use-ignition-project.ts +++ b/packages/hardhat-plugin/test/use-ignition-project.ts @@ -1,8 +1,4 @@ -import { - DeployConfig, - IgnitionModule, - ModuleParameters, -} from "@ignored/ignition-core"; +import { DeployConfig, IgnitionModule } from "@ignored/ignition-core"; import { Contract } from "ethers"; import { ensureDirSync, removeSync } from "fs-extra"; import { resetHardhatContext } from "hardhat/plugins-testing"; @@ -14,6 +10,19 @@ import { IgnitionHelper } from "../src/ignition-helper"; import { clearPendingTransactionsFromMemoryPool } from "./execution/helpers"; import { waitForPendingTxs } from "./helpers"; +declare module "mocha" { + interface Context { + hre: HardhatRuntimeEnvironment; + deploymentDir: string | undefined; + deploy: HardhatRuntimeEnvironment["ignition"]["deploy"]; + runControlledDeploy: ( + ignitionModule: IgnitionModule, + chainUpdates: (c: TestChainHelper) => Promise + ) => ReturnType; + config: Partial; + } +} + const defaultTestConfig: DeployConfig = { maxFeeBumps: 5, timeBeforeBumpingFees: 3 * 60 * 1000, @@ -37,15 +46,7 @@ export function useEphemeralIgnitionProject(fixtureProjectName: string) { await hre.run("compile", { quiet: true }); - this.deploy = ( - ignitionModule: IgnitionModule, - parameters: { [key: string]: ModuleParameters } = {} - ) => { - return this.hre.ignition.deploy(ignitionModule, { - parameters, - config: hre.config.ignition, - }); - }; + this.deploy = this.hre.ignition.deploy.bind(this.hre.ignition); }); afterEach("reset hardhat context", function () { @@ -85,7 +86,7 @@ export function useFileIgnitionProject( ensureDirSync(deploymentDir); - this.deploy = ( + this.runControlledDeploy = ( ignitionModule: IgnitionModule, chainUpdates: (c: TestChainHelper) => Promise = async () => {} ) => { @@ -101,6 +102,12 @@ export function useFileIgnitionProject( afterEach("reset hardhat context", function () { resetHardhatContext(); + if (this.deploymentDir === undefined) { + throw new Error( + "Deployment dir not set during cleanup of file based project" + ); + } + removeSync(this.deploymentDir); }); } diff --git a/packages/hardhat-plugin/test/utils/error-deployment-result-to-exception-message.ts b/packages/hardhat-plugin/test/utils/error-deployment-result-to-exception-message.ts new file mode 100644 index 000000000..0770d26bd --- /dev/null +++ b/packages/hardhat-plugin/test/utils/error-deployment-result-to-exception-message.ts @@ -0,0 +1,154 @@ +import { + DeploymentResultType, + ExecutionErrorDeploymentResult, + ReconciliationErrorDeploymentResult, + ValidationErrorDeploymentResult, +} from "@ignored/ignition-core"; +import { assert } from "chai"; + +import { errorDeploymentResultToExceptionMessage } from "../../src/utils/error-deployment-result-to-exception-message"; + +describe("display error deployment result", () => { + describe("validation", () => { + it("should display a validation error", () => { + const result: ValidationErrorDeploymentResult = { + type: DeploymentResultType.VALIDATION_ERROR, + errors: { + "MyModule:MyContract": [ + "The number of params does not match the constructor", + "The name of the contract is invalid", + ], + "MyModule:AnotherContract": ["No library provided"], + }, + }; + + assert.equal( + errorDeploymentResultToExceptionMessage(result), + `The deployment wasn't run because of the following validation errors: + + * MyModule:MyContract: The number of params does not match the constructor + * MyModule:MyContract: The name of the contract is invalid + * MyModule:AnotherContract: No library provided` + ); + }); + }); + + describe("reconciliation", () => { + it("should display a reconciliation error", () => { + const result: ReconciliationErrorDeploymentResult = { + type: DeploymentResultType.RECONCILIATION_ERROR, + errors: { + "MyModule:MyContract": [ + "The params don't match", + "The value doesn't match", + ], + "MyModule:AnotherContract": ["The future is timed out"], + }, + }; + + assert.equal( + errorDeploymentResultToExceptionMessage(result), + `The deployment wasn't run because of the following reconciliation errors: + + * MyModule:MyContract: The params don\'t match + * MyModule:MyContract: The value doesn\'t match + * MyModule:AnotherContract: The future is timed out` + ); + }); + }); + + describe("execution", () => { + it("should display an execution error with timeouts", () => { + const result: ExecutionErrorDeploymentResult = { + type: DeploymentResultType.EXECUTION_ERROR, + started: [], + timedOut: [ + { futureId: "MyModule:MyContract", executionId: 1 }, + { futureId: "MyModule:AnotherContract", executionId: 3 }, + ], + failed: [], + successful: [], + }; + + assert.equal( + errorDeploymentResultToExceptionMessage(result), + `The deployment wasn't successful, there were timeouts: + +Timed out: + + * MyModule:MyContract/1 + * MyModule:AnotherContract/3` + ); + }); + + it("should display an execution error with execution failures", () => { + const result: ExecutionErrorDeploymentResult = { + type: DeploymentResultType.EXECUTION_ERROR, + started: [], + timedOut: [], + failed: [ + { + futureId: "MyModule:MyContract", + executionId: 1, + error: "Reverted with reason x", + }, + { + futureId: "MyModule:AnotherContract", + executionId: 3, + error: "Reverted with reason y", + }, + ], + successful: [], + }; + + assert.equal( + errorDeploymentResultToExceptionMessage(result), + `The deployment wasn't successful, there were failures: + +Failures: + + * MyModule:MyContract/1: Reverted with reason x + * MyModule:AnotherContract/3: Reverted with reason y` + ); + }); + + it("should display an execution error with both timeouts and execution failures", () => { + const result: ExecutionErrorDeploymentResult = { + type: DeploymentResultType.EXECUTION_ERROR, + started: [], + timedOut: [ + { futureId: "MyModule:FirstContract", executionId: 1 }, + { futureId: "MyModule:SecondContract", executionId: 3 }, + ], + failed: [ + { + futureId: "MyModule:ThirdContract", + executionId: 1, + error: "Reverted with reason x", + }, + { + futureId: "MyModule:FourthContract", + executionId: 3, + error: "Reverted with reason y", + }, + ], + successful: [], + }; + + assert.equal( + errorDeploymentResultToExceptionMessage(result), + `The deployment wasn't successful, there were timeouts and failures: + +Timed out: + + * MyModule:FirstContract/1 + * MyModule:SecondContract/3 + +Failures: + + * MyModule:ThirdContract/1: Reverted with reason x + * MyModule:FourthContract/3: Reverted with reason y` + ); + }); + }); +});