From f42fe848f64b18a39d72d387f3f13705f7af027e Mon Sep 17 00:00:00 2001 From: Gustavo Gonzalez Date: Mon, 10 Jul 2023 17:08:54 -0400 Subject: [PATCH] Migrate from defender autotask sdk to platform sdk --- .prettierignore | 13 ++ .prettierrc.js | 3 + .prettierrc.yml | 9 + README.md | 16 +- package.json | 2 +- src/cmd/deploy.ts | 205 ++++++++++-------- src/cmd/info.ts | 40 ++-- src/cmd/invoke.ts | 17 +- src/cmd/logs.ts | 15 +- src/cmd/remove.ts | 65 +++--- src/types/index.ts | 86 ++++---- src/utils/index.ts | 69 +++--- template/README.md | 16 +- .../hello-world/index.js | 0 template/serverless.yml | 4 +- yarn.lock | 157 +++++++++++++- 16 files changed, 460 insertions(+), 257 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.js create mode 100644 .prettierrc.yml rename template/{autotasks => actions}/hello-world/index.js (100%) diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9317ed5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,13 @@ +# Ignore relevant files in fixtures, sometimes they are intentionally malformed +packages/**/fixtures/**/*.json +packages/**/fixtures/**/*.md +packages/**/fixtures/**/*.js + +# Ignore all YAML file +*.yaml +*.yml + +# Ignore gitignore +.gitignore + +dist \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..67ce511 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('code-style/.prettierrc.js'), +}; diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..1120474 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,9 @@ +# https://prettier.io/docs/en/options.html +printWidth: 120 +trailingComma: 'all' +singleQuote: true +useTabs: false +tabWidth: 2 +semi: true +arrowParens: 'always' +quoteProps: 'consistent' \ No newline at end of file diff --git a/README.md b/README.md index 8a6fa20..927bbd9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Alternatively, you can install it directly into an existing project with: ## Setup -This plugin allows you to define Autotasks, Sentinels, Notifications, Relayers, Contracts, Policies and Secrets declaratively from a `serverless.yml` and provision them via the CLI using `serverless deploy`. An example template below with an autotask, a relayer, a policy and a single relayer API key defined: +This plugin allows you to define Actions, Sentinels, Notifications, Relayers, Contracts, Policies and Secrets declaratively from a `serverless.yml` and provision them via the CLI using `serverless deploy`. An example template below with an action, a relayer, a policy and a single relayer API key defined: ```yaml service: defender-serverless-template @@ -42,9 +42,9 @@ defender: secret: '${env:TEAM_API_SECRET}' functions: - autotask-example-1: + action-example-1: name: 'Hello world from serverless' - path: './autotasks/hello-world' + path: './actions/hello-world' relayer: ${self:resources.Resources.relayers.relayer-1} trigger: type: 'schedule' @@ -90,9 +90,9 @@ Any resource removed from the `serverless.yml` file does _not_ get automatically Exported serverless configurations with Block Explorer Api Keys will not contain the `key` field but instead a `key-hash` field which is a keccak256 hash of the key. This must be replaced with the actual `key` field (and `key-hash` removed) before deploying -### Secrets (Autotask) +### Secrets (Action) -Autotask secrets can be defined both globally and per stack. Secrets defined under `global` are not affected by changes to the `stackName` and will retain when redeployed under a new stack. Secrets defined under `stack` will be removed (on the condition that [SSOT mode](#SSOT-mode) is enabled) when the stack is redeployed under a new `stackName`. To reference secrets defined under `stack`, use the following format: `_`, for example `mystack_test`. +Action secrets can be defined both globally and per stack. Secrets defined under `global` are not affected by changes to the `stackName` and will retain when redeployed under a new stack. Secrets defined under `stack` will be removed (on the condition that [SSOT mode](#SSOT-mode) is enabled) when the stack is redeployed under a new `stackName`. To reference secrets defined under `stack`, use the following format: `_`, for example `mystack_test`. ```yaml secrets: @@ -111,7 +111,7 @@ We provide auto-generated documentation based on the JSON schemas: - [Defender Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/defender.md) - [Provider Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/provider.md) -- [Function (Autotask) Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/function.md) +- [Function (Action) Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/function.md) - [Resources Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/resources.md) More information on types can be found [here](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/index.ts). Specifically, the types preceded with `Y` (e.g. YRelayer). For the schemas, you can check out the [docs-schema](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs-schemas) folder. @@ -144,11 +144,11 @@ You can use `sls remove` to remove all defender resources defined in the `server ### Logs -You can use `sls logs --function --data {...}` to retrieve the latest autotask logs for a given autotask identifier (e.g. mystack.autotask-example-1). This command will run continiously and retrieve logs every 2 seconds. The `--data` flag is optional. +You can use `sls logs --function --data {...}` to retrieve the latest action logs for a given action identifier (e.g. mystack.action-example-1). This command will run continiously and retrieve logs every 2 seconds. The `--data` flag is optional. ### Invoke -You can use `sls invoke --function ` to manually run an autotask, given its identifier (e.g. mystack.autotask-example-1). +You can use `sls invoke --function ` to manually run an action, given its identifier (e.g. mystack.action-example-1). > Each command has a standard output to a JSON object. diff --git a/package.json b/package.json index e6cc738..a80aa79 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,10 @@ }, "dependencies": { "@openzeppelin/defender-admin-client": "^1.46.0", - "@openzeppelin/defender-autotask-client": "^1.46.0", "@openzeppelin/defender-relay-client": "^1.46.0", "@openzeppelin/defender-sentinel-client": "^1.46.0", "@openzeppelin/platform-deploy-client": "^0.8.0", + "@openzeppelin/platform-sdk": "^0.1.0", "keccak256": "^1.0.6", "lodash": "^4.17.21", "prompt": "^1.3.0" diff --git a/src/cmd/deploy.ts b/src/cmd/deploy.ts index e21e978..c3f5c30 100644 --- a/src/cmd/deploy.ts +++ b/src/cmd/deploy.ts @@ -8,7 +8,7 @@ import Logger from '../utils/logger'; import { getSentinelClient, - getAutotaskClient, + getActionClient, getAdminClient, getRelayClient, constructSentinel, @@ -28,14 +28,14 @@ import { formatABI, } from '../utils'; import { - DefenderAutotask, + PlatformAction, DefenderContract, DefenderNotification, DefenderRelayer, DefenderSentinel, DefenderRelayerApiKey, TeamKey, - YAutotask, + YAction, YContract, YNotification, YRelayer, @@ -90,7 +90,7 @@ export default class DefenderDeploy { private async getSSOTDifference(): Promise { const difference: ListDefenderResources = { sentinels: [], - autotasks: [], + actions: [], notifications: [], categories: [], contracts: [], @@ -166,20 +166,20 @@ export default class DefenderDeploy { a.stackResourceId === getResourceID(getStackName(this.serverless), b[0]), ); - // Autotasks - const autotasks: YAutotask[] = this.serverless.service.functions as any; - const autotaskClient = getAutotaskClient(this.teamKey!); - const dAutotasks = (await autotaskClient.list()).items; - const autotaskDifference = _.differenceWith( - dAutotasks, - Object.entries(autotasks ?? []), - (a: DefenderAutotask, b: [string, YAutotask]) => + // Actions + const actions: YAction[] = this.serverless.service.functions as any; + const actionClient = getActionClient(this.teamKey!); + const dActions = (await actionClient.list()).items; + const actionDifference = _.differenceWith( + dActions, + Object.entries(actions ?? []), + (a: PlatformAction, b: [string, YAction]) => a.stackResourceId === getResourceID(getStackName(this.serverless), b[0]), ); // Secrets const allSecrets = getConsolidatedSecrets(this.serverless); - const dSecrets = (await autotaskClient.listSecrets()).secretNames ?? []; + const dSecrets = (await actionClient.listSecrets()).secretNames ?? []; const secretsDifference = _.differenceWith( dSecrets, Object.values(allSecrets).map((k, _) => Object.keys(k)[0] ?? ''), @@ -214,7 +214,7 @@ export default class DefenderDeploy { difference.sentinels = sentinelDifference; difference.notifications = notificationDifference; difference.categories = categoryDifference; - difference.autotasks = autotaskDifference; + difference.actions = actionDifference; difference.secrets = secretsDifference; difference.deploymentConfigs = deploymentConfigDifference; difference.blockExplorerApiKeys = blockExplorerApiKeyDifference; @@ -226,27 +226,27 @@ export default class DefenderDeploy { const end = `Are you sure you wish to continue [y/n]?`; const formattedResources = { - autotasks: - withResources.autotasks.length > 0 - ? withResources.autotasks.map(a => `${a.stackResourceId ?? a.name} (${a.autotaskId})`) + actions: + withResources.actions.length > 0 + ? withResources.actions.map((a) => `${a.stackResourceId ?? a.name} (${a.actionkId})`) : undefined, sentinels: withResources.sentinels.length > 0 - ? withResources.sentinels.map(a => `${a.stackResourceId ?? a.name} (${a.subscriberId})`) + ? withResources.sentinels.map((a) => `${a.stackResourceId ?? a.name} (${a.subscriberId})`) : undefined, notifications: withResources.notifications.length > 0 - ? withResources.notifications.map(a => `${a.stackResourceId ?? a.name} (${a.notificationId})`) + ? withResources.notifications.map((a) => `${a.stackResourceId ?? a.name} (${a.notificationId})`) : undefined, contracts: withResources.contracts.length > 0 - ? withResources.contracts.map(a => `${a.network}-${a.address} (${a.name})`) + ? withResources.contracts.map((a) => `${a.network}-${a.address} (${a.name})`) : undefined, relayerApiKeys: withResources.relayerApiKeys.length > 0 - ? withResources.relayerApiKeys.map(a => `${a.stackResourceId ?? a.apiKey} (${a.keyId})`) + ? withResources.relayerApiKeys.map((a) => `${a.stackResourceId ?? a.apiKey} (${a.keyId})`) : undefined, - secrets: withResources.secrets.length > 0 ? withResources.secrets.map(a => `${a}`) : undefined, + secrets: withResources.secrets.length > 0 ? withResources.secrets.map((a) => `${a}`) : undefined, }; return `${start}\n${ _.isEmpty(validateTypesAndSanitise(formattedResources)) @@ -287,8 +287,8 @@ export default class DefenderDeploy { private async deploySecrets(output: DeployOutput) { const allSecrets = getConsolidatedSecrets(this.serverless); - const client = getAutotaskClient(this.teamKey!); - const retrieveExisting = () => client.listSecrets().then(r => r.secretNames ?? []); + const client = getActionClient(this.teamKey!); + const retrieveExisting = () => client.listSecrets().then((r) => r.secretNames ?? []); await this.wrapper( this.serverless, 'Secrets', @@ -347,10 +347,10 @@ export default class DefenderDeploy { // on update async (contract: YContract, match: DefenderContract) => { const mappedMatch = { - name: match.name, - network: match.network, - address: match.address, - abi: match.abi && JSON.stringify(JSON.parse(match.abi)), + 'name': match.name, + 'network': match.network, + 'address': match.address, + 'abi': match.abi && JSON.stringify(JSON.parse(match.abi)), 'nat-spec': match.natSpec ? match.natSpec : undefined, }; @@ -402,7 +402,7 @@ export default class DefenderDeploy { }, // on remove async (contracts: DefenderContract[]) => { - await Promise.all(contracts.map(async c => await client.deleteContract(`${c.network}-${c.address}`))); + await Promise.all(contracts.map(async (c) => await client.deleteContract(`${c.network}-${c.address}`))); }, // overrideMatchDefinition (a: DefenderContract, b: YContract) => { @@ -420,7 +420,7 @@ export default class DefenderDeploy { ) { const relayers: YRelayer[] = this.serverless.service.resources?.Resources?.relayers ?? []; const client = getRelayClient(this.teamKey!); - const retrieveExisting = () => client.list().then(r => r.items); + const retrieveExisting = () => client.list().then((r) => r.items); await this.wrapper( this.serverless, 'Relayers', @@ -437,10 +437,10 @@ export default class DefenderDeploy { } const mappedMatch = { - name: match.name, - network: match.network, + 'name': match.name, + 'network': match.network, 'min-balance': parseInt(match.minBalance.toString()), - policy: { + 'policy': { 'gas-price-cap': match.policies.gasPriceCap, 'whitelist-receivers': match.policies.whitelistReceivers, 'eip1559-pricing': match.policies.EIP1559Pricing, @@ -482,7 +482,7 @@ export default class DefenderDeploy { this.log.info(`Unused resources found on Defender:`); this.log.info(JSON.stringify(inDefender, null, 2)); this.log.progress('component-deploy-extra', `Removing resources from Defender`); - await Promise.all(inDefender.map(async key => await client.deleteKey(match.relayerId, key.keyId))); + await Promise.all(inDefender.map(async (key) => await client.deleteKey(match.relayerId, key.keyId))); this.log.success(`Removed resources from Defender`); output.relayerKeys.removed.push(...inDefender); } @@ -496,7 +496,7 @@ export default class DefenderDeploy { // create key in Defender thats defined in template if (inTemplate) { await Promise.all( - inTemplate.map(async key => { + inTemplate.map(async (key) => { const keyStackResource = getResourceID(match.stackResourceId!, key); const createdKey = await client.createKey(match.relayerId, keyStackResource); this.log.success(`Created API Key (${keyStackResource}) for Relayer (${match.relayerId})`); @@ -544,7 +544,7 @@ export default class DefenderDeploy { const relayerKeys = relayer['api-keys']; if (relayerKeys) { await Promise.all( - relayerKeys.map(async key => { + relayerKeys.map(async (key) => { const keyStackResource = getResourceID(stackResourceId, key); const createdKey = await client.createKey(createdRelayer.relayerId, keyStackResource); this.log.success(`Created API Key (${keyStackResource}) for Relayer (${createdRelayer.relayerId})`); @@ -623,7 +623,7 @@ export default class DefenderDeploy { }, // on remove async (notifications: DefenderNotification[]) => { - await Promise.all(notifications.map(async n => await client.deleteNotificationChannel(n))); + await Promise.all(notifications.map(async (n) => await client.deleteNotificationChannel(n))); }, undefined, output, @@ -714,10 +714,10 @@ export default class DefenderDeploy { try { const sentinels: YSentinel[] = this.serverless.service.resources?.Resources?.sentinels ?? []; const client = getSentinelClient(this.teamKey!); - const autotasks = await getAutotaskClient(this.teamKey!).list(); + const actions = await getActionClient(this.teamKey!).list(); const notifications = await client.listNotificationChannels(); const categories = await client.listNotificationCategories(); - const retrieveExisting = () => client.list().then(r => r.items); + const retrieveExisting = () => client.list().then((r) => r.items); await this.wrapper( this.serverless, @@ -746,7 +746,7 @@ export default class DefenderDeploy { } const blockwatchersForNetwork = (await client.listBlockwatchers()).filter( - b => b.network === sentinel.network, + (b) => b.network === sentinel.network, ); const newSentinel = constructSentinel( @@ -754,7 +754,7 @@ export default class DefenderDeploy { match.stackResourceId!, sentinel, notifications, - autotasks.items, + actions.items, blockwatchersForNetwork, categories, ); @@ -773,7 +773,7 @@ export default class DefenderDeploy { abi: addressRule && addressRule.abi, paused: match.paused, alertThreshold: match.alertThreshold, - autotaskTrigger: match.notifyConfig?.autotaskId, + actionTrigger: match.notifyConfig?.autotaskId, alertTimeoutMs: match.notifyConfig?.timeoutMs, alertMessageBody: match.notifyConfig?.messageBody, alertMessageSubject: match.notifyConfig?.messageSubject, @@ -795,7 +795,7 @@ export default class DefenderDeploy { blockConditions[0]!.txConditions[0]!.expression, privateFortaNodeId: (isForta(match) && match.privateFortaNodeId) || undefined, addresses: isBlock(match) ? addressRule && addressRule.addresses : match.fortaRule?.addresses, - autotaskCondition: isBlock(match) + actionCondition: isBlock(match) ? addressRule && addressRule.autotaskCondition?.autotaskId : match.fortaRule?.autotaskCondition?.autotaskId, fortaLastProcessedTime: (isForta(match) && match.fortaLastProcessedTime) || undefined, @@ -830,7 +830,7 @@ export default class DefenderDeploy { // on create async (sentinel: YSentinel, stackResourceId: string) => { const blockwatchersForNetwork = (await client.listBlockwatchers()).filter( - b => b.network === sentinel.network, + (b) => b.network === sentinel.network, ); const createdSentinel = await client.create( constructSentinel( @@ -838,7 +838,7 @@ export default class DefenderDeploy { stackResourceId, sentinel, notifications, - autotasks.items, + actions.items, blockwatchersForNetwork, categories, ), @@ -852,7 +852,7 @@ export default class DefenderDeploy { }, // on remove async (sentinels: DefenderSentinel[]) => { - await Promise.all(sentinels.map(async s => await client.delete(s.subscriberId))); + await Promise.all(sentinels.map(async (s) => await client.delete(s.subscriberId))); }, undefined, output, @@ -863,30 +863,36 @@ export default class DefenderDeploy { } } - private async deployAutotasks(output: DeployOutput) { - const autotasks: YAutotask[] = this.serverless.service.functions as any; - const client = getAutotaskClient(this.teamKey!); - const retrieveExisting = () => client.list().then(r => r.items); + private async deployActions(output: DeployOutput) { + const actions: YAction[] = this.serverless.service.functions as any; + const client = getActionClient(this.teamKey!); + const retrieveExisting = () => client.list().then((r) => r.items); - await this.wrapper( + await this.wrapper( this.serverless, 'Autotasks', - autotasks, + actions, retrieveExisting, // on update - async (autotask: YAutotask, match: DefenderAutotask) => { + async (action: YAction, match: PlatformAction) => { const relayers: YRelayer[] = this.serverless.service.resources?.Resources?.relayers ?? []; const existingRelayers = (await getRelayClient(this.teamKey!).list()).items; const maybeRelayer = getEquivalentResource( this.serverless, - autotask.relayer, + action.relayer, relayers, existingRelayers, ); // Get new code digest - const code = await client.getEncodedZippedCodeFromFolder(autotask.path); - const newDigest = client.getCodeDigest(code); - const { codeDigest } = await client.get(match.autotaskId); + const code = await client.getEncodedZippedCodeFromFolder({ + path: action.path, + }); + const newDigest = client.getCodeDigest({ + encodedZippedCode: code, + }); + const { codeDigest } = await client.get({ + actionId: match.actionkId, + }); const isSchedule = ( o: DefenderWebhookTrigger | DefenderScheduleTrigger | DefenderSentinelTrigger | DefenderMonitorFilterTrigger, @@ -907,7 +913,7 @@ export default class DefenderDeploy { if ( _.isEqual( validateTypesAndSanitise({ - ..._.omit(autotask, ['events', 'package', 'relayer', 'path']), + ..._.omit(action, ['events', 'package', 'relayer', 'path']), relayerId: maybeRelayer?.relayerId, codeDigest: newDigest, }), @@ -916,21 +922,21 @@ export default class DefenderDeploy { ) { return { name: match.stackResourceId!, - id: match.autotaskId, + id: match.actionkId, success: false, response: match, notice: `Skipped ${match.stackResourceId} - no changes detected`, }; } - const updatesAutotask = await client.update({ - autotaskId: match.autotaskId, - name: autotask.name, - paused: autotask.paused, + const updatesAction = await client.update({ + actionId: match.actionkId, + name: action.name, + paused: action.paused, trigger: { - type: autotask.trigger.type, - frequencyMinutes: autotask.trigger.frequency ?? undefined, - cron: autotask.trigger.cron ?? undefined, + type: action.trigger.type, + frequencyMinutes: action.trigger.frequency ?? undefined, + cron: action.trigger.cron ?? undefined, }, relayerId: maybeRelayer?.relayerId, }); @@ -938,59 +944,64 @@ export default class DefenderDeploy { if (newDigest === codeDigest) { return { name: match.stackResourceId!, - id: match.autotaskId, + id: match.actionkId, success: true, notice: `Skipped code upload - no changes detected for ${match.stackResourceId}`, - response: updatesAutotask, + response: updatesAction, }; } else { - await client.updateCodeFromFolder(match.autotaskId, autotask.path); + await client.updateCodeFromFolder({ + actionId: match.actionkId, + path: action.path, + }); return { name: match.stackResourceId!, - id: match.autotaskId, + id: match.actionkId, success: true, - response: updatesAutotask, + response: updatesAction, }; } }, // on create - async (autotask: YAutotask, stackResourceId: string) => { - const autotaskRelayer = autotask.relayer; + async (action: YAction, stackResourceId: string) => { + const actionRelayer = action.relayer; const relayers: YRelayer[] = this.serverless.service.resources?.Resources?.relayers ?? []; const existingRelayers = (await getRelayClient(this.teamKey!).list()).items; const maybeRelayer = getEquivalentResource( this.serverless, - autotaskRelayer, + actionRelayer, relayers, existingRelayers, ); - const createdAutotask = await client.create({ - name: autotask.name, + const createdAction = await client.create({ + name: action.name, trigger: { - type: autotask.trigger.type, - frequencyMinutes: autotask.trigger.frequency ?? undefined, - cron: autotask.trigger.cron ?? undefined, + type: action.trigger.type, + frequencyMinutes: action.trigger.frequency ?? undefined, + cron: action.trigger.cron ?? undefined, }, - encodedZippedCode: await client.getEncodedZippedCodeFromFolder(autotask.path), - paused: autotask.paused, + encodedZippedCode: await client.getEncodedZippedCodeFromFolder({ + path: action.path, + }), + paused: action.paused, relayerId: maybeRelayer?.relayerId, stackResourceId: stackResourceId, }); return { name: stackResourceId, - id: createdAutotask.autotaskId, + id: createdAction.actionkId, success: true, - response: createdAutotask, + response: createdAction, }; }, // on remove - async (autotasks: DefenderAutotask[]) => { - await Promise.all(autotasks.map(async a => await client.delete(a.autotaskId))); + async (actions: PlatformAction[]) => { + await Promise.all(actions.map(async (a) => await client.delete({ actionId: a.actionkId }))); }, undefined, output, - this.ssotDifference?.autotasks, + this.ssotDifference?.actions, ); } @@ -1057,7 +1068,10 @@ export default class DefenderDeploy { if (!maybeRelayer) throw new Error(`Cannot find relayer ${deploymentConfigRelayer} in ${stackResourceId}`); - const importedDeployment = await client.create({ relayerId: maybeRelayer.relayerId, stackResourceId }); + const importedDeployment = await client.create({ + relayerId: maybeRelayer.relayerId, + stackResourceId, + }); return { name: stackResourceId, @@ -1068,7 +1082,7 @@ export default class DefenderDeploy { }, // on remove async (deploymentConfigs: DefenderDeploymentConfig[]) => { - await Promise.all(deploymentConfigs.map(async c => await client.remove(c.deploymentConfigId))); + await Promise.all(deploymentConfigs.map(async (c) => await client.remove(c.deploymentConfigId))); }, undefined, output, @@ -1112,7 +1126,10 @@ export default class DefenderDeploy { }, // on create async (blockExplorerApiKey: YBlockExplorerApiKey, stackResourceId: string) => { - const importedBlockExplorerApiKey = await client.create({ ...blockExplorerApiKey, stackResourceId }); + const importedBlockExplorerApiKey = await client.create({ + ...blockExplorerApiKey, + stackResourceId, + }); return { name: stackResourceId, id: importedBlockExplorerApiKey.blockExplorerApiKeyId, @@ -1122,7 +1139,7 @@ export default class DefenderDeploy { }, // on remove async (blockExplorerApiKeys: DefenderBlockExplorerApiKey[]) => { - await Promise.all(blockExplorerApiKeys.map(async c => await client.remove(c.blockExplorerApiKeyId))); + await Promise.all(blockExplorerApiKeys.map(async (c) => await client.remove(c.blockExplorerApiKeyId))); }, undefined, output, @@ -1230,7 +1247,7 @@ export default class DefenderDeploy { created: [], updated: [], }; - const autotasks: DeployOutput = { + const actions: DeployOutput = { removed: [], created: [], updated: [], @@ -1284,7 +1301,7 @@ export default class DefenderDeploy { stack: stackName, timestamp: new Date().toISOString(), sentinels, - autotasks, + actions: actions, contracts, relayers, notifications, @@ -1295,9 +1312,9 @@ export default class DefenderDeploy { }; await this.deploySecrets(stdOut.secrets); await this.deployContracts(stdOut.contracts); - // Always deploy relayers before autotasks + // Always deploy relayers before actions await this.deployRelayers(stdOut.relayers); - await this.deployAutotasks(stdOut.autotasks); + await this.deployActions(stdOut.actions); // Deploy notifications before sentinels and categories await this.deployNotifications(stdOut.notifications); await this.deployCategories(stdOut.categories); diff --git a/src/cmd/info.ts b/src/cmd/info.ts index a808ab2..912e2d6 100644 --- a/src/cmd/info.ts +++ b/src/cmd/info.ts @@ -7,7 +7,7 @@ import Logger from '../utils/logger'; import { getAdminClient, - getAutotaskClient, + getActionClient, getConsolidatedSecrets, getRelayClient, getSentinelClient, @@ -16,7 +16,7 @@ import { isTemplateResource, } from '../utils'; import { - DefenderAutotask, + PlatformAction, DefenderCategory, DefenderContract, DefenderNotification, @@ -25,7 +25,7 @@ import { DefenderSentinel, ResourceType, TeamKey, - YAutotask, + YAction, YCategory, YContract, YNotification, @@ -70,12 +70,12 @@ export default class DefenderInfo { try { this.log.progress('component-info', `Retrieving ${resourceType}`); this.log.notice(`${resourceType}`); - const existing = (await retrieveExistingResources()).filter(e => + const existing = (await retrieveExistingResources()).filter((e) => isTemplateResource(context, e, resourceType, resources ?? []), ); await Promise.all( - existing.map(async e => { + existing.map(async (e) => { this.log.notice(`${format(e)}`, 1); let keys: DefenderRelayerApiKey[] = []; // Also print relayer API keys @@ -83,7 +83,7 @@ export default class DefenderInfo { const listRelayerAPIKeys = await getRelayClient(getTeamAPIkeysOrThrow(context)).listKeys( (e as unknown as DefenderRelayer).relayerId, ); - listRelayerAPIKeys.map(k => { + listRelayerAPIKeys.map((k) => { this.log.notice(`${k.stackResourceId}: ${k.keyId}`, 2); }); keys = listRelayerAPIKeys; @@ -104,7 +104,7 @@ export default class DefenderInfo { const stdOut = { stack: stackName, sentinels: [], - autotasks: [], + actions: [], contracts: [], relayers: [], notifications: [], @@ -115,7 +115,7 @@ export default class DefenderInfo { const listSentinels = () => getSentinelClient(this.teamKey!) .list() - .then(i => i.items); + .then((i) => i.items); await this.wrapper( this.serverless, @@ -126,18 +126,18 @@ export default class DefenderInfo { stdOut.sentinels, ); - // Autotasks - const listAutotasks = () => - getAutotaskClient(this.teamKey!) + // Actions + const listActions = () => + getActionClient(this.teamKey!) .list() - .then(r => r.items); - await this.wrapper( + .then((r) => r.items); + await this.wrapper( this.serverless, 'Autotasks', - this.serverless.service.functions as unknown as YAutotask[], - listAutotasks, - (resource: DefenderAutotask) => `${resource.stackResourceId}: ${resource.autotaskId}`, - stdOut.autotasks, + this.serverless.service.functions as unknown as YAction[], + listActions, + (resource: PlatformAction) => `${resource.stackResourceId}: ${resource.actionkId}`, + stdOut.actions, ); // Contracts @@ -155,7 +155,7 @@ export default class DefenderInfo { const listRelayers = () => getRelayClient(this.teamKey!) .list() - .then(r => r.items); + .then((r) => r.items); await this.wrapper( this.serverless, 'Relayers', @@ -189,9 +189,9 @@ export default class DefenderInfo { // Secrets const listSecrets = () => - getAutotaskClient(this.teamKey!) + getActionClient(this.teamKey!) .listSecrets() - .then(r => r.secretNames ?? []); + .then((r) => r.secretNames ?? []); const allSecrets = getConsolidatedSecrets(this.serverless); diff --git a/src/cmd/invoke.ts b/src/cmd/invoke.ts index 78f1fb0..c768068 100644 --- a/src/cmd/invoke.ts +++ b/src/cmd/invoke.ts @@ -4,8 +4,8 @@ import { Logging } from 'serverless/classes/Plugin'; import Logger from '../utils/logger'; -import { getAutotaskClient, getEquivalentResourceByKey, getTeamAPIkeysOrThrow } from '../utils'; -import { DefenderAutotask, TeamKey } from '../types'; +import { getActionClient, getEquivalentResourceByKey, getTeamAPIkeysOrThrow } from '../utils'; +import { PlatformAction, TeamKey } from '../types'; export default class DefenderInvoke { serverless: Serverless; @@ -37,15 +37,18 @@ export default class DefenderInvoke { this.log.notice('========================================================'); this.log.progress('logs', `Running Defender Invoke on stack function: ${this.options.function}`); const payload = JSON.parse((this.options as any)?.data ?? '{}'); - const client = getAutotaskClient(this.teamKey!); + const client = getActionClient(this.teamKey!); const list = (await client.list()).items; - const defenderAutotask = getEquivalentResourceByKey(this.options.function!, list); - if (defenderAutotask) { - const response = await client.runAutotask(defenderAutotask.autotaskId, payload); + const platformAction = getEquivalentResourceByKey(this.options.function!, list); + if (platformAction) { + const response = await client.runAction({ + actionId: platformAction.actionkId, + data: payload, + }); this.log.notice(JSON.stringify(response, null, 2)); if (!process.stdout.isTTY) this.log.stdOut(JSON.stringify(response, null, 2)); } else { - this.log.error(`No autotask with identifier: ${this.options.function} found.`); + this.log.error(`No actions with identifier: ${this.options.function} found.`); } this.log.notice('========================================================'); } catch (e) { diff --git a/src/cmd/logs.ts b/src/cmd/logs.ts index 1994089..fd9866e 100644 --- a/src/cmd/logs.ts +++ b/src/cmd/logs.ts @@ -4,10 +4,10 @@ import { Logging } from 'serverless/classes/Plugin'; import Logger from '../utils/logger'; -import { tailLogsFor } from '@openzeppelin/defender-autotask-client/lib/utils'; +// import { tailLogsFor } from '@openzeppelin/defender-autotask-client/lib/utils'; -import { getAutotaskClient, getEquivalentResourceByKey, getTeamAPIkeysOrThrow } from '../utils'; -import { DefenderAutotask, TeamKey } from '../types'; +import { getActionClient, getEquivalentResourceByKey, getTeamAPIkeysOrThrow } from '../utils'; +import { PlatformAction, TeamKey } from '../types'; export default class DefenderLogs { serverless: Serverless; @@ -38,13 +38,14 @@ export default class DefenderLogs { try { this.log.notice('========================================================'); this.log.progress('logs', `Running Defender Logs on stack function: ${this.options.function}`); - const client = getAutotaskClient(this.teamKey!); + const client = getActionClient(this.teamKey!); const list = (await client.list()).items; - const defenderAutotask = getEquivalentResourceByKey(this.options.function!, list); + const defenderAutotask = getEquivalentResourceByKey(this.options.function!, list); - if (defenderAutotask) await tailLogsFor(client, defenderAutotask!.autotaskId); - else this.log.error(`No autotask with stackResourceId: ${this.options.function} found.`); + // TODO: Update with Platform equivalent once I find it + // if (defenderAutotask) await tailLogsFor(client, defenderAutotask!.autotaskId); + // else this.log.error(`No autotask with stackResourceId: ${this.options.function} found.`); this.log.notice('========================================================'); } catch (e) { this.log.tryLogDefenderError(e); diff --git a/src/cmd/remove.ts b/src/cmd/remove.ts index c5607e8..95bb7df 100644 --- a/src/cmd/remove.ts +++ b/src/cmd/remove.ts @@ -8,7 +8,7 @@ import Logger from '../utils/logger'; import { getAdminClient, - getAutotaskClient, + getActionClient, getConsolidatedSecrets, getRelayClient, getSentinelClient, @@ -17,7 +17,7 @@ import { isTemplateResource, } from '../utils'; import { - DefenderAutotask, + PlatformAction, DefenderCategory, DefenderContract, DefenderNotification, @@ -26,7 +26,7 @@ import { DefenderSentinel, ResourceType, TeamKey, - YAutotask, + YAction, YCategory, YContract, YNotification, @@ -70,7 +70,7 @@ export default class DefenderRemove { ) { try { this.log.progress('component-info', `Retrieving ${resourceType}`); - const existing = (await retrieveExistingResources()).filter(e => + const existing = (await retrieveExistingResources()).filter((e) => isTemplateResource(context, e, resourceType, resources ?? []), ); this.log.progress('component-remove', `Removing ${resourceType} from Defender`); @@ -113,16 +113,19 @@ export default class DefenderRemove { const stdOut: { stack: string; sentinels: DefenderSentinel[]; - autotasks: DefenderAutotask[]; + actions: PlatformAction[]; contracts: DefenderContract[]; - relayers: { relayerId: string; relayerApiKeys: DefenderRelayerApiKey[] }[]; + relayers: { + relayerId: string; + relayerApiKeys: DefenderRelayerApiKey[]; + }[]; notifications: DefenderNotification[]; categories: DefenderCategory[]; secrets: string[]; } = { stack: stackName, sentinels: [], - autotasks: [], + actions: [], contracts: [], relayers: [], notifications: [], @@ -131,7 +134,7 @@ export default class DefenderRemove { }; // Sentinels const sentinelClient = getSentinelClient(this.teamKey!); - const listSentinels = () => sentinelClient.list().then(i => i.items); + const listSentinels = () => sentinelClient.list().then((i) => i.items); await this.wrapper( this.serverless, 'Sentinels', @@ -139,7 +142,7 @@ export default class DefenderRemove { listSentinels, async (sentinels: DefenderSentinel[]) => { await Promise.all( - sentinels.map(async e => { + sentinels.map(async (e) => { this.log.progress( 'component-remove-extra', `Removing ${e.stackResourceId} (${e.subscriberId}) from Defender`, @@ -152,27 +155,24 @@ export default class DefenderRemove { stdOut.sentinels, ); - // Autotasks - const autotaskClient = getAutotaskClient(this.teamKey!); - const listAutotasks = () => autotaskClient.list().then(i => i.items); - await this.wrapper( + // Actions + const actionClient = getActionClient(this.teamKey!); + const listActions = () => actionClient.list().then((i) => i.items); + await this.wrapper( this.serverless, 'Autotasks', this.serverless.service.functions as any, - listAutotasks, - async (autotasks: DefenderAutotask[]) => { + listActions, + async (actions: PlatformAction[]) => { await Promise.all( - autotasks.map(async e => { - this.log.progress( - 'component-remove-extra', - `Removing ${e.stackResourceId} (${e.autotaskId}) from Defender`, - ); - await autotaskClient.delete(e.autotaskId); - this.log.success(`Removed ${e.stackResourceId} (${e.autotaskId})`); + actions.map(async (e) => { + this.log.progress('component-remove-extra', `Removing ${e.stackResourceId} (${e.actionkId}) from Defender`); + await actionClient.delete({ actionId: e.actionkId }); + this.log.success(`Removed ${e.stackResourceId} (${e.actionkId})`); }), ); }, - stdOut.autotasks, + stdOut.actions, ); // Contracts @@ -185,7 +185,7 @@ export default class DefenderRemove { listContracts, async (contracts: DefenderContract[]) => { await Promise.all( - contracts.map(async e => { + contracts.map(async (e) => { const id = `${e.network}-${e.address}`; this.log.progress('component-remove-extra', `Removing ${id} (${e.name}) from Defender`); await adminClient.deleteContract(id); @@ -200,7 +200,7 @@ export default class DefenderRemove { // Relayer API keys const relayClient = getRelayClient(this.teamKey!); const listRelayers = (await relayClient.list()).items; - const existingRelayers = listRelayers.filter(e => + const existingRelayers = listRelayers.filter((e) => isTemplateResource( this.serverless, e, @@ -211,17 +211,20 @@ export default class DefenderRemove { this.log.error('Deleting Relayers is currently only possible via the Defender UI.'); this.log.progress('component-info', `Retrieving Relayer API Keys`); await Promise.all( - existingRelayers.map(async relayer => { + existingRelayers.map(async (relayer) => { this.log.progress('component-info', `Retrieving API Keys for relayer ${relayer.stackResourceId}`); const relayerApiKeys = await relayClient.listKeys(relayer.relayerId); await Promise.all( - relayerApiKeys.map(async e => { + relayerApiKeys.map(async (e) => { this.log.progress('component-remove-extra', `Removing ${e.stackResourceId} (${e.keyId}) from Defender`); await relayClient.deleteKey(e.relayerId, e.keyId); this.log.success(`Removed ${e.stackResourceId} (${e.keyId})`); }), ); - stdOut.relayers.push({ relayerId: relayer.relayerId, relayerApiKeys }); + stdOut.relayers.push({ + relayerId: relayer.relayerId, + relayerApiKeys, + }); }), ); } catch (e) { @@ -237,7 +240,7 @@ export default class DefenderRemove { listNotifications, async (notifications: DefenderNotification[]) => { await Promise.all( - notifications.map(async e => { + notifications.map(async (e) => { this.log.progress( 'component-remove-extra', `Removing ${e.stackResourceId} (${e.notificationId}) from Defender`, @@ -275,7 +278,7 @@ export default class DefenderRemove { // ); // Secrets - const listSecrets = () => autotaskClient.listSecrets().then(r => r.secretNames ?? []); + const listSecrets = () => actionClient.listSecrets().then((r) => r.secretNames ?? []); const allSecrets = getConsolidatedSecrets(this.serverless); @@ -286,7 +289,7 @@ export default class DefenderRemove { listSecrets, async (secrets: string[]) => { this.log.progress('component-remove-extra', `Removing (${secrets.join(', ')}) from Defender`); - await autotaskClient.createSecrets({ + await actionClient.createSecrets({ deletes: secrets, secrets: {}, }); diff --git a/src/types/index.ts b/src/types/index.ts index a623ce5..26a2cc8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -26,13 +26,13 @@ import { SubscriberRiskCategory, } from '@openzeppelin/defender-sentinel-client/lib/models/subscriber'; import { - Autotask, + Action, SecretsMap, ScheduleTrigger, WebhookTrigger, SentinelTrigger, MonitorFilterTrigger, -} from '@openzeppelin/defender-autotask-client/lib/models/autotask'; +} from '@openzeppelin/platform-sdk-action-client/lib/models/action'; import { BlockExplorerApiKeyResponse, DeploymentConfigResponse } from '@openzeppelin/platform-deploy-client'; import { OpsgenieConfig } from '@openzeppelin/defender-sentinel-client/lib/models/opsgenie'; import { PagerDutyConfig } from '@openzeppelin/defender-sentinel-client/lib/models/pager-duty'; @@ -42,7 +42,7 @@ export type DefenderRelayerApiKey = RelayerApiKey; export type DefenderSecretsMap = SecretsMap; export type DefenderContract = Contract; export type DefenderRelayer = RelayerGetResponse; -export type DefenderAutotask = Autotask; +export type PlatformAction = Action; export type DefenderBlockWatcher = BlockWatcher; export type DefenderNotification = NotificationSummary; export type DefenderCategory = NotificationCategory; @@ -85,15 +85,15 @@ export type YPolicy = { }; export type YRelayer = { - name: string; - network: Network; + 'name': string; + 'network': Network; 'min-balance': number; - policy?: YPolicy; + 'policy'?: YPolicy; 'api-keys': any[]; 'address-from-relayer'?: YRelayer; }; -export type YAutotask = { +export type YAction = { name: string; path: string; relayer?: YRelayer; @@ -145,30 +145,30 @@ export type YNotification = SaveNotificationRequest & { }; export type YCategory = { - name: string; - description: string; + 'name': string; + 'description': string; 'notification-ids': YNotification[]; }; export type YBlockSentinel = { - name: string; - type: 'BLOCK'; - network: Network; - addresses: string[]; - abi?: string | string[] | JsonFragment[]; - 'alert-threshold'?: { amount: number; 'window-seconds': number }; - paused?: boolean; - 'autotask-condition'?: YAutotask; - 'autotask-trigger'?: YAutotask; + 'name': string; + 'type': 'BLOCK'; + 'network': Network; + 'addresses': string[]; + 'abi'?: string | string[] | JsonFragment[]; + 'alert-threshold'?: { 'amount': number; 'window-seconds': number }; + 'paused'?: boolean; + 'autotask-condition'?: YAction; + 'autotask-trigger'?: YAction; 'confirm-level'?: number | 'safe' | 'finalized'; 'notify-config': { - timeout?: number; - message?: string; + 'timeout'?: number; + 'message'?: string; 'message-subject'?: string; - category?: YCategory; - channels: YNotification[]; + 'category'?: YCategory; + 'channels': YNotification[]; }; - conditions?: { + 'conditions'?: { event: { signature: string; expression?: string }[]; function: { signature: string; expression?: string }[]; transaction?: string; @@ -177,25 +177,25 @@ export type YBlockSentinel = { }; export type YFortaSentinel = { - name: string; - type: 'FORTA'; - network?: Network; - addresses?: string[]; - abi?: string | string[] | JsonFragment[]; - 'alert-threshold'?: { amount: number; 'window-seconds': number }; - paused?: boolean; - 'autotask-condition'?: YAutotask; - 'autotask-trigger'?: YAutotask; + 'name': string; + 'type': 'FORTA'; + 'network'?: Network; + 'addresses'?: string[]; + 'abi'?: string | string[] | JsonFragment[]; + 'alert-threshold'?: { 'amount': number; 'window-seconds': number }; + 'paused'?: boolean; + 'autotask-condition'?: YAction; + 'autotask-trigger'?: YAction; 'notify-config': { - timeout?: number; - message?: string; + 'timeout'?: number; + 'message'?: string; 'message-subject'?: string; - category?: YCategory; - channels: YNotification[]; + 'category'?: YCategory; + 'channels': YNotification[]; }; - conditions?: { + 'conditions'?: { 'min-scanner-count': number; - severity?: 0 | 1 | 2 | 3 | 4 | 5; + 'severity'?: 0 | 1 | 2 | 3 | 4 | 5; 'alert-ids'?: string[]; }; 'forta-node-id'?: string; @@ -207,10 +207,10 @@ export type YFortaSentinel = { export type YSentinel = YBlockSentinel | YFortaSentinel; export type YContract = { - name: string; - address: string; - network: Network; - abi?: string | string[] | JsonFragment[]; + 'name': string; + 'address': string; + 'network': Network; + 'abi'?: string | string[] | JsonFragment[]; 'nat-spec'?: string; }; @@ -236,7 +236,7 @@ export type DeployOutput = { removed: T[]; created: T[]; updated: T[] }; export type ListDefenderResources = { sentinels: DefenderSentinel[]; - autotasks: DefenderAutotask[]; + actions: PlatformAction[]; notifications: DefenderNotification[]; categories: DefenderCategory[]; contracts: DefenderContract[]; diff --git a/src/utils/index.ts b/src/utils/index.ts index d9d6b61..7831a2a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,8 @@ import Serverless from 'serverless'; import _ from 'lodash'; -import { AutotaskClient } from '@openzeppelin/defender-autotask-client'; +import { Platform } from '@openzeppelin/platform-sdk'; +import { ActionClient } from '@openzeppelin/platform-sdk-action-client'; import { SentinelClient } from '@openzeppelin/defender-sentinel-client'; import { RelayClient } from '@openzeppelin/defender-relay-client'; import { AdminClient } from '@openzeppelin/defender-admin-client'; @@ -17,7 +18,7 @@ import { YDatadogConfig, YOpsgenieConfig, YPagerdutyConfig, - DefenderAutotask, + PlatformAction, DefenderNotification, DefenderBlockSentinel, DefenderFortaSentinel, @@ -29,7 +30,7 @@ import { YCategory, DefenderCategory, DefenderAPIError, - YAutotask, + YAction, DefenderNotificationReference, } from '../types'; import { sanitise } from './sanitise'; @@ -46,7 +47,7 @@ export const getEquivalentResource = ( currentResources: D[], ) => { if (resource) { - const [key, value] = Object.entries(resources ?? []).find(a => _.isEqual(a[1], resource))!; + const [key, value] = Object.entries(resources ?? []).find((a) => _.isEqual(a[1], resource))!; return currentResources.find((e: D) => (e as any).stackResourceId === getResourceID(getStackName(context), key)); } }; @@ -81,7 +82,7 @@ export const isTemplateResource = ( resourceType: ResourceType, resources: Y[], ): boolean => { - return !!Object.entries(resources).find(a => + return !!Object.entries(resources).find((a) => resourceType === 'Secrets' ? // if secret, just compare key Object.keys(a[1] as unknown as YSecret)[0] === (resource as unknown as string) @@ -129,8 +130,9 @@ export const getSentinelClient = (key: TeamKey): SentinelClient => { return new SentinelClient(key); }; -export const getAutotaskClient = (key: TeamKey): AutotaskClient => { - return new AutotaskClient(key); +export const getActionClient = (key: TeamKey): ActionClient => { + const platformClient = new Platform(key); + return platformClient.action; }; export const getRelayClient = (key: TeamKey): RelayClient => { @@ -217,7 +219,7 @@ export const constructNotificationCategory = ( description: category.description, notificationIds: (category['notification-ids'] ? category['notification-ids'] - .map(notification => { + .map((notification) => { const maybeNotification = getEquivalentResource( context, notification, @@ -244,17 +246,17 @@ export const constructSentinel = ( stackResourceId: string, sentinel: YSentinel, notifications: DefenderNotification[], - autotasks: DefenderAutotask[], + actions: PlatformAction[], blockwatchers: DefenderBlockWatcher[], categories: DefenderCategory[], ): DefenderBlockSentinel | DefenderFortaSentinel => { - const autotaskCondition = - sentinel['autotask-condition'] && autotasks.find(a => a.name === sentinel['autotask-condition']!.name); - const autotaskTrigger = - sentinel['autotask-trigger'] && autotasks.find(a => a.name === sentinel['autotask-trigger']!.name); + const actionCondition = + sentinel['autotask-condition'] && actions.find((a) => a.name === sentinel['autotask-condition']!.name); + const actionTrigger = + sentinel['autotask-trigger'] && actions.find((a) => a.name === sentinel['autotask-trigger']!.name); const notificationChannels = sentinel['notify-config'].channels - .map(notification => { + .map((notification) => { const maybeNotification = getEquivalentResource( context, notification, @@ -266,7 +268,8 @@ export const constructSentinel = ( .filter(isResource); const sentinelCategory = sentinel['notify-config'].category; - const notificationCategoryId = sentinelCategory && categories.find(c => c.name === sentinelCategory.name)?.categoryId; + const notificationCategoryId = + sentinelCategory && categories.find((c) => c.name === sentinelCategory.name)?.categoryId; const commonSentinel = { type: sentinel.type, @@ -275,8 +278,8 @@ export const constructSentinel = ( addresses: sentinel.addresses, abi: sentinel.abi && JSON.stringify(typeof sentinel.abi === 'string' ? JSON.parse(sentinel.abi) : sentinel.abi), paused: sentinel.paused, - autotaskCondition: autotaskCondition && autotaskCondition.autotaskId, - autotaskTrigger: autotaskTrigger && autotaskTrigger.autotaskId, + autotaskCondition: actionCondition && actionCondition.actionkId, + autotaskTrigger: actionTrigger && actionTrigger.actionkId, alertThreshold: sentinel['alert-threshold'] && { amount: sentinel['alert-threshold'].amount, windowSeconds: sentinel['alert-threshold']['window-seconds'], @@ -307,7 +310,7 @@ export const constructSentinel = ( } if (sentinel.type === 'BLOCK') { - const compatibleBlockWatcher = blockwatchers.find(b => b.confirmLevel === sentinel['confirm-level']); + const compatibleBlockWatcher = blockwatchers.find((b) => b.confirmLevel === sentinel['confirm-level']); if (!compatibleBlockWatcher) { throw new Error( `A blockwatcher with confirmation level (${sentinel['confirm-level']}) does not exist on ${sentinel.network}. Choose another confirmation level.`, @@ -322,7 +325,7 @@ export const constructSentinel = ( eventConditions: sentinel.conditions && sentinel.conditions.event && - sentinel.conditions.event.map(c => { + sentinel.conditions.event.map((c) => { return { eventSignature: c.signature, expression: c.expression, @@ -331,7 +334,7 @@ export const constructSentinel = ( functionConditions: sentinel.conditions && sentinel.conditions.function && - sentinel.conditions.function.map(c => { + sentinel.conditions.function.map((c) => { return { functionSignature: c.signature, expression: c.expression, @@ -354,22 +357,22 @@ export const validateAdditionalPermissionsOrThrow = async ( const teamApiKey = getTeamAPIkeysOrThrow(context); switch (resourceType) { case 'Sentinels': - // Check for access to Autotasks - // Enumerate all sentinels, and check if any sentinel has an autotask associated - const sentinelsWithAutotasks = (Object.values(resources) as unknown as YSentinel[]).filter( - r => !!r['autotask-condition'] || !!r['autotask-trigger'], + // Check for access to Actions + // Enumerate all sentinels, and check if any sentinel has an action associated + const sentinelsWithActions = (Object.values(resources) as unknown as YSentinel[]).filter( + (r) => !!r['autotask-condition'] || !!r['autotask-trigger'], ); - // If there are sentinels with autotasks associated, then try to list autotasks - if (!_.isEmpty(sentinelsWithAutotasks)) { + // If there are sentinels with actions associated, then try to list actions + if (!_.isEmpty(sentinelsWithActions)) { try { - await getAutotaskClient(teamApiKey).list(); + await getActionClient(teamApiKey).list(); return; } catch (e) { // catch the error and verify it is an unauthorised access error if (isUnauthorisedError(e)) { // if this fails (due to permissions issue), we re-throw the error with more context throw new Error( - 'At least one Sentinel is associated with an Autotask. Additional API key permissions are required to access Autotasks. Alternatively, remove the association with the autotask to complete this action.', + 'At least one Sentinel is associated with an Action. Additional API key permissions are required to access Actions. Alternatively, remove the association with the autotask to complete this action.', ); } // also throw with original error if its not a permission issue @@ -378,10 +381,10 @@ export const validateAdditionalPermissionsOrThrow = async ( } case 'Autotasks': // Check for access to Relayers - // Enumerate all autotasks, and check if any autotask has a relayer associated - const autotasksWithRelayers = (Object.values(resources) as unknown as YAutotask[]).filter(r => !!r.relayer); - // If there are autotasks with relayers associated, then try to list relayers - if (!_.isEmpty(autotasksWithRelayers)) { + // Enumerate all actions, and check if any action has a relayer associated + const actionsWithRelayers = (Object.values(resources) as unknown as YAction[]).filter((r) => !!r.relayer); + // If there are actions with relayers associated, then try to list relayers + if (!_.isEmpty(actionsWithRelayers)) { try { await getRelayClient(teamApiKey).list(); return; @@ -390,7 +393,7 @@ export const validateAdditionalPermissionsOrThrow = async ( if (isUnauthorisedError(e)) { // if this fails (due to permissions issue), we re-throw the error with more context throw new Error( - 'At least one Autotask is associated with a Relayer. Additional API key permissions are required to access Relayers. Alternatively, remove the association with the relayer to complete this action.', + 'At least one Action is associated with a Relayer. Additional API key permissions are required to access Relayers. Alternatively, remove the association with the relayer to complete this action.', ); } // also throw with original error if its not a permission issue diff --git a/template/README.md b/template/README.md index dfd2c5e..911ed7d 100644 --- a/template/README.md +++ b/template/README.md @@ -24,7 +24,7 @@ Alternatively, you can install it directly into an existing project with: ## Setup -This plugin allows you to define Autotasks, Sentinels, Notifications, Relayers, Contracts, Policies and Secrets declaratively from a `serverless.yml` and provision them via the CLI using `serverless deploy`. An example template below with an autotask, a relayer, a policy and a single relayer API key defined: +This plugin allows you to define Actions, Sentinels, Notifications, Relayers, Contracts, Policies and Secrets declaratively from a `serverless.yml` and provision them via the CLI using `serverless deploy`. An example template below with an action, a relayer, a policy and a single relayer API key defined: ```yaml service: defender-serverless-template @@ -42,9 +42,9 @@ defender: secret: '${env:TEAM_API_SECRET}' functions: - autotask-example-1: + action-example-1: name: 'Hello world from serverless' - path: './autotasks/hello-world' + path: './actions/hello-world' relayer: ${self:resources.Resources.relayers.relayer-1} trigger: type: 'schedule' @@ -86,9 +86,9 @@ This means that all Defender resources, that are not defined in your current tem Any resource removed from the `serverless.yml` file does _not_ get automatically deleted in order to prevent inadvertent resource deletion. For this behaviour to be anticipated, SSOT mode must be enabled. -### Secrets (Autotask) +### Secrets (Action) -Autotask secrets can be defined both globally and per stack. Secrets defined under `global` are not affected by changes to the `stackName` and will retain when redeployed under a new stack. Secrets defined under `stack` will be removed (on the condition that [SSOT mode](#SSOT-mode) is enabled) when the stack is redeployed under a new `stackName`. To reference secrets defined under `stack`, use the following format: `_`, for example `mystack_test`. +Action secrets can be defined both globally and per stack. Secrets defined under `global` are not affected by changes to the `stackName` and will retain when redeployed under a new stack. Secrets defined under `stack` will be removed (on the condition that [SSOT mode](#SSOT-mode) is enabled) when the stack is redeployed under a new `stackName`. To reference secrets defined under `stack`, use the following format: `_`, for example `mystack_test`. ```yaml secrets: @@ -107,7 +107,7 @@ We provide auto-generated documentation based on the JSON schemas: - [Defender Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/defender.md) - [Provider Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/provider.md) -- [Function (Autotask) Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/function.md) +- [Function (Action) Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/function.md) - [Resources Property](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs/resources.md) More information on types can be found [here](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/index.ts). Specifically, the types preceded with `Y` (e.g. YRelayer). For the schemas, you can check out the [docs-schema](https://github.com/OpenZeppelin/defender-serverless/blob/main/src/types/docs-schemas) folder. @@ -140,11 +140,11 @@ You can use `sls remove` to remove all defender resources defined in the `server ### Logs -You can use `sls logs --function --data {...}` to retrieve the latest autotask logs for a given autotask identifier (e.g. mystack.autotask-example-1). This command will run continiously and retrieve logs every 2 seconds. The `--data` flag is optional. +You can use `sls logs --function --data {...}` to retrieve the latest action logs for a given action identifier (e.g. mystack.action-example-1). This command will run continiously and retrieve logs every 2 seconds. The `--data` flag is optional. ### Invoke -You can use `sls invoke --function ` to manually run an autotask, given its identifier (e.g. mystack.autotask-example-1). +You can use `sls invoke --function ` to manually run an action, given its identifier (e.g. mystack.action-example-1). > Each command has a standard output to a JSON object. diff --git a/template/autotasks/hello-world/index.js b/template/actions/hello-world/index.js similarity index 100% rename from template/autotasks/hello-world/index.js rename to template/actions/hello-world/index.js diff --git a/template/serverless.yml b/template/serverless.yml index beb8d9c..ca88251 100644 --- a/template/serverless.yml +++ b/template/serverless.yml @@ -15,9 +15,9 @@ defender: secret: '${env:TEAM_API_SECRET}' functions: - autotask-example-1: + action-example-1: name: 'Hello world from serverless' - path: './autotasks/hello-world' + path: './actions/hello-world' relayer: ${self:resources.Resources.relayers.relayer-1} trigger: type: 'schedule' diff --git a/yarn.lock b/yarn.lock index 11bdcb9..c905ef3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,7 +199,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.7.0": +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== @@ -280,7 +280,7 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0", "@ethersproject/networks@^5.7.1": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== @@ -561,6 +561,105 @@ lodash "^4.17.19" node-fetch "^2.6.0" +"@openzeppelin/platform-sdk-action-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-action-client/-/platform-sdk-action-client-0.1.0.tgz#773dddf641ba61dafa14057d0685986c9a4d56a7" + integrity sha512-667URONj5LCkfZiIW+NEYxOjWjEhZEUeZ87/g8hvsZQtTlMswZ3vU8ivV2ZM0OpBmcMfOmqyVIFBEymkg8v3Lw== + dependencies: + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + jszip "^3.5.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-base-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-base-client/-/platform-sdk-base-client-0.1.0.tgz#811e64b8591a5a11daa8eee8bc9a7fc08624c4e3" + integrity sha512-62ekx1AyxhntmWneaoUXpqtMT/fhRVrmJ5zNXLWO/Puvv2JVCXE/KcXHjQg9PYvUXrpkD+UehOPihkRNmH596w== + dependencies: + amazon-cognito-identity-js "^6.0.1" + async-retry "^1.3.3" + +"@openzeppelin/platform-sdk-deploy-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-deploy-client/-/platform-sdk-deploy-client-0.1.0.tgz#b59925d7b3446840af34ed9e8f524e1f81b62131" + integrity sha512-pabD23zJ0kA0yEH/tGbak8lCsH2JKMYWqeXhZWIbSXTDbfk1GIjbDADCQcFsnNtytoI4oN2f+iy5HQCiGT2NPA== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-monitor-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-monitor-client/-/platform-sdk-monitor-client-0.1.0.tgz#fba9a4510090d2373b8ad03a277c3cb6c7d3c48c" + integrity sha512-GWvqGb4LEGImKBlNWmuuqt0Ld0B0uli0RCTnEOALW2bHXT9zxaQXmxxBLwJn9RLL3yQYDh5uVhC7qeoS5QaDyQ== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-notification-channel-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-notification-channel-client/-/platform-sdk-notification-channel-client-0.1.0.tgz#75bbcd6d9a00b1ac040a601a4fdf31444e14521c" + integrity sha512-5BEIuU9lwfn5M8GP02bUkWGs5naiQwzjSa8IqhojmFyqXkxCesG78ihNbd9ovIa6dAhiQVEbEwseeX5kcEf5CQ== + dependencies: + "@ethersproject/abi" "^5.6.3" + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-proposal-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-proposal-client/-/platform-sdk-proposal-client-0.1.0.tgz#e6dbc26f45abdfa17675e4d5e9d08f86f1075cf6" + integrity sha512-o5ufSPvD5qwQlVi3dS521FQcsTscD3muO5AUk4sgUHUO3F7CNHUsJ8UpK8aK2nWf8wzdKsbwEPPsGq8vFdawag== + dependencies: + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + ethers "^5.7.2" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-relay-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-relay-client/-/platform-sdk-relay-client-0.1.0.tgz#5284834dd0d4b499e42213a2376193258af166a3" + integrity sha512-Ue7bEKBijYnOCj90GWeJJJ7K7pKiyfSKRGYcZBjJ/vrMMxWOW34EUfUG7TV1Q9CVT0M0X8FD+1PHxRYTLK5Ciw== + dependencies: + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + axios "^1.4.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk-relay-signer-client@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk-relay-signer-client/-/platform-sdk-relay-signer-client-0.1.0.tgz#43ab488a7164b13bd9b699230e4b7a87da2620af" + integrity sha512-w3NFk5HFX2RdppqlxX+qk5Qt0Rx7msayuW2D1ZRyWYYRinpKNgc0vZr4fLE6JaLC15HgrL1pMWjBV4OX8fUdUQ== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.1" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + amazon-cognito-identity-js "^6.0.1" + axios "^1.4.0" + lodash "^4.17.19" + +"@openzeppelin/platform-sdk@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/platform-sdk/-/platform-sdk-0.1.0.tgz#26222b4b738436bd97fec137252cfb45b069e367" + integrity sha512-xmYalPOikZyY/t5XE43vj2xu/T2GFkpzzISYXNQNu1NqtNMQdAnaw24VK2M3m636QoAgcpjubXp/9ckN1tdpEA== + dependencies: + "@openzeppelin/platform-sdk-action-client" "^0.1.0" + "@openzeppelin/platform-sdk-base-client" "^0.1.0" + "@openzeppelin/platform-sdk-deploy-client" "^0.1.0" + "@openzeppelin/platform-sdk-monitor-client" "^0.1.0" + "@openzeppelin/platform-sdk-notification-channel-client" "^0.1.0" + "@openzeppelin/platform-sdk-proposal-client" "^0.1.0" + "@openzeppelin/platform-sdk-relay-client" "^0.1.0" + "@openzeppelin/platform-sdk-relay-signer-client" "^0.1.0" + "@polka/url@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31" @@ -807,6 +906,11 @@ async@^2.6.4: dependencies: lodash "^4.17.14" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -814,6 +918,15 @@ axios@^0.21.2: dependencies: follow-redirects "^1.14.0" +axios@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" + integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -949,6 +1062,13 @@ colors@1.0.x: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -997,6 +1117,11 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + dequal@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" @@ -1318,11 +1443,20 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.14.0: +follow-redirects@^1.14.0, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs-extra@11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" @@ -2079,6 +2213,18 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -2256,6 +2402,11 @@ prompt@^1.3.0: revalidator "0.1.x" winston "2.x" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"