diff --git a/packages/builder/src/definition.ts b/packages/builder/src/definition.ts index cdc9a1a4f..b36381478 100644 --- a/packages/builder/src/definition.ts +++ b/packages/builder/src/definition.ts @@ -150,8 +150,8 @@ export class ChainDefinition { .map((n) => this.getDependencies(n)) .flatten() .uniq() - .value() - ) + .value(), + ), ); debug('start check all'); @@ -225,16 +225,26 @@ export class ChainDefinition { if (!action) { throw new Error( - `action kind plugin not installed: "${kind}" (for action: "${n}"). please install the plugin necessary to build this package.` + `action kind plugin not installed: "${kind}" (for action: "${n}"). please install the plugin necessary to build this package.`, ); } validateConfig(action.validate, _.get(this.raw, n)); - return action.configInject({ ...ctx, ...CannonHelperContext }, _.get(this.raw, n), { + const ctxWithHelpers = { ...ctx, ...CannonHelperContext }; + + const injectedConfig = action.configInject(ctxWithHelpers, _.get(this.raw, n), { ref: this.getPackageRef(ctx), currentLabel: n, }); + + if (injectedConfig.labels) { + for (const k in injectedConfig.labels) { + injectedConfig.labels[k] = template(injectedConfig.labels[k])(ctxWithHelpers); + } + } + + return injectedConfig; } /** @@ -247,7 +257,7 @@ export class ChainDefinition { n: string, runtime: ChainBuilderRuntime, ctx: ChainBuilderContext, - tainted: boolean + tainted: boolean, ): Promise { const kind = n.split('.')[0] as keyof typeof ActionKinds; @@ -270,7 +280,7 @@ export class ChainDefinition { { ref: this.getPackageRef(ctx), currentLabel: n, - } + }, ); if (!objs) { @@ -297,7 +307,7 @@ export class ChainDefinition { source: template(d.source)(ctx), chainId: d.chainId || ctx.chainId, preset: template(d.preset)(ctx) || 'main', - })) + })), ); } @@ -353,8 +363,8 @@ export class ChainDefinition { if (this.sensitiveDependencies && accessComputationResults.unableToCompute && !_.get(this.raw, node).depends) { throw new Error( `Unable to compute dependencies for [${node}] because of advanced logic in template strings. Specify dependencies manually, like "depends = ['${_.uniq( - _.uniq(accessComputationResults.accesses).map((a) => `${this.dependencyFor.get(a)}`) - ).join("', '")}']"` + _.uniq(accessComputationResults.accesses).map((a) => `${this.dependencyFor.get(a)}`), + ).join("', '")}']"`, ); } @@ -451,7 +461,7 @@ export class ChainDefinition { checkCycles( actions = this.allActionNames, seenNodes = new Set(), - currentPath = new Set() + currentPath = new Set(), ): string[] | null { for (const n of actions) { if (seenNodes.has(n)) { @@ -658,7 +668,7 @@ export class ChainDefinition { } return Math.max( layers[n].actions.length + 2, - _.sumBy(layers[n].depends, (d) => this.getPrintLinesUsed(d, layers)) + _.sumBy(layers[n].depends, (d) => this.getPrintLinesUsed(d, layers)), ); } diff --git a/packages/builder/src/schemas.ts b/packages/builder/src/schemas.ts index bbfa9863c..5bcae3fb4 100644 --- a/packages/builder/src/schemas.ts +++ b/packages/builder/src/schemas.ts @@ -1,1000 +1,503 @@ -import * as viem from 'viem'; import { z } from 'zod'; /// ================================ INPUT CONFIG SCHEMAS ================================ \\\ // Different types that can be passed into the args schema property const argtype: z.ZodLazy = z.lazy(() => - z.union([z.string(), z.number(), z.boolean(), z.record(z.string(), argtype), z.array(argtype)]) + z.union([z.string(), z.number(), z.boolean(), z.record(z.string(), argtype), z.array(argtype)]), ); // Different regular expressions used to validate formats like // <%= string interpolation %>, step.names or property.names, packages:versions -const interpolatedRegex = RegExp(/\w*<%= [^%]* %>\w*|[^<%=]*<%= [^%]* %>[^<%=]*/, 'i'); -const stepRegex = RegExp(/^[\w-]+\.[.\w-]+$/, 'i'); -const packageRegex = RegExp(/^(?@?[a-z0-9][a-z0-9-]{1,}[a-z0-9])(?::(?[^@]+))?(@(?[^\s]+))?$/, 'i'); -const jsonAbiPathRegex = RegExp(/^(?!.*\.d?$).*\.json?$/, 'i'); +const interpolatedRegex = RegExp(/\w*<%= ([^%]*) %>\w*|[^<%=]*<%= [^%]* %>[^<%=]*/, 'i'); +const actionNameRegex = RegExp(/^[\w-]+\.[.\w-]+$/, 'i'); +const packageRegex = RegExp( + /^(?@?[a-z0-9][a-z0-9-]{2,31}[a-z0-9])(?::(?[^@]{1,32}))?(@(?[^\s]{1,26}))?$/, + 'i', +); // This regex matches artifact names which are just capitalized words like solidity contract names const artifactNameRegex = RegExp(/^[A-Z]{1}[\w]+$/, 'i'); const artifactPathRegex = RegExp(/^.*\.sol:\w+/, 'i'); -// Because of a weird type cohercion, after using viem.isAddress during website build, -// the string type of the given value gets invalid to "never", and breaks the build. -const isAddress = (val: any): boolean => typeof val === 'string' && viem.isAddress(val); - -// Invoke target string schema -const targetString = z.string().refine( - (val) => - !!isAddress(val) || - !!val.match(interpolatedRegex) || - !!val.match(stepRegex) || - !!val.match(artifactNameRegex) || - !!val.match(artifactPathRegex), - (val) => ({ - message: `"${val}" must be a valid ethereum address, existing contract operation name, contract artifact name or filepath`, - }) -); +const artifactNameOrPathRegex = RegExp(artifactNameRegex.source + '|' + artifactPathRegex.source); -// stolen from https://stackoverflow.com/questions/3710204/how-to-check-if-a-string-is-a-valid-json-string -function tryParseJson(jsonString: string) { - try { - const o = JSON.parse(jsonString); +// This regex is stolen from viem isAddress source code +const addressRegex = /^0x[a-fA-F0-9]{40}$/; - // Handle non-exception-throwing cases: - // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, - // but... JSON.parse(null) returns null, and typeof null === "object", - // so we must check for that, too. Thankfully, null is falsey, so this suffices: - if (o && typeof o === 'object') { - return o; - } - } catch (e) { - // do nothing - } +const numericRegex = RegExp('[0-9]*|0x[a-fA-F0-9]*|' + interpolatedRegex.source); - return undefined; -} +// Invoke target string schema +const targetRegex = new RegExp( + [ + addressRegex.source, + interpolatedRegex.source, + actionNameRegex.source, + artifactNameRegex.source, + artifactPathRegex.source, + ].join('|'), +); // Note: The first schema defined contains required properties, we then merge a schema with the `deepPartial` function which contains the optional properties -const targetSchema = targetString.or(z.array(targetString).nonempty()); +const targetSchema = z + .string() + .regex(targetRegex) + .or(z.array(z.string().regex(targetRegex)).nonempty()); -export const deploySchema = z +export const sharedActionSchema = z .object({ + description: z.string().describe('Human-readable description of the operation.'), /** - * Artifact name of the target contract + * List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies. */ + depends: z + .array(z.string().regex(actionNameRegex, 'Must reference another action. Example: `"contract.Storage"`')) + .describe( + 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.', + ), + labels: z + .record(z.string().regex(actionNameRegex, 'Must be lowercase with no special characters'), z.string()) + .describe('Map of keys and values to help identify the outputs of this action'), + }) + .partial(); + +export const deploySchema = z + .object({ artifact: z .string() - .refine( - (val) => !!val.match(artifactNameRegex) || !!val.match(artifactPathRegex), - (val) => ({ message: `Artifact name "${val}" is invalid` }) + .regex( + artifactNameOrPathRegex, + 'Must be name of solidity artifact. Example: `MyContract` or `src/MyContract.sol:MyContract`', ) .describe('Artifact name of the target contract'), }) .merge( z .object({ - /** - * Description of the operation - */ - description: z.string().describe('Description of the operation'), - /** - * Determines whether contract should get priority in displays - */ highlight: z.boolean().describe('Determines whether contract should get priority in displays'), - /** - * Determines whether to deploy the contract using create2 - */ create2: z - .union([z.boolean(), z.string().refine((val) => isAddress(val))]) + .union([z.boolean(), z.string().regex(addressRegex, 'Must be EVM address')]) .describe( - 'Determines whether to deploy the contract using create2. If an address is specified, the arachnid create2 contract will be deployed/used from this address.' + 'Determines whether to deploy the contract using create2. If an address is specified, the arachnid create2 contract will be deployed/used from this address.', ), - /** - * Determines whether to deploy the contract using create2 - */ ifExists: z .enum(['continue']) .optional() .describe( - 'When deploying a contract with CREATE2, determines the behavior when the target contract is already deployed (ex. due to same bytecode and salt). Set to continue to allow the build to continue if the contract is found to have already been deployed. By default, an error is thrown and the action is halted.' + 'When deploying a contract with CREATE2, determines the behavior when the target contract is already deployed (ex. due to same bytecode and salt). Set to continue to allow the build to continue if the contract is found to have already been deployed. By default, an error is thrown and the action is halted.', ), - /** - * Contract deployer address. - * Must match the ethereum address format - */ from: z .string() - .refine( - (val) => isAddress(val) || !!val.match(interpolatedRegex), - (val) => ({ message: `"${val}" is not a valid ethereum address` }) + .regex( + RegExp(addressRegex.source + '|' + interpolatedRegex.source), + 'Must be Ethereum address or interpolated variable reference.', ) .describe('Contract deployer address. Must match the ethereum address format'), nonce: z - .union([z.string(), z.number()]) - .refine( - (val) => viem.isHex(val) || Number.isFinite(parseInt(val.toString())), - (val) => ({ - message: `Nonce ${val} must be a string, number or hexadecimal value`, - }) - ) + .union([z.string().regex(numericRegex, 'Must be numeric or hex quantity'), z.number()]) .transform((val) => { return val.toString(); }) - .describe('-'), - /** - * Abi of the contract being deployed - */ - abi: z - .string() - .refine( - (val) => - !!val.match(artifactNameRegex) || - !!val.match(jsonAbiPathRegex) || - !!val.match(interpolatedRegex) || - tryParseJson(val), - { - message: - 'ABI must be a valid JSON ABI string or artifact name or artifact name, see more here: https://docs.soliditylang.org/en/latest/abi-spec.html#json', - } - ) - .describe('Abi of the contract being deployed'), - /** - * An array of contract artifacts that have already been deployed with Cannon. - * This is useful when deploying proxy contracts. - */ + .describe( + 'Require for the transaction to be executed at a particular nonce on the signer. If the nonce does not match, an error is thrown.', + ), + abi: z.string().describe('String-format JSON of the contract being deployed'), abiOf: z - .array( - z.string().refine( - (val) => !!val.match(artifactNameRegex) || !!val.match(stepRegex), - (val) => ({ message: `Artifact name ${val} is invalid` }) - ) - ) + .array(z.string().regex(artifactNameRegex, 'Must be deployed contract artifact name. Example: `MyContract`')) .describe( - 'An array of contract artifacts that have already been deployed with Cannon. This is useful when deploying proxy contracts.' + 'An array of contract artifacts that have already been deployed with Cannon. This is useful when deploying proxy contracts.', ), - /** - * Constructor or initializer args - */ - args: z.array(argtype).describe('Constructor or initializer args'), - /** - * An array of contract operation names that deploy libraries this contract depends on. - */ + args: z.array(argtype).describe('Constructor or initializer args.'), libraries: z .record(z.string()) - .describe('An array of contract operation names that deploy libraries this contract depends on.'), - - /** - * Used to force new copy of a contract (not actually used) - */ - salt: z.string().describe('Used to force new copy of a contract (not actually used)'), - - /** - * Native currency value to send in the transaction - */ + .describe( + 'An array of addresses this contract depends on as a Solidity library. These contracts will be automatically linked.', + ), + salt: z.string().describe('Used to force new copy of a contract.'), value: z .string() - .refine((val) => !!val.match(interpolatedRegex) || !!viem.parseEther(val), { - message: 'Field value must be of numeric value', - }) - .describe('Native currency value to send in the transaction'), - /** - * Override transaction settings - */ + .regex(numericRegex, 'Must be a numeric value or interpolated variable reference') + .describe('Native currency value to send with the contract creation transaction'), overrides: z .object({ - gasLimit: z.string(), - simulate: z.boolean(), + gasLimit: z + .string() + .regex(numericRegex, 'Must be numeric or interpolated value reference') + .describe( + 'The maximum amount of gas consumed by the transaction. If left unset, the amount of gas required is estimated with the RPC node.', + ), + simulate: z.boolean().describe('Do not use. Only used internally.'), }) - .describe('Override transaction settings'), - - /** - * List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies. - */ - depends: z - .array( - z.string().refine( - (val) => !!val.match(stepRegex), - (val) => ({ - message: `Bad format for "${val}". Must reference a previous operation, example: 'contract.Storage'`, - }) - ) - ) - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), + .partial() + .describe('Override transaction settings.'), }) - .deepPartial() + .partial(), ) - .strict(); + .strict() + .merge(sharedActionSchema); export const pullSchema = z .object({ - /** - * Source of the cannonfile package to import from. - * Can be a cannonfile operation name or package name - */ source: z .string() - .refine( - (val) => !!val.match(packageRegex) || !!val.match(stepRegex) || !!val.match(interpolatedRegex), - (val) => ({ - message: `Source value: ${val} must match package formats: "package:version" or "package:version@preset" or operation name "import.Contract" or be an interpolated value`, - }) - ) - .refine( - (val) => { - const match = val.match(packageRegex); - - if (match) { - const nameSize = match!.groups!.name.length; - - return nameSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package name exceeds 32 bytes` }) + .regex( + RegExp(packageRegex.source + '|' + actionNameRegex + '|' + interpolatedRegex), + 'Must be package name or interpolated value reference', ) - .refine( - (val) => { - const match = val.match(packageRegex); - - if (match && match!.groups!.version) { - const versionSize = match!.groups!.version.length; - - return versionSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package version exceeds 32 bytes` }) - ) - .describe('Source of the cannonfile package to import from. Can be a cannonfile operation name or package name'), + .describe('Source of the cannonfile package to import from. Can be a cannonfile operation name or package name.'), }) .merge( z .object({ - /** - * Description of the operation - */ - description: z.string().describe('Description of the operation'), - /** - * ID of the chain to import the package from - */ - chainId: z.number().int().describe('ID of the chain to import the package from'), - /** - * Preset label of the package being imported - */ - preset: z.string().describe('Preset label of the package being imported'), - /** - * Previous operations this operation is dependent on - * ```toml - * depends = ['contract.Storage', 'import.Contract'] - * ``` - */ - depends: z - .array( - z.string().refine( - (val) => !!val.match(stepRegex), - (val) => ({ - message: `"${val}" is invalid. Must reference a previous operation, example: 'contract.Storage'`, - }) - ) - ) - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), + chainId: z.number().int().describe('ID of the chain to import the package from.'), + preset: z.string().describe('Preset label of the package being imported.'), }) - .deepPartial() + .partial(), ) - .strict(); + .strict() + .merge(sharedActionSchema); const invokeVarRecord = z .record( z .object({ - /** - * Name of the event to get data for - */ - event: z.string().describe('Name of the event to get data for'), - /** - * Data argument of the event output - */ - arg: z.number().int().describe('Data argument of the event output'), - /** - * Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional) - */ + event: z.string().describe('Name of the event to retrieve data from.'), + arg: z.number().int().describe('Within the given `event`, which event argument contains the data to import.'), expectCount: z .number() .int() .optional() .describe( - 'Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)' + 'Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional).', ), - - /** - * Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction. - */ allowEmptyEvents: z .boolean() .optional() .describe( - 'Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction.' + 'Bypass errors if an event is expected in the invoke operation but none are emitted in the transaction.', ), }) - .strict() + .strict(), ) .describe( - 'Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations' + 'Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations.', ); export const invokeSchema = z .object({ - /** - * Names of the contract to call or contract operation that deployed the contract to call - */ - target: targetSchema.describe('Names of the contract to call or contract operation that deployed the contract to call'), - /** - * Name of the function to call on the contract - */ - func: z.string().describe('Name of the function to call on the contract'), + target: targetSchema.describe('Names of the contract to call or contract operation that deployed the contract to call.'), + func: z.string().describe('Name of the function to call on the contract.'), }) .merge( z .object({ - /** - * Description of the operation - */ - description: z.string().describe('Description of the operation'), - /** - * JSON file of the contract ABI. - * Required if the target contains an address rather than a contract operation name. - */ abi: z .string() - .refine( - (val) => - !!val.match(artifactNameRegex) || - !!val.match(jsonAbiPathRegex) || - !!val.match(interpolatedRegex) || - tryParseJson(val), - { - message: - 'ABI must be a valid JSON ABI string or artifact name, see more here: https://docs.soliditylang.org/en/latest/abi-spec.html#json', - } - ) .describe( - 'JSON file of the contract ABI. Required if the target contains an address rather than a contract operation name.' + 'String-encoded JSON of the contract ABI. Required if the target contains an address rather than a contract operation name.', + ), + args: z + .array(argtype) + .describe( + 'Arguments to use when invoking this call. Must match the number of arguments expected when calling the EVM function. Can be omitted if no arguments are required.', ), - - /** - * Arguments to use when invoking this call. - */ - args: z.array(argtype).describe('Arguments to use when invoking this call.'), - /** - * The calling address to use when invoking this call. - */ from: z .string() - .refine( - (val) => isAddress(val) || !!val.match(interpolatedRegex), - (val) => ({ message: `"${val}" must be a valid ethereum address` }) + .regex( + RegExp(addressRegex.source + '|' + interpolatedRegex.source), + 'Must be Ethereum address or interpolated variable reference.', ) .describe('The calling address to use when invoking this call.'), - - /** - * Specify a function to use as the 'from' value in a function call. Example `owner()`. - */ fromCall: z .object({ - /** - * The name of a view function to call on this contract. The result will be used as the from input. - */ func: z .string() .describe('The name of a view function to call on this contract. The result will be used as the from input.'), - /** - * The arguments to pass into the function being called. - */ args: z.array(argtype).optional().describe('The arguments to pass into the function being called.'), }) .describe("Specify a function to use as the 'from' value in a function call. Example `owner()`."), - /** - * The amount of ether/wei to send in the transaction. - */ value: z .string() - .refine((val) => !!val.match(interpolatedRegex) || !!viem.parseEther(val), { - message: 'Field must be of numeric value', - }) - .describe('The amount of ether/wei to send in the transaction.'), - /** - * Override transaction settings - */ + .regex(numericRegex, 'Must be numeric or interpolated value reference') + .describe('The amount of ether/wei to send with the transaction.'), overrides: z .object({ /** * Gas limit to send along with the transaction */ - gasLimit: z.string().refine((val) => !!parseInt(val), { message: 'Gas limit is invalid' }), + gasLimit: z + .string() + .regex(numericRegex, 'Must be numeric or interpolated value reference') + .describe( + 'The maximum amount of gas that can be consumed by the transaction. If unset, defaults to the estimated gas limit.', + ), }) - .describe('Override transaction settings'), - /** - * Object defined to hold extra transaction result data. - * For now its limited to getting event data so it can be reused in other operations - */ + .partial() + .describe('Override transaction settings.'), var: invokeVarRecord, extra: invokeVarRecord.describe( - '⚠ Deprecated in favor of var. Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations. Use `var` instead.' + '⚠ Deprecated in favor of var. Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations. Use `var` instead.', ), - /** - * Object defined to hold deployment transaction result data. - * For now its limited to getting deployment event data so it can be reused in other operations - */ factory: z .record( z.object({ - /** - * Name of the event to get data for - */ - event: z.string().describe('Name of the event to get data for'), - /** - * Data argument of the event output - */ - arg: z.number().int().describe('Data argument of the event output'), - - /** - * Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional) - */ + event: z.string().describe('Name of the event containing the deployed contract address.'), + arg: z.number().int().describe('Index of the event containing the deployed contract address.'), expectCount: z .number() .int() .optional() .describe( - 'Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)' + 'Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional).', ), - - /** - * Name of the contract artifact - */ artifact: z .string() - .refine( - (val) => !!val.match(artifactNameRegex) || !!val.match(artifactPathRegex), - (val) => ({ message: `"${val}" must match a contract artifact name or path` }) - ) + .regex(artifactNameOrPathRegex, 'Must a contract artifact name or path.') .optional() - .describe('Name of the contract artifact'), - - /** - * An array of contract artifacts that have already been deployed with Cannon. - * Used if the code for the deployed contract is not available in the artifacts. - */ + .describe('Name of the contract artifact.'), abiOf: z - .array( - z.string().refine( - (val) => !!val.match(artifactNameRegex) || !!val.match(stepRegex), - (val) => ({ - message: `"${val}" must match a previously defined contract operation name or contract artifact name or path`, - }) - ) - ) + .array(z.string().regex(artifactNameRegex, 'Must be a deployed contract name.')) .optional() .describe( - 'An array of contract artifacts that have already been deployed with Cannon. Used if the code for the deployed contract is not available in the artifacts.' + 'An array of contract artifacts that have already been deployed with Cannon. Used if the code for the deployed contract is not available in the artifacts.', ), abi: z .string() - .refine( - (val) => - !!val.match(artifactNameRegex) || - !!val.match(jsonAbiPathRegex) || - !!val.match(interpolatedRegex) || - tryParseJson(val), - { - message: - 'ABI must be a valid JSON ABI string or artifact name, see more here: https://docs.soliditylang.org/en/latest/abi-spec.html#json', - } - ) .optional() - .describe('Abi of the contract being deployed'), - - /** - * Constructor or initializer args - */ - constructorArgs: z.array(argtype).optional().describe('Constructor or initializer args'), - - /** - * Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction. - */ + .describe( + 'String-formatted ABI of the deployed contract. Will be stored in the generated contract artifact.', + ), + constructorArgs: z + .array(argtype) + .optional() + .describe('Arguments passed to the constructor. Required for block explorer verification.'), allowEmptyEvents: z .boolean() .optional() .describe( - 'Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction.' + 'Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction.', ), - - /** - * Determines whether contract should get priority in displays - */ - highlight: z.boolean().optional().describe('Determines whether contract should get priority in displays'), - }) + highlight: z + .boolean() + .optional() + .describe('DEPRECATED. Determines whether contract should get priority in displays'), + }), ) .optional() .describe( - 'Object defined to hold deployment transaction result data. For now its limited to getting deployment event data so it can be reused in other operations' - ), - /** - * Previous operations this operation is dependent on - */ - depends: z - .array( - z.string().refine( - (val) => !!val.match(stepRegex), - (val) => ({ - message: `"${val}" is invalid. Must reference a previous operation, example: 'contract.Storage'`, - }) - ) - ) - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' + 'Object defined to hold deployment transaction result data. For now its limited to getting deployment event data so it can be reused in other operations', ), }) - .partial() + .partial(), ) - .strict(); + .strict() + .merge(sharedActionSchema); export const cloneSchema = z .object({ - /** - * Name of the package to provision - */ source: z .string() - .refine( - (val) => !!val.match(packageRegex) || !!val.match(interpolatedRegex), - (val) => ({ - message: `Source value: ${val} must match package formats: "package:version" or "package:version@preset" or be an interpolated value`, - }) - ) - .refine( - (val) => { - const match = val.match(packageRegex); - if (match) { - const nameSize = match!.groups!.name.length; - - return nameSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package name exceeds 32 bytes` }) - ) - .refine( - (val) => { - const match = val.match(packageRegex); - - if (match && match!.groups!.version) { - const versionSize = match!.groups!.version.length; - - return versionSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package version exceeds 32 bytes` }) - ) - .describe('Name of the package to provision'), + .regex(RegExp(packageRegex.source + '|' + interpolatedRegex), 'Must be package name or interpolated value reference') + .describe('Source of the cannonfile package to clone from. Can be a cannonfile operation name or package name.'), }) .merge( z .object({ - /** - * Description of the operation - */ - description: z.string().describe('Description of the operation'), - /** - * ID of the chain to import the package from. - * Default - 13370 - */ - chainId: z.number().int().describe('ID of the chain to import the package from. Default - 13370'), - /** - * (DEPRECATED) Use `source` instead. Override the preset to use when provisioning this package. - * Default - "main" - */ + description: z.string().describe('Description of the operation.'), + chainId: z.number().int().describe('ID of the chain to import the package from. Default - 13370.'), sourcePreset: z .string() .describe( - '⚠ Deprecated in favor of appending @PRESET_NAME to source. Override the preset to use when provisioning this package. Default - "main"' + '⚠ DEPRECATED. Append @PRESET_NAME to `source` instead. Override the preset to use when provisioning this package. Default - "main"', ), - /** - * Name of the package to write the provisioned package to - */ target: z .string() - .refine( - (val) => !!val.match(packageRegex) || !!val.match(interpolatedRegex), - (val) => ({ - message: `Target value: ${val} must match package formats: "package:version" or "package:version@preset" or be an interpolated value`, - }) - ) - .refine( - (val) => { - const match = val.match(packageRegex); - if (match) { - const nameSize = match!.groups!.name.length; - - return nameSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package name exceeds 32 bytes` }) + .regex( + RegExp(packageRegex.source + '|' + interpolatedRegex.source), + `Must be cannon package format: "package:version" or "package:version@preset" or be an interpolated value. Additionally, package name, version, and preset can't be longer than normal.`, ) - .refine( - (val) => { - const match = val.match(packageRegex); - - if (match && match!.groups!.version) { - const versionSize = match!.groups!.version.length; - - return versionSize <= 32; - } else { - return true; - } - }, - (val) => ({ message: `Package reference "${val}" is too long. Package version exceeds 32 bytes` }) - ) - .describe('Name of the package to clone'), - /** - * (DEPRECATED) use `target` instead. Set the new preset to use for this package. - * Default - "main" - */ + .describe('Name of the package to write the cloned resulting package to.'), targetPreset: z .string() .describe( - '⚠ Deprecated in favor using target only with format packageName:version@targetPreset. Set the new preset to use for this package. Default - "main"' + '⚠ DEPRECATED. Use `target` only with format `packageName:version@targetPreset`. Set the new preset to use for this package. Default - `main`', ), - /** - * The settings to be used when initializing this Cannonfile. - * Overrides any defaults preset in the source package. - */ var: z .record(z.string()) .describe( - 'The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package.' + 'The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package.', ), - /** - * (DEPRECATED) use `var`. The settings to be used when initializing this Cannonfile. - * Overrides any defaults preset in the source package. - */ options: z .record(z.string()) .describe( - '⚠ Deprecated in favor of var. The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package.' + '⚠ DEPRECATED. Use `var` instead. The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package.', ), - /** - * Additional tags to set on the registry for when this provisioned package is published. - */ tags: z .array(z.string()) .describe('Additional tags to set on the registry for when this provisioned package is published.'), - /** - * Previous operations this operation is dependent on - */ - depends: z - .array( - z.string().refine( - (val) => !!val.match(stepRegex), - (val) => ({ - message: `"${val}" is invalid. Must reference a previous operation, example: 'contract.Storage'`, - }) - ) - ) - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), }) - .deepPartial() + .partial(), ) - .strict(); + .strict() + .merge(sharedActionSchema); export const routerSchema = z .object({ - /** - * Set of contracts that will be passed to the router - */ - contracts: z.array(z.string()).describe('Set of contracts that will be passed to the router'), - /** - * Description of the operation - */ - description: z.string().optional().describe('Description of the operation'), - /** - * Include a `receive` function on the router so that it can receive ETH (or, whatever the gas token is on your network). - * NOTE: you can always define `payable` functions on your end-functions to receive ETH as well. This is only for receiving ETH like a regular EOA would. - */ - includeReceive: z.boolean().optional(), - /** - * Address to pass to the from call - */ - from: z.string().optional().describe('Address to pass to the from call'), - /** - * Used to force new copy of a contract (not actually used) - */ - salt: z.string().optional().describe('Used to force new copy of a contract (not actually used)'), - /** - * Override transaction settings - */ + contracts: z.array(z.string()).describe('Set of contracts that the router will be able to resolve.'), + includeReceive: z + .boolean() + .describe( + 'Include a `receive` function on the router so that it can receive ETH (or whatever the gas token is on your network).\nNOTE: even if field is not enabled, your routed functions can still receive ETH. This only affects the behavior of the router receiving ETH without any data/a function call.', + ) + .optional(), + from: z.string().optional().describe('Address to pass to the from call.'), + salt: z.string().optional().describe('Used to force new copy of a contract.'), overrides: z .object({ - gasLimit: z.string().optional(), + gasLimit: z + .string() + .regex(numericRegex, 'Must be numeric or interpolated value reference') + .optional() + .describe( + 'The maximum amount of gas that can be spent when executing this transaction. If unset, the gas limit will be automatically computed with eth_estimateTransactionGas call on the RPC node.', + ), }) .optional() - .describe('Override transaction settings'), - /** - * List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies. - */ - depends: z - .array(z.string()) - .optional() - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), - highlight: z.boolean().optional().describe('Determines whether contract should get priority in displays'), + .describe('Override transaction settings.'), + highlight: z.boolean().optional().describe('Determines whether contract should get priority in displays.'), }) - .strict(); + .strict() + .merge(sharedActionSchema); export const diamondSchema = z .object({ - /** - * Set of contracts that will be passed to the router - */ - contracts: z.array(z.string()).describe('Set of contracts that should be facets of the Diamond proxy'), - /** - * Description of the operation - */ - description: z.string().optional().describe('Description of the action'), - /** - * Used to force new copy of a new diamond proxy - */ - salt: z.string().describe('Used to force new copy of a contract.'), - /** - * Override transaction settings - */ + contracts: z.array(z.string()).describe('Set of contracts that should be facets of the Diamond proxy.'), + salt: z.string().describe('Used to force deployment of a new copy of a contract.'), diamondArgs: z.object({ - owner: z.string().describe('Address has permission to change Diamond facets (ie proxied contracts to upgrade)'), + owner: z.string().describe('Address that has permission to change Diamond facets (ie proxied contracts to upgrade).'), init: z .string() .optional() - .describe('Address to DELEGATECALL on diamondCut() or constructor after the facets have been set'), + .describe('Address to DELEGATECALL on diamondCut() or constructor after the facets have been set.'), initCalldata: z.string().optional().describe('Additional data to send to the `init` DELEGATECALL'), }), immutable: z .boolean() .optional() .describe( - 'Prevents the diamond proxy from being modified in the future. Setting this value to `true` is irreversable once deployed.' + 'Prevents the diamond proxy from being modified in the future. Setting this value to `true` is irreversable once deployed.', ), overrides: z .object({ - gasLimit: z.string().optional(), + gasLimit: z + .string() + .regex(numericRegex, 'Must be numeric or interpolated value reference') + .optional() + .describe( + 'The maximum amount of gas that can be spent when executing this transaction. If unset, the gas limit will be automatically computed with eth_estimateTransactionGas call on the RPC node.', + ), }) .optional() - .describe('Override transaction settings'), - /** - * List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies. - */ - depends: z - .array(z.string()) - .optional() - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), + .describe('Override transaction settings.'), highlight: z.boolean().optional().describe('Determines whether contract should get priority in displays'), }) - .strict(); + .strict() + .merge(sharedActionSchema); -export const varSchema = z - .object({ - /** - * The setting value to apply - */ - defaultValue: z.string().optional().describe('⚠ Deprecated in favor of var. The value to set in the setting'), - /** - * Description of the operation - */ - description: z.string().optional().describe('Description of the operation'), - /** - * List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies. - */ - depends: z - .array(z.string()) - .optional() - .describe( - 'List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies.' - ), - }) - .catchall(z.string()); +export const varSchema = z.object({}).catchall(z.string()); /** * @internal NOTE: if you edit this schema, please also edit the constructor of ChainDefinition in 'definition.ts' to account for non-operation components */ export const chainDefinitionSchema = z .object({ - /** - * Name of the package - */ name: z .string() - .min(3) - .refine( - (val) => { - return new Blob([val]).size <= 32; - }, - (val) => ({ message: `Package name "${val}" is too long. Package name exceeds 32 bytes` }) + .regex( + RegExp(/^[a-z0-9-]{3,32}$/, 'gm'), + 'Name cannot contain any special characters and must be between 3 and 32 characters long', ) - .refine((val) => !!val.match(RegExp(/[a-zA-Z0-9-]+/, 'gm')), { - message: 'Name cannot contain any special characters', - }) - .describe('Name of the package'), - /** - * Version of the package - */ + .describe('Name of the package on the registry.'), version: z .string() - .refine( - (val) => { - return new Blob([val]).size <= 32; - }, - (val) => ({ message: `Package version "${val}" is too long. Package version exceeds 32 bytes` }) - ) - .refine((val) => !!val.match(RegExp(/[\w.]+/, 'gm')), { - message: 'Version cannot contain any special characters', - }) + .regex(RegExp(/^[\w.]{1,32}$/, 'gm'), 'Version must be between 1 and 32 characters long') .describe( - 'Version of the package. Publishes as the "latest" version by default in addition to the version specified here.' + 'Version of the package. Publishes as the "latest" version by default in addition to the version specified here. This value is only intended for human consumption, and does not have any effect on version tracking inside of Cannon.', ), - /** - * Preset of the package - */ preset: z .string() - .refine((val) => !!val.match(RegExp(/[\w.]+/, 'gm')), { - message: 'Preset cannot contain any special characters', - }) + .regex(RegExp(/^[\w.]{1,26}$/, 'gm'), 'Preset must be between 1 and 26 characters long') .describe( - 'Preset of the package (Presets are useful for distinguishing multiple deployments of the same protocol on the same chain.) Defaults to "main".' + 'Preset of the package (Presets are useful for distinguishing multiple deployments of the same protocol on the same chain.) Defaults to "main".', ) .optional(), - /** - * Whether or not source code from local package should be bundled in the package. - * NOTE: If this is set to true, it will not be possible to verify your contracts on etherscan with cannon - * If not specified, the value is treated as `false` (ie contract source codes included) - */ privateSourceCode: z .boolean() .describe( - 'Turns off inclusion of source code in packages. When set to true, Cannon cannot verify contracts on Etherscan. Defaults to false.' + 'Turns off inclusion of source code in packages. When set to true, Cannon cannot verify contracts on a block explorer, and your source code is hidden. Defaults to false.', ) .optional(), - /** - * Description for the package - */ - description: z.string().describe('Description for the package').optional(), - /** - * Keywords for search indexing - */ - keywords: z.array(z.string()).describe('Keywords for search indexing').optional(), - /** - * Any deployers that could publish this package. Will be used for automatic version management. - */ + description: z.string().describe('Human-readable short explanation about the package.').optional(), + keywords: z.array(z.string()).describe('Keywords for registry search indexing.').optional(), deployers: z - .array( - z.string().refine((val) => !!val.match(RegExp(/^0x[a-fA-F0-9]{40}$/, 'gm')), { - message: 'Invalid Ethereum address', - }) + .array(z.string().regex(addressRegex, 'Must be an EVM address')) + .describe( + 'Any deployers that could publish this package. Used *only* to automatically detect previous deployed package for version management.', ) - .describe('Any deployers that could publish this package. Will be used for automatic version management.') .optional(), + include: z.array(z.string()).describe('Array of additional files to load into the cannon deployment state.').optional(), }) .merge( z .object({ - /** - * Object that allows the definition of values for use in next operations - * ```toml - * [settings.owner] - * defaultValue: "some-eth-address" - * ``` - */ setting: z .record( z .object({ - /** - * Description of the operation - */ - description: z.string().describe('Description of the operation'), - /** - * Data type of the value being stored - */ type: z.enum(['number', 'string', 'boolean']).describe('Data type of the value being stored'), - /** - * Stored value of the setting - */ defaultValue: z.string().describe('Stored value of the setting'), }) .partial() + .merge(sharedActionSchema), ) .describe( - '⚠ Deprecated in favor of var. A setting is a variable that can be set (or overriden using the CLI) when building a Cannonfile. It is accessible elsewhere in the file a property of the settings object. For example, [setting.sampleSetting] can be referenced with <%= settings.sampleSetting %>' + '⚠ DEPRECATED. Use `var` instead. A setting is a variable that can be set (or overriden using the CLI) when building a Cannonfile. It is accessible elsewhere in the file a property of the settings object. For example, [setting.sampleSetting] can be referenced with <%= settings.sampleSetting %>', ), - /** - * @internal - */ pull: z .record(pullSchema) .describe( - 'Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to.' + 'Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to.', ), - /** - * @internal - */ import: z .record(pullSchema) .describe( - '⚠ Deprecated in favor of pull. Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to.' + '⚠ DEPRECATED. Use `pull` instead. Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to.', ), - /** - * @internal - */ clone: z .record(cloneSchema) .describe( - 'Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370).' + 'Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370).', ), - /** - * @internal - */ provision: z .record(cloneSchema) .describe( - '⚠ Deprecated in favor of clone. Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370).' + '⚠ DEPRECATED. Use `clone` instead. Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370).', ), - /** - * @internal - */ deploy: z.record(deploySchema).describe('Deploy a contract.'), - /** - * @internal - */ contract: z.record(deploySchema).describe('⚠ Deprecated in favor of deploy. Deploy a contract.'), - /** - * @internal - */ invoke: z.record(invokeSchema).describe('Call a function.'), - /** - * @internal - */ router: z .record(routerSchema) .describe('Generate a contract that proxies calls to multiple contracts using the synthetix router codegen.'), - /** - * @internal - */ diamond: z .record(diamondSchema) .describe( - 'Generate a upgradable contract that proxies calls to multiple contracts using a ERC2535 Diamond standard.' + 'Generate a upgradable contract that proxies calls to multiple contracts using a ERC2535 Diamond standard.', ), - /** - * @internal - */ var: z.record(varSchema).describe('Apply a setting or intermediate value.'), // ... there may be others that come from plugins }) - .deepPartial() + .partial(), ); diff --git a/packages/builder/src/steps/clone.ts b/packages/builder/src/steps/clone.ts index cf8f07653..6de1b2c8c 100644 --- a/packages/builder/src/steps/clone.ts +++ b/packages/builder/src/steps/clone.ts @@ -279,6 +279,7 @@ const cloneSpec = { [importLabel]: { url: newSubDeployUrl || '', tags: config.tags || ['latest'], + labels: config.labels, target: targetRef.fullPackageRef, preset: targetRef.preset, ...(await getOutputs(importRuntime, def, builtState))!, diff --git a/packages/builder/src/steps/deploy.ts b/packages/builder/src/steps/deploy.ts index ed87c5485..618c3fe93 100644 --- a/packages/builder/src/steps/deploy.ts +++ b/packages/builder/src/steps/deploy.ts @@ -5,7 +5,7 @@ import { z } from 'zod'; import { computeTemplateAccesses, mergeTemplateAccesses } from '../access-recorder'; import { ARACHNID_DEFAULT_DEPLOY_ADDR, ensureArachnidCreate2Exists, makeArachnidCreate2Txn } from '../create2'; import { CannonError, handleTxnError } from '../error'; -import { deploySchema } from '../schemas'; +import { deploySchema, sharedActionSchema } from '../schemas'; import { ChainArtifacts, ChainBuilderContext, @@ -24,7 +24,7 @@ const debug = Debug('cannon:builder:deploy'); * @public * @group Contract */ -export type Config = z.infer; +export type Config = z.infer & z.infer; export interface ContractOutputs { abi: string; @@ -34,7 +34,7 @@ export interface ContractOutputs { function resolveBytecode( artifactData: ContractArtifact, - config: Config + config: Config, ): [viem.Hex, { [sourceName: string]: { [libName: string]: string } }] { let injectedBytecode = artifactData.bytecode; const linkedLibraries: { [sourceName: string]: { [libName: string]: string } } = {}; @@ -76,7 +76,7 @@ function checkConstructorArgs(abi: viem.Abi, args: any[] | undefined) { if (suppliedArgs.length !== neededArgs.length) { throw new Error( - `incorrect number of constructor arguments to deploy contract. supplied: ${suppliedArgs.length}, expected: ${neededArgs.length}` + `incorrect number of constructor arguments to deploy contract. supplied: ${suppliedArgs.length}, expected: ${neededArgs.length}`, ); } } @@ -88,7 +88,7 @@ function generateOutputs( deployTxn: viem.TransactionReceipt | null, deployTxnBlock: viem.Block | null, deployAddress: viem.Address, - currentLabel: string + currentLabel: string, ): ChainArtifacts { const [, linkedLibraries] = resolveBytecode(artifactData, config); @@ -128,6 +128,7 @@ function generateOutputs( highlight: config.highlight, gasUsed: Number(deployTxn?.gasUsed) || 0, gasCost: deployTxn?.effectiveGasPrice.toString() || '0', + labels: config.labels, }, }, }; @@ -206,21 +207,21 @@ const deploySpec = { if (config.abiOf) { _.forEach( config.abiOf, - (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(v, possibleFields))) + (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(v, possibleFields))), ); } if (config.args) { _.forEach( config.args, - (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(v), possibleFields))) + (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(v), possibleFields))), ); } if (config.libraries) { _.forEach( config.libraries, - (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(v, possibleFields))) + (v) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(v, possibleFields))), ); } @@ -240,7 +241,7 @@ const deploySpec = { runtime: ChainBuilderRuntimeInfo, ctx: ChainBuilderContext, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { debug('exec', config); @@ -255,7 +256,7 @@ const deploySpec = { if (!artifactData) { throw new Error( - `bytecode/abi for artifact ${config.artifact} not found. please double check the contract name and your build configuration` + `bytecode/abi for artifact ${config.artifact} not found. please double check the contract name and your build configuration`, ); } @@ -297,7 +298,7 @@ const deploySpec = { if (config.create2) { const arachnidDeployerAddress = await ensureArachnidCreate2Exists( runtime, - typeof config.create2 === 'string' ? (config.create2 as viem.Address) : ARACHNID_DEFAULT_DEPLOY_ADDR + typeof config.create2 === 'string' ? (config.create2 as viem.Address) : ARACHNID_DEFAULT_DEPLOY_ADDR, ); debug('performing arachnid create2'); @@ -314,7 +315,7 @@ const deploySpec = { if (config.ifExists !== 'continue') { throw new CannonError( `The contract at the create2 destination ${addr} is already deployed, but the Cannon state does not recognize that this contract has already been deployed. This typically indicates incorrect upgrade configuration. Please confirm if this contract should already be deployed or not, and if you want to continue the build as-is, add 'ifExists = "continue"' to the step definition`, - 'CREATE2_COLLISION' + 'CREATE2_COLLISION', ); } } else { @@ -368,15 +369,15 @@ const deploySpec = { // if the code goes here, it means that the Create2 deployment failed // and prepareTransactionRequest will throw an error with the underlying revert message await runtime.provider.prepareTransactionRequest( - _.assign(txn, overrides, { account: signer.wallet.account || signer.address }) + _.assign(txn, overrides, { account: signer.wallet.account || signer.address }), ); throw new Error( - 'The CREATE2 contract seems to be failing in the constructor. However, we were not able to get a stack trace.' + 'The CREATE2 contract seems to be failing in the constructor. However, we were not able to get a stack trace.', ); } else { const preparedTxn = await runtime.provider.prepareTransactionRequest( - _.assign(txn, overrides, { account: signer.wallet.account || signer.address }) + _.assign(txn, overrides, { account: signer.wallet.account || signer.address }), ); const hash = await signer.wallet.sendTransaction(preparedTxn as any); @@ -412,7 +413,7 @@ const deploySpec = { null, // note: send zero address since there is no contract address viem.zeroAddress, - packageState.currentLabel + packageState.currentLabel, ); return await handleTxnError(contractArtifact, runtime.provider, error); @@ -429,11 +430,11 @@ const deploySpec = { ctx: ChainBuilderContext, config: Config, packageState: PackageState, - existingKeys: string[] + existingKeys: string[], ): Promise { if (existingKeys.length != 1) { throw new Error( - 'a contract can only be deployed on one transaction, so you can only supply one hash transaction to import' + 'a contract can only be deployed on one transaction, so you can only supply one hash transaction to import', ); } diff --git a/packages/builder/src/steps/diamond.ts b/packages/builder/src/steps/diamond.ts index 23ea7695a..febe3998c 100644 --- a/packages/builder/src/steps/diamond.ts +++ b/packages/builder/src/steps/diamond.ts @@ -112,7 +112,7 @@ const diamondStep = { accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(config.salt, possibleFields)); accesses.accesses.push( - ...config.contracts.map((c) => (c.includes('.') ? `imports.${c.split('.')[0]}` : `contracts.${c}`)) + ...config.contracts.map((c) => (c.includes('.') ? `imports.${c.split('.')[0]}` : `contracts.${c}`)), ); if (config?.overrides) { @@ -130,7 +130,7 @@ const diamondStep = { runtime: ChainBuilderRuntime, ctx: ChainBuilderContext, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { debug('exec', config); @@ -235,7 +235,7 @@ const diamondStep = { }; } catch (err) { throw new Error( - `failed to cut (upgrade) the diamond which is already deployed. This could happen for a few reasons:\n* the diamond owner has been changed and is now incorrect.\n* the diamond was previously made immutable and can no longer can be upgraded.\noriginal error: ${err}` + `failed to cut (upgrade) the diamond which is already deployed. This could happen for a few reasons:\n* the diamond owner has been changed and is now incorrect.\n* the diamond was previously made immutable and can no longer can be upgraded.\noriginal error: ${err}`, ); } }, @@ -244,13 +244,13 @@ const diamondStep = { async function firstTimeDeploy( runtime: ChainBuilderRuntime, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { const stepName = packageState.currentLabel.split('.')[1]; const signer = await runtime.getDefaultSigner( { data: viem.keccak256(viem.encodePacked(['string'], [config.salt])) as viem.Hex }, - config.salt + config.salt, ); debug('using deploy signer with address', signer.address); @@ -263,7 +263,7 @@ async function firstTimeDeploy( contract: ContractArtifact, deployedContractLabel: string, constructorArgs: any[], - salt = '' + salt = '', ) { debug('deploy contract', contract.contractName, deployedContractLabel, constructorArgs, salt); runtime.reportContractArtifact(`${contract.sourceName}:${contract.contractName}`, { @@ -309,7 +309,7 @@ async function firstTimeDeploy( if (!bytecode) { const hash = await signer.wallet.sendTransaction( - _.assign({ account: signer.wallet.account || signer.address }, create2Txn as any) + _.assign({ account: signer.wallet.account || signer.address }, create2Txn as any), ); const receipt = await runtime.provider.waitForTransactionReceipt({ hash }); const block = await runtime.provider.getBlock({ blockHash: receipt.blockHash }); @@ -325,6 +325,7 @@ async function firstTimeDeploy( highlight: deployedContractLabel === stepName ? config.highlight : false, gasUsed: Number(receipt.gasUsed), gasCost: receipt.effectiveGasPrice.toString(), + labels: config.labels, }; } else { outputContracts[deployedContractLabel] = { @@ -339,6 +340,7 @@ async function firstTimeDeploy( highlight: deployedContractLabel === stepName ? config.highlight : false, gasUsed: Number(0), gasCost: '0', + labels: config.labels, }; } @@ -368,7 +370,7 @@ async function firstTimeDeploy( (await import('../abis/diamond/Diamond.json')) as any, stepName, [addFacets, config.diamondArgs], - config.salt || '' + config.salt || '', ); return outputContracts; diff --git a/packages/builder/src/steps/invoke.ts b/packages/builder/src/steps/invoke.ts index 6c80f7b42..c9c3e9292 100644 --- a/packages/builder/src/steps/invoke.ts +++ b/packages/builder/src/steps/invoke.ts @@ -59,7 +59,7 @@ async function runTxn( config: Config, contract: Contract, signer: CannonSigner, - packageState: PackageState + packageState: PackageState, ): Promise<[viem.TransactionReceipt, EncodedTxnEvents]> { let txn: viem.Hash; @@ -68,7 +68,7 @@ async function runTxn( // if invoke calls succeeding when no action was actually performed. if ((await runtime.provider.getCode({ address: contract.address })) === '0x') { throw new Error( - `contract ${contract.address} for ${packageState.currentLabel} has no bytecode. This is most likely a missing dependency or bad state.` + `contract ${contract.address} for ${packageState.currentLabel} has no bytecode. This is most likely a missing dependency or bad state.`, ); } @@ -97,7 +97,7 @@ async function runTxn( // Attempt to encode data so that if any arguments have any type mismatches, we can catch them and present them to the user. const functionList = assembleFunctionSignatures(contract.abi); const neededFuncAbi = functionList.find( - (f) => config.func == f[1] || config.func == f[1].split('(')[0] + (f) => config.func == f[1] || config.func == f[1].split('(')[0], )?.[0] as viem.AbiFunction; if (!neededFuncAbi) { throw new Error( @@ -106,8 +106,8 @@ async function runTxn( }". List of recognized functions is:\n${functionList .map((v) => v[1]) .join( - '\n' - )}\n\nIf this is a proxy contract, make sure you’ve specified abiOf for the contract action in the cannonfile that deploys it. If you’re calling an overloaded function, update func to include parentheses.` + '\n', + )}\n\nIf this is a proxy contract, make sure you’ve specified abiOf for the contract action in the cannonfile that deploys it. If you’re calling an overloaded function, update func to include parentheses.`, ); } @@ -115,17 +115,17 @@ async function runTxn( debug('resolve from address', contract.address); const neededOwnerFuncAbi = functionList.find( - (f) => config.fromCall!.func == f[1] || config.fromCall!.func == f[1].split('(')[0] + (f) => config.fromCall!.func == f[1] || config.fromCall!.func == f[1].split('(')[0], )?.[0] as viem.AbiFunction; if (!neededOwnerFuncAbi) { throw new Error( `contract ${contract.address} for ${packageState.currentLabel} does not contain the function "${ config.fromCall.func }" to determine owner. List of recognized functions is:\n${Object.keys( - contract.abi.filter((v) => v.type === 'function').map((v) => (v as viem.AbiFunction).name) + contract.abi.filter((v) => v.type === 'function').map((v) => (v as viem.AbiFunction).name), ).join( - '\n' - )}\n\nIf this is a proxy contract, make sure you’ve specified abiOf for the contract action in the cannonfile that deploys it.` + '\n', + )}\n\nIf this is a proxy contract, make sure you’ve specified abiOf for the contract action in the cannonfile that deploys it.`, ); } const addressCall = await runtime.provider.simulateContract({ @@ -170,7 +170,7 @@ async function runTxn( const eventAbi = viem.getAbiItem({ abi: contract!.abi, name: l.eventName }) as any; return { name: l.eventName, args: eventAbi.inputs.map((i: any) => (l.args as any)[i.name]) }; }), - 'name' + 'name', ); debug('decoded events', txnEvents); @@ -198,7 +198,7 @@ function parseEventOutputs(config: Config['var'], txnEvents: EncodedTxnEvents[]) if (!config[name].allowEmptyEvents) { if (events.length === 0) { throw new Error( - `Event specified in cannonfile:\n\n ${expectedEvent} \n\ndoesn't exist or match an event emitted by the invoked function of the contract.` + `Event specified in cannonfile:\n\n ${expectedEvent} \n\ndoesn't exist or match an event emitted by the invoked function of the contract.`, ); } } @@ -230,7 +230,7 @@ async function importTxnData( ctx: ChainBuilderContext, config: Config, packageState: PackageState, - txns: TransactionMap + txns: TransactionMap, ) { const contracts: ChainArtifacts['contracts'] = {}; @@ -275,7 +275,7 @@ async function importTxnData( if (!abi) { throw new Error( - `factory."${topLabel}": must specify at least one of "artifact", "abi", or "abiOf" to resolve the contract ABI for the created contract.` + `factory."${topLabel}": must specify at least one of "artifact", "abi", or "abiOf" to resolve the contract ABI for the created contract.`, ); } @@ -453,7 +453,7 @@ const invokeSpec = { if (config.args) { _.forEach( config.args, - (a) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(a), possibleFields))) + (a) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(a), possibleFields))), ); } @@ -462,7 +462,7 @@ const invokeSpec = { _.forEach( config.fromCall.args, - (a) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(a), possibleFields))) + (a) => (accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(JSON.stringify(a), possibleFields))), ); } @@ -531,7 +531,7 @@ const invokeSpec = { runtime: ChainBuilderRuntimeInfo, ctx: ChainBuilderContext, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { debug('exec', config); @@ -591,6 +591,7 @@ ${getAllContractPaths(ctx).join('\n')}`); gasUsed: Number(receipt.gasUsed), gasCost: receipt.effectiveGasPrice.toString(), signer: viem.getAddress(receipt.from), + labels: config.labels, }; } @@ -602,7 +603,7 @@ ${getAllContractPaths(ctx).join('\n')}`); ctx: ChainBuilderContext, config: Config, packageState: PackageState, - existingKeys: string[] + existingKeys: string[], ): Promise { const txns: TransactionMap = {}; for (let i = 0; i < existingKeys.length; i++) { @@ -644,7 +645,7 @@ ${getAllContractPaths(ctx).join('\n')}`); const eventAbi = viem.getAbiItem({ abi: contract!.abi, name: l.eventName }) as any; return { name: l.eventName, args: eventAbi.inputs.map((i: any) => (l.args as any)[i.name]) }; }), - 'name' + 'name', ); txns[label] = { @@ -654,6 +655,7 @@ ${getAllContractPaths(ctx).join('\n')}`); gasUsed: Number(receipt.gasUsed), gasCost: receipt.effectiveGasPrice.toString(), signer: viem.getAddress(receipt.from), + labels: config.labels, }; } diff --git a/packages/builder/src/steps/pull.ts b/packages/builder/src/steps/pull.ts index 41e4e6730..a76290f75 100644 --- a/packages/builder/src/steps/pull.ts +++ b/packages/builder/src/steps/pull.ts @@ -74,7 +74,7 @@ const pullSpec = { runtime: ChainBuilderRuntime, ctx: ChainBuilderContext, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { const importLabel = packageState.currentLabel?.split('.')[1] || ''; debug('exec', config); @@ -87,7 +87,7 @@ const pullSpec = { runtime.emit( Events.Notice, packageState.currentLabel, - 'To prevent unexpected upgrades, it is strongly recommended to lock the version of the source package by specifying a version in the `source` field.' + 'To prevent unexpected upgrades, it is strongly recommended to lock the version of the source package by specifying a version in the `source` field.', ); } @@ -97,13 +97,13 @@ const pullSpec = { if (!deployInfo) { throw new Error( - `deployment not found: ${source}. please make sure it exists for the cannon network and ${preset} preset.` + `deployment not found: ${source}. please make sure it exists for the cannon network and ${preset} preset.`, ); } if (deployInfo.status === 'partial') { throw new Error( - `deployment status is incomplete for ${source}. cannot generate artifacts safely. please complete deployment to continue import.` + `deployment status is incomplete for ${source}. cannot generate artifacts safely. please complete deployment to continue import.`, ); } @@ -111,6 +111,7 @@ const pullSpec = { imports: { [importLabel]: { url: (await runtime.registry.getUrl(source, chainId))!, // todo: duplication + labels: config.labels, ...(await getOutputs(runtime, new ChainDefinition(deployInfo.def), deployInfo.state))!, }, }, diff --git a/packages/builder/src/steps/router.ts b/packages/builder/src/steps/router.ts index d5f1c1113..5b271243d 100644 --- a/packages/builder/src/steps/router.ts +++ b/packages/builder/src/steps/router.ts @@ -83,7 +83,7 @@ const routerStep = { let accesses = computeTemplateAccesses(config.from); accesses = mergeTemplateAccesses(accesses, computeTemplateAccesses(config.salt, possibleFields)); accesses.accesses.push( - ...config.contracts.map((c) => (c.includes('.') ? `imports.${c.split('.')[0]}` : `contracts.${c}`)) + ...config.contracts.map((c) => (c.includes('.') ? `imports.${c.split('.')[0]}` : `contracts.${c}`)), ); if (config?.overrides) { @@ -101,7 +101,7 @@ const routerStep = { runtime: ChainBuilderRuntime, ctx: ChainBuilderContext, config: Config, - packageState: PackageState + packageState: PackageState, ): Promise { debug('exec', config); @@ -208,6 +208,7 @@ const routerStep = { highlight: config.highlight, gasUsed: Number(receipt.gasUsed), gasCost: receipt.effectiveGasPrice.toString(), + labels: config.labels, }, } as ContractMap, }; diff --git a/packages/builder/src/types.ts b/packages/builder/src/types.ts index 9edc563b5..695b498db 100644 --- a/packages/builder/src/types.ts +++ b/packages/builder/src/types.ts @@ -37,6 +37,7 @@ export type ContractData = { contractName: string; sourceName: string; deployedOn: string; + labels?: Record; highlight?: boolean; gasUsed: number; gasCost: string; @@ -53,6 +54,7 @@ export type TransactionMap = { timestamp?: string; events: EventMap; deployedOn: string; + labels?: Record; gasUsed: number; gasCost: string; signer: string; @@ -102,13 +104,13 @@ const ethersStyleConstants = { encode: (a: string[], v: any[]) => { return viem.encodeAbiParameters( a.map((arg) => ({ type: arg })), - v + v, ); }, decode: (a: string[], v: viem.Hex | viem.ByteArray) => { return viem.decodeAbiParameters( a.map((arg) => ({ type: arg })), - v + v, ); }, }, @@ -187,7 +189,7 @@ export interface ChainBuilderRuntimeInfo { // returns a signer which should be used for sending the specified transaction. getDefaultSigner?: ( txn: Omit, - salt?: string + salt?: string, ) => Promise; // returns contract information from the specified artifact name. @@ -215,7 +217,7 @@ export interface PackageState { currentLabel: string; } -export type BundledOutput = { url: string; tags?: string[]; target?: string; preset?: string } & ChainArtifacts; +export type BundledOutput = { url: string; tags?: string[]; labels?: Record; target?: string; preset?: string } & ChainArtifacts; export interface BundledChainBuilderOutputs { [module: string]: BundledOutput; diff --git a/packages/lsp/Dockerfile b/packages/lsp/Dockerfile deleted file mode 100644 index 3cd8e42fe..000000000 --- a/packages/lsp/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:18-alpine AS build -WORKDIR /app -ENV REDIS_URL "" -ENV IPFS_URL "" -ENV MAINNET_PROVIDER_URL "" -COPY --link . . -RUN npm i -RUN npm run build - -FROM node:18-alpine -WORKDIR /app -COPY --link --from=build /app/dist dist -COPY --link --from=build /app/package.json package.json -COPY --link --from=build /app/package-lock.json package-lock.json -RUN npm install --omit=dev -CMD ["node", "dist/index.js"] diff --git a/packages/lsp/package.json b/packages/lsp/package.json index 3c2daba3c..a08807391 100644 --- a/packages/lsp/package.json +++ b/packages/lsp/package.json @@ -19,7 +19,9 @@ "dependencies": { "@taplo/lsp": "^0.8.0", "@usecannon/builder": "workspace:*", - "@usecannon/cli": "workspace:*" + "@usecannon/cli": "workspace:*", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12" }, "devDependencies": { "zod-to-json-schema": "^3.21.4" diff --git a/packages/lsp/scripts/generateSchema.js b/packages/lsp/scripts/generateSchema.mjs similarity index 100% rename from packages/lsp/scripts/generateSchema.js rename to packages/lsp/scripts/generateSchema.mjs diff --git a/packages/lsp/src/index.ts b/packages/lsp/src/index.ts new file mode 100644 index 000000000..30f7b5406 --- /dev/null +++ b/packages/lsp/src/index.ts @@ -0,0 +1,216 @@ +import { TaploLsp } from '@taplo/lsp'; + +import { + createConnection, + TextDocuments, + Diagnostic, + DiagnosticSeverity, + ProposedFeatures, + InitializeParams, + DidChangeConfigurationNotification, + CompletionItem, + CompletionItemKind, + TextDocumentPositionParams, + TextDocumentSyncKind, + InitializeResult, +} from 'vscode-languageserver/node'; + +import { TextDocument } from 'vscode-languageserver-textdocument'; + +// Create a connection for the server, using Node's IPC as a transport. +// Also include all preview / proposed LSP features. +const connection = createConnection(ProposedFeatures.all); + +// Create a simple text document manager. +const documents: TextDocuments = new TextDocuments(TextDocument); + +let hasConfigurationCapability = false; +let hasWorkspaceFolderCapability = false; +let hasDiagnosticRelatedInformationCapability = false; + +connection.onInitialize((params: InitializeParams) => { + const capabilities = params.capabilities; + + // Does the client support the `workspace/configuration` request? + // If not, we fall back using global settings. + hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration); + hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders); + hasDiagnosticRelatedInformationCapability = !!( + capabilities.textDocument && + capabilities.textDocument.publishDiagnostics && + capabilities.textDocument.publishDiagnostics.relatedInformation + ); + + const result: InitializeResult = { + capabilities: { + textDocumentSync: TextDocumentSyncKind.Incremental, + // Tell the client that this server supports code completion. + completionProvider: { + resolveProvider: true, + }, + }, + }; + if (hasWorkspaceFolderCapability) { + result.capabilities.workspace = { + workspaceFolders: { + supported: true, + }, + }; + } + return result; +}); + +connection.onInitialized(() => { + if (hasConfigurationCapability) { + // Register for all configuration changes. + connection.client.register(DidChangeConfigurationNotification.type, undefined); + } + if (hasWorkspaceFolderCapability) { + connection.workspace.onDidChangeWorkspaceFolders((_event) => { + connection.console.log('Workspace folder change event received.'); + }); + } +}); + +// The example settings +interface ExampleSettings { + maxNumberOfProblems: number; +} + +// The global settings, used when the `workspace/configuration` request is not supported by the client. +// Please note that this is not the case when using this server with the client provided in this example +// but could happen with other clients. +const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 }; +let globalSettings: ExampleSettings = defaultSettings; + +// Cache the settings of all open documents +const documentSettings: Map> = new Map(); + +connection.onDidChangeConfiguration((change) => { + if (hasConfigurationCapability) { + // Reset all cached document settings + documentSettings.clear(); + } else { + globalSettings = (change.settings.languageServerExample || defaultSettings); + } + + // Revalidate all open text documents + documents.all().forEach(validateTextDocument); +}); + +function getDocumentSettings(resource: string): Thenable { + if (!hasConfigurationCapability) { + return Promise.resolve(globalSettings); + } + let result = documentSettings.get(resource); + if (!result) { + result = connection.workspace.getConfiguration({ + scopeUri: resource, + section: 'languageServerExample', + }); + documentSettings.set(resource, result); + } + return result; +} + +// Only keep settings for open documents +documents.onDidClose((e) => { + documentSettings.delete(e.document.uri); +}); + +// The content of a text document has changed. This event is emitted +// when the text document first opened or when its content has changed. +documents.onDidChangeContent((change) => { + validateTextDocument(change.document); +}); + +async function validateTextDocument(textDocument: TextDocument): Promise { + // In this simple example we get the settings for every validate run. + const settings = await getDocumentSettings(textDocument.uri); + + // The validator creates diagnostics for all uppercase words length 2 and more + const text = textDocument.getText(); + const pattern = /\b[A-Z]{2,}\b/g; + let m: RegExpExecArray | null; + + let problems = 0; + const diagnostics: Diagnostic[] = []; + while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) { + problems++; + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Warning, + range: { + start: textDocument.positionAt(m.index), + end: textDocument.positionAt(m.index + m[0].length), + }, + message: `${m[0]} is all uppercase.`, + source: 'ex', + }; + if (hasDiagnosticRelatedInformationCapability) { + diagnostic.relatedInformation = [ + { + location: { + uri: textDocument.uri, + range: Object.assign({}, diagnostic.range), + }, + message: 'Spelling matters', + }, + { + location: { + uri: textDocument.uri, + range: Object.assign({}, diagnostic.range), + }, + message: 'Particularly for names', + }, + ]; + } + diagnostics.push(diagnostic); + } + + // Send the computed diagnostics to VS Code. + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); +} + +connection.onDidChangeWatchedFiles((_change) => { + // Monitored files have change in VS Code + connection.console.log('We received a file change event'); +}); + +// This handler provides the initial list of the completion items. +connection.onCompletion((_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { + // The pass parameter contains the position of the text document in + // which code complete got requested. For the example we ignore this + // info and always provide the same completion items. + return [ + { + label: 'TypeScript', + kind: CompletionItemKind.Text, + data: 1, + }, + { + label: 'JavaScript', + kind: CompletionItemKind.Text, + data: 2, + }, + ]; +}); + +// This handler resolves additional information for the item selected in +// the completion list. +connection.onCompletionResolve((item: CompletionItem): CompletionItem => { + if (item.data === 1) { + item.detail = 'TypeScript details'; + item.documentation = 'TypeScript documentation'; + } else if (item.data === 2) { + item.detail = 'JavaScript details'; + item.documentation = 'JavaScript documentation'; + } + return item; +}); + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); + +// Listen on the connection +connection.listen(); diff --git a/packages/lsp/src/schema.json b/packages/lsp/src/schema.json index fdc3dfce8..324afd8d9 100644 --- a/packages/lsp/src/schema.json +++ b/packages/lsp/src/schema.json @@ -1 +1 @@ -{"type":"object","properties":{"name":{"type":"string","minLength":3,"description":"Name of the package"},"version":{"type":"string","description":"Version of the package. Publishes as the \"latest\" version by default in addition to the version specified here."},"preset":{"type":"string","description":"Preset of the package (Presets are useful for distinguishing multiple deployments of the same protocol on the same chain.) Defaults to \"main\"."},"privateSourceCode":{"type":"boolean","description":"Turns off inclusion of source code in packages. When set to true, Cannon cannot verify contracts on Etherscan. Defaults to false."},"description":{"type":"string","description":"Description for the package"},"keywords":{"type":"array","items":{"type":"string"},"description":"Keywords for search indexing"},"deployers":{"type":"array","items":{"type":"string"},"description":"Any deployers that could publish this package. Will be used for automatic version management."},"setting":{"type":"object","additionalProperties":{"type":"object","properties":{"description":{"type":"string","description":"Description of the operation"},"type":{"type":"string","enum":["number","string","boolean"],"description":"Data type of the value being stored"},"defaultValue":{"type":"string","description":"Stored value of the setting"}},"additionalProperties":false},"description":"⚠ Deprecated in favor of var. A setting is a variable that can be set (or overriden using the CLI) when building a Cannonfile. It is accessible elsewhere in the file a property of the settings object. For example, [setting.sampleSetting] can be referenced with <%= settings.sampleSetting %>"},"pull":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"type":"string","description":"Source of the cannonfile package to import from. Can be a cannonfile operation name or package name"},"description":{"type":"string","description":"Description of the operation"},"chainId":{"type":"integer","description":"ID of the chain to import the package from"},"preset":{"type":"string","description":"Preset label of the package being imported"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"required":["source"],"additionalProperties":false},"description":"Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to."},"import":{"type":"object","additionalProperties":{"$ref":"#/properties/pull/additionalProperties"},"description":"⚠ Deprecated in favor of pull. Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to."},"clone":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"type":"string","description":"Name of the package to provision"},"description":{"type":"string","description":"Description of the operation"},"chainId":{"type":"integer","description":"ID of the chain to import the package from. Default - 13370"},"sourcePreset":{"type":"string","description":"⚠ Deprecated in favor of appending @PRESET_NAME to source. Override the preset to use when provisioning this package. Default - \"main\""},"target":{"type":"string","description":"Name of the package to clone"},"targetPreset":{"type":"string","description":"⚠ Deprecated in favor using target only with format packageName:version@targetPreset. Set the new preset to use for this package. Default - \"main\""},"var":{"type":"object","additionalProperties":{"type":"string"},"description":"The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package."},"options":{"type":"object","additionalProperties":{"type":"string"},"description":"⚠ Deprecated in favor of var. The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package."},"tags":{"type":"array","items":{"type":"string"},"description":"Additional tags to set on the registry for when this provisioned package is published."},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"required":["source"],"additionalProperties":false},"description":"Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370)."},"provision":{"type":"object","additionalProperties":{"$ref":"#/properties/clone/additionalProperties"},"description":"⚠ Deprecated in favor of clone. Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370)."},"deploy":{"type":"object","additionalProperties":{"type":"object","properties":{"artifact":{"type":"string","description":"Artifact name of the target contract"},"description":{"type":"string","description":"Description of the operation"},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"},"create2":{"anyOf":[{"type":"boolean"},{"type":"string"}],"description":"Determines whether to deploy the contract using create2. If an address is specified, the arachnid create2 contract will be deployed/used from this address."},"ifExists":{"type":"string","enum":["continue"]},"from":{"type":"string","description":"Contract deployer address. Must match the ethereum address format"},"nonce":{"type":["string","number"],"description":"-"},"abi":{"type":"string","description":"Abi of the contract being deployed"},"abiOf":{"type":"array","items":{"type":"string"},"description":"An array of contract artifacts that have already been deployed with Cannon. This is useful when deploying proxy contracts."},"args":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"object","additionalProperties":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"}},{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"}}]},"description":"Constructor or initializer args"},"libraries":{"type":"object","additionalProperties":{"type":"string"},"description":"An array of contract operation names that deploy libraries this contract depends on."},"salt":{"type":"string","description":"Used to force new copy of a contract (not actually used)"},"value":{"type":"string","description":"Native currency value to send in the transaction"},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string"},"simulate":{"type":"boolean"}},"additionalProperties":false,"description":"Override transaction settings"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"required":["artifact"],"additionalProperties":false},"description":"Deploy a contract."},"contract":{"type":"object","additionalProperties":{"$ref":"#/properties/deploy/additionalProperties"},"description":"⚠ Deprecated in favor of deploy. Deploy a contract."},"invoke":{"type":"object","additionalProperties":{"type":"object","properties":{"target":{"anyOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/properties/invoke/additionalProperties/properties/target/anyOf/0"},"minItems":1}],"description":"Names of the contract to call or contract operation that deployed the contract to call"},"func":{"type":"string","description":"Name of the function to call on the contract"},"description":{"type":"string","description":"Description of the operation"},"abi":{"type":"string","description":"JSON file of the contract ABI. Required if the target contains an address rather than a contract operation name."},"args":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"Arguments to use when invoking this call."},"from":{"type":"string","description":"The calling address to use when invoking this call."},"fromCall":{"type":"object","properties":{"func":{"type":"string","description":"The name of a view function to call on this contract. The result will be used as the from input."},"args":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"The arguments to pass into the function being called."}},"required":["func"],"additionalProperties":false,"description":"Specify a function to use as the 'from' value in a function call. Example `owner()`."},"value":{"type":"string","description":"The amount of ether/wei to send in the transaction."},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string"}},"required":["gasLimit"],"additionalProperties":false,"description":"Override transaction settings"},"var":{"type":"object","additionalProperties":{"type":"object","properties":{"event":{"type":"string","description":"Name of the event to get data for"},"arg":{"type":"integer","description":"Data argument of the event output"},"expectCount":{"type":"integer","description":"Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)"},"allowEmptyEvents":{"type":"boolean","description":"Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction."}},"required":["event","arg"],"additionalProperties":false},"description":"Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations"},"extra":{"type":"object","additionalProperties":{"$ref":"#/properties/invoke/additionalProperties/properties/var/additionalProperties"},"description":"⚠ Deprecated in favor of var. Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations. Use `var` instead."},"factory":{"type":"object","additionalProperties":{"type":"object","properties":{"event":{"type":"string","description":"Name of the event to get data for"},"arg":{"type":"integer","description":"Data argument of the event output"},"expectCount":{"type":"integer","description":"Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)"},"artifact":{"type":"string","description":"Name of the contract artifact"},"abiOf":{"type":"array","items":{"type":"string"},"description":"An array of contract artifacts that have already been deployed with Cannon. Used if the code for the deployed contract is not available in the artifacts."},"abi":{"type":"string","description":"Abi of the contract being deployed"},"constructorArgs":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"Constructor or initializer args"},"allowEmptyEvents":{"type":"boolean","description":"Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction."},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"}},"required":["event","arg"],"additionalProperties":false},"description":"Object defined to hold deployment transaction result data. For now its limited to getting deployment event data so it can be reused in other operations"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"required":["target","func"],"additionalProperties":false},"description":"Call a function."},"router":{"type":"object","additionalProperties":{"type":"object","properties":{"contracts":{"type":"array","items":{"type":"string"},"description":"Set of contracts that will be passed to the router"},"description":{"type":"string","description":"Description of the operation"},"includeReceive":{"type":"boolean"},"from":{"type":"string","description":"Address to pass to the from call"},"salt":{"type":"string","description":"Used to force new copy of a contract (not actually used)"},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string"}},"additionalProperties":false,"description":"Override transaction settings"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"}},"required":["contracts"],"additionalProperties":false},"description":"Generate a contract that proxies calls to multiple contracts using the synthetix router codegen."},"diamond":{"type":"object","additionalProperties":{"type":"object","properties":{"contracts":{"type":"array","items":{"type":"string"},"description":"Set of contracts that should be facets of the Diamond proxy"},"description":{"type":"string","description":"Description of the action"},"salt":{"type":"string","description":"Used to force new copy of a contract."},"diamondArgs":{"type":"object","properties":{"owner":{"type":"string","description":"Address has permission to change Diamond facets (ie proxied contracts to upgrade)"},"init":{"type":"string","description":"Address to DELEGATECALL on diamondCut() or constructor after the facets have been set"},"initCalldata":{"type":"string","description":"Additional data to send to the `init` DELEGATECALL"}},"required":["owner"],"additionalProperties":false},"immutable":{"type":"boolean","description":"Prevents the diamond proxy from being modified in the future. Setting this value to `true` is irreversable once deployed."},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string"}},"additionalProperties":false,"description":"Override transaction settings"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"}},"required":["contracts","salt","diamondArgs"],"additionalProperties":false},"description":"Generate a upgradable contract that proxies calls to multiple contracts using a ERC2535 Diamond standard."},"var":{"type":"object","additionalProperties":{"type":"object","properties":{"defaultValue":{"type":"string","description":"⚠ Deprecated in favor of var. The value to set in the setting"},"description":{"type":"string","description":"Description of the operation"},"depends":{"type":"array","items":{"type":"string"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"additionalProperties":{"type":"string"}},"description":"Apply a setting or intermediate value."}},"required":["name","version"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} \ No newline at end of file +{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9-]{3,32}$","description":"Name of the package on the registry."},"version":{"type":"string","pattern":"^[\\w.]{1,32}$","description":"Version of the package. Publishes as the \"latest\" version by default in addition to the version specified here. This value is only intended for human consumption, and does not have any effect on version tracking inside of Cannon."},"preset":{"type":"string","pattern":"^[\\w.]{1,26}$","description":"Preset of the package (Presets are useful for distinguishing multiple deployments of the same protocol on the same chain.) Defaults to \"main\"."},"privateSourceCode":{"type":"boolean","description":"Turns off inclusion of source code in packages. When set to true, Cannon cannot verify contracts on a block explorer, and your source code is hidden. Defaults to false."},"description":{"type":"string","description":"Human-readable short explanation about the package."},"keywords":{"type":"array","items":{"type":"string"},"description":"Keywords for registry search indexing."},"deployers":{"type":"array","items":{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$"},"description":"Any deployers that could publish this package. Used *only* to automatically detect previous deployed package for version management."},"setting":{"type":"object","additionalProperties":{"type":"object","properties":{"type":{"type":"string","enum":["number","string","boolean"],"description":"Data type of the value being stored"},"defaultValue":{"type":"string","description":"Stored value of the setting"},"description":{"type":"string","description":"Human-readable description of the operation."},"depends":{"type":"array","items":{"type":"string","pattern":"^[\\w-]+\\.[.\\w-]+$"},"description":"List of operations that this operation depends on, which Cannon will execute first. If unspecified, Cannon automatically detects dependencies."}},"additionalProperties":false},"description":"⚠ DEPRECATED. Use `var` instead. A setting is a variable that can be set (or overriden using the CLI) when building a Cannonfile. It is accessible elsewhere in the file a property of the settings object. For example, [setting.sampleSetting] can be referenced with <%= settings.sampleSetting %>"},"pull":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"type":"string","pattern":"^(?@?[a-z0-9][a-z0-9-]{2,31}[a-z0-9])(?::(?[^@]{1,32}))?(@(?[^\\s]{1,26}))?$|\\/^[\\w-]+\\.[.\\w-]+$\\/i|\\/\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*\\/i","description":"Source of the cannonfile package to import from. Can be a cannonfile operation name or package name."},"chainId":{"type":"integer","description":"ID of the chain to import the package from."},"preset":{"type":"string","description":"Preset label of the package being imported."},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["source"],"additionalProperties":false},"description":"Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to."},"import":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"$ref":"#/properties/pull/additionalProperties/properties/source"},"chainId":{"$ref":"#/properties/pull/additionalProperties/properties/chainId"},"preset":{"$ref":"#/properties/pull/additionalProperties/properties/preset"},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["source"],"additionalProperties":false},"description":"⚠ DEPRECATED. Use `pull` instead. Import a package from the registry. This will make the output of that deployment, such as contract addresses, available to other operations in your Cannonfile. Imported packages must include deployments with chain ID that matches the chain ID of the network you are deploying to."},"clone":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"type":"string","pattern":"^(?@?[a-z0-9][a-z0-9-]{2,31}[a-z0-9])(?::(?[^@]{1,32}))?(@(?[^\\s]{1,26}))?$|\\/\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*\\/i","description":"Source of the cannonfile package to clone from. Can be a cannonfile operation name or package name."},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"chainId":{"type":"integer","description":"ID of the chain to import the package from. Default - 13370."},"sourcePreset":{"type":"string","description":"⚠ DEPRECATED. Append @PRESET_NAME to `source` instead. Override the preset to use when provisioning this package. Default - \"main\""},"target":{"type":"string","pattern":"^(?@?[a-z0-9][a-z0-9-]{2,31}[a-z0-9])(?::(?[^@]{1,32}))?(@(?[^\\s]{1,26}))?$|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"Name of the package to write the cloned resulting package to."},"targetPreset":{"type":"string","description":"⚠ DEPRECATED. Use `target` only with format `packageName:version@targetPreset`. Set the new preset to use for this package. Default - `main`"},"var":{"type":"object","additionalProperties":{"type":"string"},"description":"The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package."},"options":{"type":"object","additionalProperties":{"type":"string"},"description":"⚠ DEPRECATED. Use `var` instead. The settings to be used when initializing this Cannonfile. Overrides any defaults preset in the source package."},"tags":{"type":"array","items":{"type":"string"},"description":"Additional tags to set on the registry for when this provisioned package is published."},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["source"],"additionalProperties":false},"description":"Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370)."},"provision":{"type":"object","additionalProperties":{"type":"object","properties":{"source":{"$ref":"#/properties/clone/additionalProperties/properties/source"},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"chainId":{"$ref":"#/properties/clone/additionalProperties/properties/chainId"},"sourcePreset":{"$ref":"#/properties/clone/additionalProperties/properties/sourcePreset"},"target":{"$ref":"#/properties/clone/additionalProperties/properties/target"},"targetPreset":{"$ref":"#/properties/clone/additionalProperties/properties/targetPreset"},"var":{"$ref":"#/properties/clone/additionalProperties/properties/var"},"options":{"$ref":"#/properties/clone/additionalProperties/properties/options"},"tags":{"$ref":"#/properties/clone/additionalProperties/properties/tags"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["source"],"additionalProperties":false},"description":"⚠ DEPRECATED. Use `clone` instead. Deploy a new instance of a package from the registry. Packages may only be provisioned if they include a local, Cannon deployment (Chain ID: 13370)."},"deploy":{"type":"object","additionalProperties":{"type":"object","properties":{"artifact":{"type":"string","pattern":"^[A-Z]{1}[\\w]+$|^.*\\.sol:\\w+","description":"Artifact name of the target contract"},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"},"create2":{"anyOf":[{"type":"boolean"},{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$"}],"description":"Determines whether to deploy the contract using create2. If an address is specified, the arachnid create2 contract will be deployed/used from this address."},"ifExists":{"type":"string","enum":["continue"],"description":"When deploying a contract with CREATE2, determines the behavior when the target contract is already deployed (ex. due to same bytecode and salt). Set to continue to allow the build to continue if the contract is found to have already been deployed. By default, an error is thrown and the action is halted."},"from":{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"Contract deployer address. Must match the ethereum address format"},"nonce":{"anyOf":[{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*"},{"type":"number"}],"description":"Require for the transaction to be executed at a particular nonce on the signer. If the nonce does not match, an error is thrown."},"abi":{"type":"string","description":"String-format JSON of the contract being deployed"},"abiOf":{"type":"array","items":{"type":"string","pattern":"^[A-Z]{1}[\\w]+$"},"description":"An array of contract artifacts that have already been deployed with Cannon. This is useful when deploying proxy contracts."},"args":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"object","additionalProperties":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"}},{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"}}]},"description":"Constructor or initializer args."},"libraries":{"type":"object","additionalProperties":{"type":"string"},"description":"An array of addresses this contract depends on as a Solidity library. These contracts will be automatically linked."},"salt":{"type":"string","description":"Used to force new copy of a contract."},"value":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"Native currency value to send with the contract creation transaction"},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The maximum amount of gas consumed by the transaction. If left unset, the amount of gas required is estimated with the RPC node."},"simulate":{"type":"boolean","description":"Do not use. Only used internally."}},"additionalProperties":false,"description":"Override transaction settings."}},"required":["artifact"],"additionalProperties":false},"description":"Deploy a contract."},"contract":{"type":"object","additionalProperties":{"$ref":"#/properties/deploy/additionalProperties"},"description":"⚠ Deprecated in favor of deploy. Deploy a contract."},"invoke":{"type":"object","additionalProperties":{"type":"object","properties":{"target":{"anyOf":[{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*|^[\\w-]+\\.[.\\w-]+$|^[A-Z]{1}[\\w]+$|^.*\\.sol:\\w+"},{"type":"array","items":{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*|^[\\w-]+\\.[.\\w-]+$|^[A-Z]{1}[\\w]+$|^.*\\.sol:\\w+"},"minItems":1}],"description":"Names of the contract to call or contract operation that deployed the contract to call."},"func":{"type":"string","description":"Name of the function to call on the contract."},"abi":{"type":"string","description":"String-encoded JSON of the contract ABI. Required if the target contains an address rather than a contract operation name."},"args":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"Arguments to use when invoking this call. Must match the number of arguments expected when calling the EVM function. Can be omitted if no arguments are required."},"from":{"type":"string","pattern":"^0x[a-fA-F0-9]{40}$|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The calling address to use when invoking this call."},"fromCall":{"type":"object","properties":{"func":{"type":"string","description":"The name of a view function to call on this contract. The result will be used as the from input."},"args":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"The arguments to pass into the function being called."}},"required":["func"],"additionalProperties":false,"description":"Specify a function to use as the 'from' value in a function call. Example `owner()`."},"value":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The amount of ether/wei to send with the transaction."},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The maximum amount of gas that can be consumed by the transaction. If unset, defaults to the estimated gas limit."}},"additionalProperties":false,"description":"Override transaction settings."},"var":{"type":"object","additionalProperties":{"type":"object","properties":{"event":{"type":"string","description":"Name of the event to retrieve data from."},"arg":{"type":"integer","description":"Within the given `event`, which event argument contains the data to import."},"expectCount":{"type":"integer","description":"Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)."},"allowEmptyEvents":{"type":"boolean","description":"Bypass errors if an event is expected in the invoke operation but none are emitted in the transaction."}},"required":["event","arg"],"additionalProperties":false},"description":"Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations."},"extra":{"type":"object","additionalProperties":{"$ref":"#/properties/invoke/additionalProperties/properties/var/additionalProperties"},"description":"⚠ Deprecated in favor of var. Object defined to hold transaction result data in a setting. For now its limited to getting event data so it can be reused in other operations. Use `var` instead."},"factory":{"type":"object","additionalProperties":{"type":"object","properties":{"event":{"type":"string","description":"Name of the event containing the deployed contract address."},"arg":{"type":"integer","description":"Index of the event containing the deployed contract address."},"expectCount":{"type":"integer","description":"Number of matching contract events which should be seen by this event (default 1) (set to 0 to make optional)."},"artifact":{"type":"string","pattern":"^[A-Z]{1}[\\w]+$|^.*\\.sol:\\w+","description":"Name of the contract artifact."},"abiOf":{"type":"array","items":{"type":"string","pattern":"^[A-Z]{1}[\\w]+$"},"description":"An array of contract artifacts that have already been deployed with Cannon. Used if the code for the deployed contract is not available in the artifacts."},"abi":{"type":"string","description":"String-formatted ABI of the deployed contract. Will be stored in the generated contract artifact."},"constructorArgs":{"type":"array","items":{"$ref":"#/properties/deploy/additionalProperties/properties/args/items"},"description":"Arguments passed to the constructor. Required for block explorer verification."},"allowEmptyEvents":{"type":"boolean","description":"Bypass error messages if an event is expected in the invoke operation but none are emitted in the transaction."},"highlight":{"type":"boolean","description":"DEPRECATED. Determines whether contract should get priority in displays"}},"required":["event","arg"],"additionalProperties":false},"description":"Object defined to hold deployment transaction result data. For now its limited to getting deployment event data so it can be reused in other operations"}},"required":["target","func"],"additionalProperties":false},"description":"Call a function."},"router":{"type":"object","additionalProperties":{"type":"object","properties":{"contracts":{"type":"array","items":{"type":"string"},"description":"Set of contracts that the router will be able to resolve."},"includeReceive":{"type":"boolean","description":"Include a `receive` function on the router so that it can receive ETH (or whatever the gas token is on your network).\nNOTE: even if field is not enabled, your routed functions can still receive ETH. This only affects the behavior of the router receiving ETH without any data/a function call."},"from":{"type":"string","description":"Address to pass to the from call."},"salt":{"type":"string","description":"Used to force new copy of a contract."},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The maximum amount of gas that can be spent when executing this transaction. If unset, the gas limit will be automatically computed with eth_estimateTransactionGas call on the RPC node."}},"additionalProperties":false,"description":"Override transaction settings."},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays."},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["contracts"],"additionalProperties":false},"description":"Generate a contract that proxies calls to multiple contracts using the synthetix router codegen."},"diamond":{"type":"object","additionalProperties":{"type":"object","properties":{"contracts":{"type":"array","items":{"type":"string"},"description":"Set of contracts that should be facets of the Diamond proxy."},"salt":{"type":"string","description":"Used to force deployment of a new copy of a contract."},"diamondArgs":{"type":"object","properties":{"owner":{"type":"string","description":"Address that has permission to change Diamond facets (ie proxied contracts to upgrade)."},"init":{"type":"string","description":"Address to DELEGATECALL on diamondCut() or constructor after the facets have been set."},"initCalldata":{"type":"string","description":"Additional data to send to the `init` DELEGATECALL"}},"required":["owner"],"additionalProperties":false},"immutable":{"type":"boolean","description":"Prevents the diamond proxy from being modified in the future. Setting this value to `true` is irreversable once deployed."},"overrides":{"type":"object","properties":{"gasLimit":{"type":"string","pattern":"[0-9]*|0x[a-fA-F0-9]*|\\w*<%= [^%]* %>\\w*|[^<%=]*<%= [^%]* %>[^<%=]*","description":"The maximum amount of gas that can be spent when executing this transaction. If unset, the gas limit will be automatically computed with eth_estimateTransactionGas call on the RPC node."}},"additionalProperties":false,"description":"Override transaction settings."},"highlight":{"type":"boolean","description":"Determines whether contract should get priority in displays"},"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"required":["contracts","salt","diamondArgs"],"additionalProperties":false},"description":"Generate a upgradable contract that proxies calls to multiple contracts using a ERC2535 Diamond standard."},"var":{"type":"object","additionalProperties":{"type":"object","properties":{"description":{"$ref":"#/properties/setting/additionalProperties/properties/description"},"depends":{"$ref":"#/properties/setting/additionalProperties/properties/depends"}},"additionalProperties":false},"description":"Apply a setting or intermediate value."}},"required":["name","version"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 763cd92f2..451db6bd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -228,7 +228,7 @@ importers: dependencies: '@usecannon/router': specifier: ^4.0.1 - version: 4.0.1 + version: 4.1.0 '@usecannon/web-solc': specifier: 0.5.1 version: 0.5.1 @@ -298,13 +298,13 @@ importers: version: 2.0.3 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.5.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) + version: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)) rollup: specifier: ^4.18.1 version: 4.21.0 ts-jest: specifier: ^29.1.2 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)))(typescript@5.5.4) typedoc: specifier: ^0.26.5 version: 0.26.6(typescript@5.5.4) @@ -410,7 +410,7 @@ importers: version: 1.7.5(debug@4.3.6) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)) + version: 29.7.0(@types/node@22.5.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) mock-fs: specifier: ^5.2.0 version: 5.2.0 @@ -419,10 +419,10 @@ importers: version: 3.0.3 ts-jest: specifier: ^29.1.2 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.7.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)))(typescript@5.5.4) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.5.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)))(typescript@5.5.4) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.7.5)(typescript@5.5.4) + version: 10.9.2(@types/node@22.5.0)(typescript@5.5.4) typedoc: specifier: ^0.26.5 version: 0.26.6(typescript@5.5.4) @@ -530,6 +530,12 @@ importers: '@usecannon/cli': specifier: workspace:* version: link:../cli + vscode-languageserver: + specifier: ^9.0.1 + version: 9.0.1 + vscode-languageserver-textdocument: + specifier: ^1.0.12 + version: 1.0.12 devDependencies: zod-to-json-schema: specifier: ^3.21.4 @@ -3501,10 +3507,6 @@ packages: resolution: {integrity: sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==} engines: {node: '>=16.0.0'} - '@metamask/utils@9.1.0': - resolution: {integrity: sha512-g2REf+xSt0OZfMoNNdC4+/Yy8eP3KUqvIArel54XRFKPoXbHI6+YjFfrLtfykWBjffOp7DTfIc3Kvk5TLfuiyg==} - engines: {node: '>=16.0.0'} - '@metamask/utils@9.3.0': resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==} engines: {node: '>=16.0.0'} @@ -5169,6 +5171,7 @@ packages: '@safe-global/safe-core-sdk-types@5.0.3': resolution: {integrity: sha512-SNoIq/bYeUvxtB9bn+9FVMcCW3SCOJaK6crRN7DXY+N2xaLtTMAaGeUCPuOGsHxfAJVkO+CdiwWNFoqt9GN0Zg==} + deprecated: 'WARNING: This project has been renamed to @safe-global/types-kit. Please, migrate from @safe-global/safe-core-sdk-types@5.1.0 to @safe-global/types-kit@1.0.0.' '@safe-global/safe-deployments@1.37.3': resolution: {integrity: sha512-EtbiOJVGe697+GcbHtfo75NYpp+hTlIIBqL2ETPLGoQBHoxo9HWbGX/6ZkVxsZv/NN4nKawyMi+MvpUkH9VXGg==} @@ -6162,8 +6165,8 @@ packages: resolution: {integrity: sha512-SSID1UUH84LPMp7PTloC0IijwCJpWfsSQXmvPpyJB8vjbAHOCNLRxsNT3PRgtZvmCWdTrfuYh3FMtsvlpB/UKQ==} hasBin: true - '@usecannon/router@4.0.1': - resolution: {integrity: sha512-2dR3tc7Py89FIQdZVdpOowdV4T41xPM9Twvdn2V3QWHsf09jpvP6v3RHnHRLfkf8Xetm3/f5IfUhS+fTR9Mg+g==} + '@usecannon/router@4.1.0': + resolution: {integrity: sha512-FpEmBz/s+cE4j9gAJecbuir/GgpbJKwhewxIoaUTrIajPNORrC08y4aUu/sF2mxIZvonivkSsBW5WHlsBW5Hug==} '@usecannon/web-solc@0.5.1': resolution: {integrity: sha512-wK8J1snp1ikYzpxA8LzhQwp+6cvmLDnFG2EaMLmqtQZD/FIz7xh8IaSgHUliwDBm9zdsPEIvXvhDvanyZ7IBuw==} @@ -6435,6 +6438,7 @@ packages: acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes peerDependencies: acorn: ^8 @@ -8334,6 +8338,7 @@ packages: eciesjs@0.3.20: resolution: {integrity: sha512-Rz5AB8v9+xmMdS/R7RzWPe/R8DP5QfyrkA6ce4umJopoB5su2H2aDy/GcgIfwhmCwxnBkqGf/PbGzmKcGtIgGA==} + deprecated: Please upgrade to v0.4+ ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -14996,6 +15001,23 @@ packages: vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + wagmi@2.12.7: resolution: {integrity: sha512-E7f+2fd+rZPJ3ggBZmVj064gYuCi/Z32x9WMfSDvj5jmGHDkAmTfSi9isKkjJrTf0I+sNxd3PCWku7pndFYsIw==} peerDependencies: @@ -19295,7 +19317,7 @@ snapshots: '@metamask/rpc-errors@6.3.1': dependencies: - '@metamask/utils': 9.1.0 + '@metamask/utils': 9.3.0 fast-safe-stringify: 2.1.1 transitivePeerDependencies: - supports-color @@ -19318,7 +19340,7 @@ snapshots: bufferutil: 4.0.8 cross-fetch: 4.0.0(encoding@0.1.13) date-fns: 2.30.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 eciesjs: 0.3.20 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -19346,7 +19368,7 @@ snapshots: '@types/dom-screen-wake-lock': 1.0.3 bowser: 2.11.0 cross-fetch: 4.0.0(encoding@0.1.13) - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 eciesjs: 0.3.20 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -19391,21 +19413,7 @@ snapshots: '@noble/hashes': 1.5.0 '@scure/base': 1.1.7 '@types/debug': 4.1.12 - debug: 4.3.6(supports-color@8.1.1) - pony-cause: 2.1.11 - semver: 7.6.3 - uuid: 9.0.1 - transitivePeerDependencies: - - supports-color - - '@metamask/utils@9.1.0': - dependencies: - '@ethereumjs/tx': 4.2.0 - '@metamask/superstruct': 3.1.0 - '@noble/hashes': 1.5.0 - '@scure/base': 1.1.7 - '@types/debug': 4.1.12 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 pony-cause: 2.1.11 semver: 7.6.3 uuid: 9.0.1 @@ -19417,7 +19425,7 @@ snapshots: '@ethereumjs/tx': 4.2.0 '@metamask/superstruct': 3.1.0 '@noble/hashes': 1.5.0 - '@scure/base': 1.1.9 + '@scure/base': 1.1.7 '@types/debug': 4.1.12 debug: 4.3.7 pony-cause: 2.1.11 @@ -22003,7 +22011,7 @@ snapshots: '@textlint/markdown-to-ast@12.6.1': dependencies: '@textlint/ast-node-types': 12.6.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 mdast-util-gfm-autolink-literal: 0.1.3 remark-footnotes: 3.0.0 remark-frontmatter: 3.0.0 @@ -22782,7 +22790,7 @@ snapshots: - typescript - utf-8-validate - '@usecannon/router@4.0.1': + '@usecannon/router@4.1.0': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/keccak256': 5.7.0 @@ -23383,13 +23391,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -25556,7 +25564,7 @@ snapshots: engine.io-client@6.5.4(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) xmlhttprequest-ssl: 2.0.0 @@ -27923,7 +27931,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -28490,7 +28498,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -30666,7 +30674,7 @@ snapshots: micromark@2.11.4: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 parse-entities: 2.0.0 transitivePeerDependencies: - supports-color @@ -30674,7 +30682,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -32884,7 +32892,7 @@ snapshots: require-in-the-middle@7.4.0: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -33440,7 +33448,7 @@ snapshots: socket.io-client@4.7.5(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 engine.io-client: 6.5.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -33451,14 +33459,14 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color socks-proxy-agent@8.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -34430,7 +34438,7 @@ snapshots: tuf-js@2.2.1: dependencies: '@tufjs/models': 2.0.1 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.7 make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -35068,6 +35076,21 @@ snapshots: vlq@1.0.1: {} + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + wagmi@2.12.7(@tanstack/query-core@5.52.0)(@tanstack/react-query@5.52.1(react@18.3.1))(@types/react@18.2.37)(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react-native@0.75.2(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.37)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(typescript@5.5.4)(utf-8-validate@5.0.10))(react@18.3.1)(rollup@3.29.4)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.21.15(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8): dependencies: '@tanstack/react-query': 5.52.1(react@18.3.1)