import type { ArtifactResolver } from "../../../types/artifact.js"; import type { DeploymentParameters } from "../../../types/deploy.js"; import type { Future } from "../../../types/module.js"; import type { DeploymentLoader } from "../../deployment-loader/types.js"; import type { JsonRpcClient } from "../jsonrpc-client.js"; import type { NonceManager } from "../nonce-management/json-rpc-nonce-manager.js"; import type { TransactionTrackingTimer } from "../transaction-tracking-timer.js"; import type { DeploymentState } from "../types/deployment-state.js"; import type { CallExecutionState, DeploymentExecutionState, SendDataExecutionState, StaticCallExecutionState, } from "../types/execution-state.js"; import type { ExecutionStrategy } from "../types/execution-strategy.js"; import type { JournalMessage } from "../types/messages.js"; import { assertIgnitionInvariant } from "../../utils/assertions.js"; import { isExecutionStateComplete } from "../../views/is-execution-state-complete.js"; import { applyNewMessage } from "../deployment-state-helpers.js"; import { ExecutionResultType } from "../types/execution-result.js"; import { ExecutionSateType } from "../types/execution-state.js"; import { JournalMessageType } from "../types/messages.js"; import { monitorOnchainInteraction } from "./handlers/monitor-onchain-interaction.js"; import { queryStaticCall } from "./handlers/query-static-call.js"; import { runStrategy } from "./handlers/run-strategy.js"; import { sendTransaction } from "./handlers/send-transaction.js"; import { buildInitializeMessageFor } from "./helpers/build-initialize-message-for.js"; import { NextAction, nextActionForExecutionState as nextActionExecutionState, } from "./helpers/next-action-for-execution-state.js"; import { saveArtifactsForFuture } from "./helpers/save-artifacts-for-future.js"; /** * This class is used to process a future, executing as much as possible, and * returning the new deployment state and a boolean indicating if the future * was completed. */ export class FutureProcessor { constructor( private readonly _deploymentLoader: DeploymentLoader, private readonly _artifactResolver: ArtifactResolver, private readonly _executionStrategy: ExecutionStrategy, private readonly _jsonRpcClient: JsonRpcClient, private readonly _transactionTrackingTimer: TransactionTrackingTimer, private readonly _nonceManager: NonceManager, private readonly _requiredConfirmations: number, private readonly _millisecondBeforeBumpingFees: number, private readonly _maxFeeBumps: number, private readonly _accounts: string[], private readonly _deploymentParameters: DeploymentParameters, private readonly _defaultSender: string, private readonly _disableFeeBumping: boolean, private readonly _maxRetries: number, private readonly _retryInterval: number, ) {} /** * Process a future, executing as much as possible, and returning the new * deployment state and a boolean indicating if the future was completed. * * @param future The future to process. * @returns An object with the new state and a boolean indicating if the future * was completed. If it wasn't completed, it should be processed again later, * as there's a transactions awaiting to be confirmed. */ public async processFuture( future: Future, deploymentState: DeploymentState, ): Promise<{ newState: DeploymentState }> { let exState = deploymentState.executionStates[future.id]; if (exState === undefined) { const initMessage = await buildInitializeMessageFor( future, deploymentState, this._executionStrategy, this._deploymentParameters, this._deploymentLoader, this._accounts, this._defaultSender, ); await saveArtifactsForFuture( future, this._artifactResolver, this._deploymentLoader, ); deploymentState = await applyNewMessage( initMessage, deploymentState, this._deploymentLoader, ); exState = deploymentState.executionStates[future.id]; assertIgnitionInvariant( exState !== undefined, `Invalid initialization message for future ${future.id}: it didn't create its execution state`, ); await this._recordDeployedAddressIfNeeded(initMessage); } while (!isExecutionStateComplete(exState)) { assertIgnitionInvariant( exState.type !== ExecutionSateType.CONTRACT_AT_EXECUTION_STATE && exState.type !== ExecutionSateType.READ_EVENT_ARGUMENT_EXECUTION_STATE && exState.type !== ExecutionSateType.ENCODE_FUNCTION_CALL_EXECUTION_STATE, `Unexpected ExecutionState ${exState.id} with type ${exState.type} and status ${exState.status}: it should have been immediately completed`, ); const nextAction = nextActionExecutionState(exState); const nextMessage: JournalMessage | undefined = await this._nextActionDispatch(exState, nextAction); if (nextMessage === undefined) { // continue with the next future return { newState: deploymentState }; } deploymentState = await applyNewMessage( nextMessage, deploymentState, this._deploymentLoader, ); exState = deploymentState.executionStates[future.id]; await this._recordDeployedAddressIfNeeded(nextMessage); } return { newState: deploymentState }; } /** * Records a deployed address if the last applied message was a * successful completion of a deployment. * * @param lastAppliedMessage The last message that was applied to the deployment state. */ private async _recordDeployedAddressIfNeeded( lastAppliedMessage: JournalMessage, ) { if ( lastAppliedMessage.type === JournalMessageType.DEPLOYMENT_EXECUTION_STATE_COMPLETE && lastAppliedMessage.result.type === ExecutionResultType.SUCCESS ) { await this._deploymentLoader.recordDeployedAddress( lastAppliedMessage.futureId, lastAppliedMessage.result.address, ); } else if ( lastAppliedMessage.type === JournalMessageType.CONTRACT_AT_EXECUTION_STATE_INITIALIZE ) { await this._deploymentLoader.recordDeployedAddress( lastAppliedMessage.futureId, lastAppliedMessage.contractAddress, ); } } /** * Executes the next action for the execution state, and returns a message to * be applied as a result of the execution, or undefined if no progress can be made * yet and execution should be resumed later. */ private async _nextActionDispatch( exState: | DeploymentExecutionState | CallExecutionState | SendDataExecutionState | StaticCallExecutionState, nextAction: NextAction, ): Promise { switch (nextAction) { case NextAction.RUN_STRATEGY: return runStrategy(exState, this._executionStrategy); case NextAction.SEND_TRANSACTION: assertIgnitionInvariant( exState.type !== ExecutionSateType.STATIC_CALL_EXECUTION_STATE, `Unexpected transaction request in StaticCallExecutionState ${exState.id}`, ); return sendTransaction( exState, this._executionStrategy, this._jsonRpcClient, this._nonceManager, this._transactionTrackingTimer, this._deploymentLoader, ); case NextAction.QUERY_STATIC_CALL: return queryStaticCall(exState, this._jsonRpcClient); case NextAction.MONITOR_ONCHAIN_INTERACTION: assertIgnitionInvariant( exState.type !== ExecutionSateType.STATIC_CALL_EXECUTION_STATE, `Unexpected transaction request in StaticCallExecutionState ${exState.id}`, ); return monitorOnchainInteraction({ exState, jsonRpcClient: this._jsonRpcClient, transactionTrackingTimer: this._transactionTrackingTimer, requiredConfirmations: this._requiredConfirmations, millisecondBeforeBumpingFees: this._millisecondBeforeBumpingFees, maxFeeBumps: this._maxFeeBumps, disableFeeBumping: this._disableFeeBumping, maxRetries: this._maxRetries, retryInterval: this._retryInterval, }); } } }